适用于iOS9.0以后的通讯录开发

需求中不少会出现选择联系人信息的情况,有些需要自定义UI,有些不需要。直接调用系统的不需要授权,自定义UI只获取数据的需要用户授权。

#import <ContactsUI/ContactsUI.h>  //有UI效果
#import <Contacts/Contacts.h>  //无UI效果,只拿到数据,UI可以自定义
<CNContactPickerDelegate>  //遵循协议
import Contacts
import ContactsUI
CNContactPickerDelegate
ContactsUI (有UI效果,无需自定义)
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    CNContactPickerViewController *cncpVC = [[CNContactPickerViewController alloc]init];
    cncpVC.delegate = self;
    [self presentViewController:cncpVC animated:YES completion:nil];
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let cpvc = CNContactPickerViewController()
        cpvc.delegate = self
        self.present(cpvc, animated: true, completion: nil)
    }

代理方法根据需求二选一实现

#pragma mark - CNContactPickerDelegate
/**
 选择单个联系人

 @param picker 通讯录列表
 @param contact 联系人对象
 */
-(void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact
{
    NSLog(@"%@ %@",contact.givenName,contact.familyName);
    
    for (CNLabeledValue *labelValue in contact.phoneNumbers) {
        //contact.phoneNumbers联系人下的所有电话号码
        NSString *phoneLabel = labelValue.label;
        CNPhoneNumber *phoneNumer = labelValue.value;
        NSString *phoneValue = phoneNumer.stringValue;
        
        NSLog(@"%@ %@", phoneLabel, phoneValue);
    }
}

/**
 选中某一联系人的某一属性

 @param picker 通讯录列表
 @param contactProperty 联系人属性
 */
-(void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperty:(CNContactProperty *)contactProperty
{
    NSLog(@"contact == %@\nkey == %@\nvalue == %@\nidentifier == %@\nlabel == %@",contactProperty.contact,contactProperty.key,contactProperty.value,contactProperty.identifier,contactProperty.label);
    if ([contactProperty.key isEqualToString:@"phoneNumbers"]) {
        for (CNLabeledValue *labelValue in contactProperty.contact.phoneNumbers) {
            if ([labelValue.identifier isEqualToString:contactProperty.identifier]) {
                CNPhoneNumber *phoneNumber = labelValue.value;
                NSString *phoneStr = phoneNumber.stringValue;
                NSLog(@"%@",phoneStr);
                break;
            }
        }
    }
}

/**
 点击取消

 @param picker 通讯录列表
 */
-(void)contactPickerDidCancel:(CNContactPickerViewController *)picker
{
    NSLog(@"click cancel");
}
func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) {
        print(contactProperty.contact.familyName + contactProperty.contact.givenName)
        if contactProperty.key == "phoneNumbers" {
            for labelVale in contactProperty.contact.phoneNumbers {
                if contactProperty.identifier == labelVale.identifier {
                    let phoneStr = labelVale.value.stringValue
                    print(phoneStr)
                }
            }
        }
    }

点选属性时打印结果

contact == <CNContact: 0x7f8ebb80d510: identifier=9E55CCA2-E951-4DD7-AC18-86C2243BAA44, givenName=John, familyName=Appleseed, organizationName=, phoneNumbers=(
    "<CNLabeledValue: 0x608000264140: identifier=8B15D69B-EEE8-4FAC-ACB9-041BEC74B880, label=_$!<Mobile>!$_, value=<CNPhoneNumber: 0x60800002ac60: countryCode=us, digits=8885555512>>",
    "<CNLabeledValue: 0x6080002641c0: identifier=7D2CD244-2CB3-4778-AC37-967B5837BC84, label=_$!<Home>!$_, value=<CNPhoneNumber: 0x60800002ace0: countryCode=us, digits=8885551212>>"
), emailAddresses=(
    "<CNLabeledValue: 0x6080002636c0: identifier=005253A8-BA89-4E3D-B2A3-C9C1B051A5D6, label=_$!<Work>!$_, [value=John-Appleseed@mac.com](mailto:value=John-Appleseed@mac.com)>"
), postalAddresses=(
    "<CNLabeledValue: 0x608000264380: identifier=B8BACE69-C9A4-4EEC-979B-197B0F3AD29A, label=_$!<Work>!$_, value=<CNPostalAddress: 0x608000289ce0: street=3494 Kuhl Avenue, subLocality=, city=Atlanta, subAdministrativeArea=, state=GA, postalCode=30303, country=USA, countryCode=ca>>",
    "<CNLabeledValue: 0x608000264400: identifier=4C5D91E4-3313-4154-B5E4-A4448F0E4AD6, label=_$!<Home>!$_, value=<CNPostalAddress: 0x608000289d30: street=1234 Laurel Street, subLocality=, city=Atlanta, subAdministrativeArea=, state=GA, postalCode=30303, country=USA, countryCode=us>>"
)>
key == phoneNumbers
value == <CNPhoneNumber: 0x60800002ace0: countryCode=us, digits=8885551212>
identifier == 7D2CD244-2CB3-4778-AC37-967B5837BC84
label == _$!<Home>!$_
屏幕快照 2017-08-22 下午5.22.55.png
Contacts(无UI效果,需自定义)
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    [self initData];
    [self createCustomUI];
}

-(void)initData
{
    self.dataArray = [[NSMutableArray alloc]init];
    [self registAuthorize];
}

-(void)createCustomUI
{
    self.tableView = [[UITableView alloc]initWithFrame:self.view.bounds];
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    [self.view addSubview:_tableView];
}

#pragma mark - 请求授权
-(void)registAuthorize
{
    CNAuthorizationStatus authorizationStatus = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
    
    if (authorizationStatus == CNAuthorizationStatusNotDetermined) {
        //用户还没有决定是否授权
        CNContactStore *contactStore = [[CNContactStore alloc] init];
        [contactStore requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
            if (granted) {
                NSLog(@"授权成功!");
                [self createCustomContact];
            } else {
                NSLog(@"授权失败, error=%@", error);
            }
        }];
    }else if (authorizationStatus == CNAuthorizationStatusAuthorized){
        //用户明确同意
        [self createCustomContact];
    }else{
        //用户明确拒绝或者程序中阻止了通讯录,可跳转通讯录手动打开
    }
}

#pragma mark - 获取数据
-(void)createCustomContact
{
    CNContactStore *contactStore = [[CNContactStore alloc]init];
    NSArray *keys = @[CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey];
    CNContactFetchRequest *request = [[CNContactFetchRequest alloc]initWithKeysToFetch:keys];
    __block int index = 1;
    [contactStore enumerateContactsWithFetchRequest:request error:nil usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
        
        [self.dataArray addObject:contact];
        
        NSLog(@"%@ %@",contact.givenName,contact.familyName);
        
        for (CNLabeledValue *labelValue in contact.phoneNumbers) {
            
            NSString *phoneLabel = labelValue.label;
            CNPhoneNumber *phoneNumer = labelValue.value;
            NSString *phoneValue = phoneNumer.stringValue;
            
            NSLog(@"%@ %@", phoneLabel, phoneValue);
        }
        
        NSLog(@"---------------------------------%d",index);
        index += 1;
        
        //回到主线程刷新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            [_tableView reloadData];
        });
        
    }];
}

#pragma tableViewDelegate
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return _dataArray.count;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cellID"];
    if (!cell) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cellID"];
    }
    if (_dataArray.count > 0) {
        CNContact *contact = _dataArray[indexPath.row];
        cell.textLabel.text = [NSString stringWithFormat:@"%@%@",contact.familyName,contact.givenName];
        CNLabeledValue *labelValue = [contact.phoneNumbers firstObject];
        CNPhoneNumber *phoneNum = labelValue.value;
        cell.detailTextLabel.text = [NSString stringWithFormat:@"%@",phoneNum.stringValue];
    }
    return cell;
}
override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        initData()
        createLayout()
    }

    func initData() -> Void {
        registAuthorize()
    }
    
    func registAuthorize() -> Void {
        let authorizeState = CNContactStore.authorizationStatus(for: .contacts)
        if authorizeState == .notDetermined {
            //用户还未决定授权
            let contactStore = CNContactStore.init()
            contactStore.requestAccess(for: .contacts, completionHandler: { (granted: Bool, error: Error?) in
                if granted {
                    print("授权成功")
                    self.createCustomContactData()
                }else{
                    print("授权失败")
                }
            })
            
        }else if authorizeState == .authorized {
            //用户确认授权
            createCustomContactData()
        }else{
            //用户拒绝或者有程序阻止了通讯录调起
        }
    }
    
    func createCustomContactData() -> Void {
        let contactStore = CNContactStore.init()
        let keys = [CNContactFamilyNameKey, CNContactGivenNameKey, CNContactPhoneNumbersKey]
        let request = CNContactFetchRequest.init(keysToFetch: keys as [CNKeyDescriptor])
        do {
            try contactStore.enumerateContacts(with: request, usingBlock: {(contact : CNContact, stop : UnsafeMutablePointer<ObjCBool>) -> Void in
                
                self.dataArray.append(contact)
                
                //1.获取姓名
                let lastName = contact.familyName
                let firstName = contact.givenName
                print("姓名 : \(lastName)\(firstName)")
                
                //2.获取电话号码
                let phoneNumbers = contact.phoneNumbers
                for phoneNumber in phoneNumbers {
                    print(phoneNumber.label ?? "")
                    print(phoneNumber.value.stringValue)
                }
                
                //3.回到主线程刷新UI
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
                
            })
        } catch  {
            print(error)
        }
        
    }
    
    func createLayout() -> Void {
        tableView = UITableView.init(frame: self.view.bounds)
        tableView.delegate = self
        tableView.dataSource = self
        tableView.tableFooterView = UIView()
        self.view.addSubview(tableView)
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: UITableViewCellStyle.subtitle
            , reuseIdentifier: "contactCell");
        if dataArray.isEmpty == false {
            print(self.dataArray)
            
            let contact = dataArray[indexPath.row]
            let labelValue = (contact as AnyObject).phoneNumbers.first
            cell.textLabel?.text = labelValue?.value.stringValue
            cell.detailTextLabel?.text = (contact as AnyObject).familyName + (contact as AnyObject).givenName
        }
        return cell
    }

打印结果

John Appleseed
_$!<Mobile>!$_ 888-555-5512
_$!<Home>!$_ 888-555-1212
---------------------------------1
Kate Bell
_$!<Mobile>!$_ (555) 564-8583
_$!<Main>!$_ (415) 555-3695
---------------------------------2
Anna Haro
_$!<Home>!$_ 555-522-8243
---------------------------------3
Daniel Higgins
_$!<Home>!$_ 555-478-7672
_$!<Mobile>!$_ (408) 555-5270
_$!<HomeFAX>!$_ (408) 555-3514
---------------------------------4
David Taylor
_$!<Home>!$_ 555-610-6679
---------------------------------5
Hank Zakroff
_$!<Work>!$_ (555) 766-4823
_$!<Other>!$_ (707) 555-1854
---------------------------------6
屏幕快照 2017-08-22 下午5.21.58.png
屏幕快照 2017-08-22 下午5.22.04.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,964评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,080评论 4 62
  • 我们都知道,iPhone手机在温度过高或过低时都无法正常使用,近日寒流席卷了我国的大部分地区,很多人就发现自己的手...
    移动开发联盟阅读 11,941评论 0 0
  • 以下代码适用于直接在代码中操作数据库,实际中可以使用navicat图形化界面先进行数据库测试数据的填充,在APP启...
    karthus阅读 246评论 0 0
  • 桃子因为失恋,已经一个星期没有出门了。 她跟男孩谈了三年恋爱,一开始男孩疯狂追求她,她是被深爱的人,不多不少都会有...
    奇葩菇凉阅读 700评论 0 4