如何使用联系人框架在iOS 9中获取所有联系人记录

76

大部分的地址簿框架在iOS 9中已经被弃用。在新的联系人框架文档中,只展示了如何匹配NSPredicate的记录,但如果我想要所有记录呢?

20个回答

122

其他两个答案只会从具有defaultContainerIdentifier的容器中加载联系人。在用户拥有多个容器的情况下(例如Exchange和iCloud账户都用于存储联系人),这只会从配置为默认值的帐户中加载联系人。因此,它不会像问题作者所要求的那样加载所有联系人。

相反,您可能需要做的是获取所有容器并迭代它们以从每个容器中提取所有联系人。以下代码片段是我们在其中一个应用程序中执行此操作的示例(使用Swift):

lazy var contacts: [CNContact] = {
    let contactStore = CNContactStore()
    let keysToFetch = [
        CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName),
        CNContactEmailAddressesKey,
        CNContactPhoneNumbersKey,
        CNContactImageDataAvailableKey,
        CNContactThumbnailImageDataKey]

    // Get all the containers
    var allContainers: [CNContainer] = []
    do {
        allContainers = try contactStore.containersMatchingPredicate(nil)
    } catch {
        print("Error fetching containers")
    }

    var results: [CNContact] = []

    // Iterate all containers and append their contacts to our results array
    for container in allContainers {
        let fetchPredicate = CNContact.predicateForContactsInContainerWithIdentifier(container.identifier)

        do {
            let containerResults = try contactStore.unifiedContactsMatchingPredicate(fetchPredicate, keysToFetch: keysToFetch)
            results.appendContentsOf(containerResults)
        } catch {
            print("Error fetching results for container")
        }
    }

    return results
}()

@flohei 这是一个很好的答案!但是你添加了“lazy”关键字?它在这里有什么作用? - Developer
1
我这里只使用了延迟加载,因为我不需要立即加载所有联系人。我只需要在用户执行某些操作后才需要它们。 - flohei
1
如果我使用enumerateContactsWithFetchRequest:error:usingBlock:方法而不是上述方法,会有什么区别?它们之间有什么不同?我是否也能在这里获取所有联系人? - Shyam
3
根据苹果的示例代码 "ManagingContacts",使用 "enumerateContactsWithFetchRequest" 可以足够地获取所有可用联系人。此外,获取操作不应该在主队列上执行。 - Marek H
我在使用contactStore.containersMatchingPredicate(nil)时发现了一个问题,这个函数返回一个空数组。但是手机里有一些联系人。这是在iPhone X上发生的。 - 鸡肉味嘎嘣脆
一些联系人可能会在多个容器中。因此,您可以使用Set<CNContact>,或者过滤重复的[CNContact]。但是,过滤重复项的操作在底层确实使用了Set<T>,所以最好直接使用Set。 - Cœur

45

Objective-C:

//ios 9+
CNContactStore *store = [[CNContactStore alloc] init];
[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
    if (granted == YES) {
        //keys with fetching properties
        NSArray *keys = @[CNContactFamilyNameKey, CNContactGivenNameKey, CNContactPhoneNumbersKey, CNContactImageDataKey];
        NSString *containerId = store.defaultContainerIdentifier;
        NSPredicate *predicate = [CNContact predicateForContactsInContainerWithIdentifier:containerId];
        NSError *error;
        NSArray *cnContacts = [store unifiedContactsMatchingPredicate:predicate keysToFetch:keys error:&error];
        if (error) {
            NSLog(@"error fetching contacts %@", error);
        } else {
            for (CNContact *contact in cnContacts) {
                // copy data to my custom Contacts class. 
                Contact *newContact = [[Contact alloc] init];
                newContact.firstName = contact.givenName;
                newContact.lastName = contact.familyName;
                UIImage *image = [UIImage imageWithData:contact.imageData];
                newContact.image = image;
                for (CNLabeledValue *label in contact.phoneNumbers) {
                    NSString *phone = [label.value stringValue];
                    if ([phone length] > 0) {
                        [contact.phones addObject:phone];
                    }
                }
            }
        }
    }        
}];

还可以使用enumerateContactsWithFetchRequest方法来获取所有联系人:

CNContactStore *store = [[CNContactStore alloc] init];
[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
    if (granted == YES) {
        //keys with fetching properties
        NSArray *keys = @[CNContactFamilyNameKey, CNContactGivenNameKey, CNContactPhoneNumbersKey, CNContactImageDataKey];
        CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];
        NSError *error;
        BOOL success = [store enumerateContactsWithFetchRequest:request error:&error usingBlock:^(CNContact * __nonnull contact, BOOL * __nonnull stop) {
            if (error) {
                NSLog(@"error fetching contacts %@", error);
            } else {
                // copy data to my custom Contact class. 
                Contact *newContact = [[Contact alloc] init];
                newContact.firstName = contact.givenName;
                newContact.lastName = contact.familyName;
                // etc.
            }
        }];
    }        
}];
如果您想按姓名过滤联系人,则可以使用以下内容:

Obj-C:

// keys from example above
NSArray *keys = @[CNContactFamilyNameKey, CNContactGivenNameKey, CNContactPhoneNumbersKey, CNContactImageDataKey];
NSArray *cnContacts = [store unifiedContactsMatchingPredicate:[CNContact predicateForContactsMatchingName:@"John Appleseed"] keysToFetch:keys error:&error];

Swift 3:

let store = CNContactStore()
let contacts = try store.unifiedContactsMatchingPredicate(CNContact.predicateForContactsMatchingName("Appleseed"), keysToFetch:[CNContactGivenNameKey, CNContactFamilyNameKey])

官方文档在这里:https://developer.apple.com/reference/contacts


18

使用 SwiftContacts 框架获取所有联系人,包括姓名和电话号码。

import Contacts

let store = CNContactStore()
store.requestAccessForEntityType(.Contacts, completionHandler: {
    granted, error in

    guard granted else {
        let alert = UIAlertController(title: "Can't access contact", message: "Please go to Settings -> MyApp to enable contact permission", preferredStyle: .Alert)
        alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
        self.presentViewController(alert, animated: true, completion: nil)
        return
    }

    let keysToFetch = [CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName), CNContactPhoneNumbersKey]
    let request = CNContactFetchRequest(keysToFetch: keysToFetch)
    var cnContacts = [CNContact]()

    do {
        try store.enumerateContactsWithFetchRequest(request){
            (contact, cursor) -> Void in
            cnContacts.append(contact)
        }
    } catch let error {
        NSLog("Fetch contact error: \(error)")
    }

    NSLog(">>>> Contact list:")
    for contact in cnContacts {
        let fullName = CNContactFormatter.stringFromContact(contact, style: .FullName) ?? "No Name"
        NSLog("\(fullName): \(contact.phoneNumbers.description)")
    }
})

获取联系人是一个耗时的操作,因此您不应该阻塞主UI线程。请在后台线程上执行CNContactFetchRequest。这就是我将代码放在completionHandler中的原因。它在后台线程上运行。


这句话的意思是:“这就是为什么我把代码放到completionHandler中。它在后台线程上运行。” 我认为这段代码总是在主线程上运行。在你的try块之前和try块内尝试打印Thread.current.isMainThread并检查是否返回true。要使其在BG上运行,首先,completionHandler应该是@escaping以便在BG线程上运行,并且您应该将请求包装在某个BG线程中,如下所示:DispatchQueue.global(qos: .default).async { //fetch here}。据我所知,您的代码在主线程上运行。 - Rajan Maheshwari

12

苹果实际上建议使用CNContactStore的enumerateContactsWithFetchRequest方法来获取所有联系人,而不是使用unifiedContactsMatchingPredicate方法。

以下是Obj-C的可工作代码。

CNContactStore *store = [[CNContactStore alloc] init];

//keys with fetching properties
NSArray *keys = @[CNContactGivenNameKey, CNContactPhoneNumbersKey]; 
CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];
NSError *error;

[store enumerateContactsWithFetchRequest:request error:&error usingBlock:^(CNContact * __nonnull contact, BOOL * __nonnull stop) {

        // access it this way -> contact.givenName; etc

}];

这是苹果推荐使用枚举函数的链接: https://developer.apple.com/reference/contacts/cncontactstore/1403266-unifiedcontactsmatchingpredicate?language=objc#discussion

如果链接已过期,这是苹果的原话:

如果没有找到匹配项,则此方法返回一个空数组(或在出现错误的情况下返回nil)。仅使用CNContact类谓词中的谓词。该方法不支持复合谓词。由于统一,返回的联系人可能具有不同于您指定的标识符。要获取所有联系人,请使用enumerateContactsWithFetchRequest:error:usingBlock:


1
https://developer.apple.com/reference/contacts/cncontactstore/1403266-unifiedcontactsmatchingpredicate?language=objc#discussion - GeneCode
Lambda是一个阻塞/同步调用吗? - mr5

9

针对Swift 4

        var results: [CNContact] = []

        let fetchRequest = CNContactFetchRequest(keysToFetch: [CNContactGivenNameKey as CNKeyDescriptor, CNContactFamilyNameKey as CNKeyDescriptor, CNContactMiddleNameKey as CNKeyDescriptor, CNContactEmailAddressesKey as CNKeyDescriptor,CNContactPhoneNumbersKey as CNKeyDescriptor])

        fetchRequest.sortOrder = CNContactSortOrder.userDefault

        let store = CNContactStore()

        do {
            try store.enumerateContacts(with: fetchRequest, usingBlock: { (contact, stop) -> Void in
                print(contact.phoneNumbers.first?.value ?? "no")
                results.append(contact)

            })
        }
        catch let error as NSError {
            print(error.localizedDescription)
        }

旧版本中,swift的变量“results”包含所有联系人

let contactStore = CNContactStore()
    var results: [CNContact] = []
    do {
        try contactStore.enumerateContactsWithFetchRequest(CNContactFetchRequest(keysToFetch: [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactMiddleNameKey, CNContactEmailAddressesKey,CNContactPhoneNumbersKey])) {
            (contact, cursor) -> Void in
            results.append(contact)
            }
    }
    catch{
        print("Handle the error please")
    }

我们如何在Objective C中实现这个? - user3306145

7
从iOS9的联系人框架中获取全名、电子邮件地址、电话号码、个人资料图片和生日日期。
#pragma mark
#pragma mark -- Getting Contacts From AddressBook
 -(void)contactsDetailsFromAddressBook{
//ios 9+
CNContactStore *store = [[CNContactStore alloc] init];
[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
    if (granted == YES) {
        //keys with fetching properties
        NSArray *keys = @[CNContactBirthdayKey,CNContactFamilyNameKey, CNContactGivenNameKey, CNContactPhoneNumbersKey, CNContactImageDataKey, CNContactEmailAddressesKey];
        NSString *containerId = store.defaultContainerIdentifier;
        NSPredicate *predicate = [CNContact predicateForContactsInContainerWithIdentifier:containerId];
        NSError *error;
        NSArray *cnContacts = [store unifiedContactsMatchingPredicate:predicate keysToFetch:keys error:&error];
        if (error) {
            NSLog(@"error fetching contacts %@", error);
        } else {
            NSString *phone;
            NSString *fullName;
            NSString *firstName;
            NSString *lastName;
            UIImage *profileImage;
            NSDateComponents *birthDayComponent;
            NSMutableArray *contactNumbersArray;
            NSString *birthDayStr;
            NSMutableArray *emailArray;
            NSString* email = @"";
            for (CNContact *contact in cnContacts) {
                // copy data to my custom Contacts class.
                firstName = contact.givenName;
                lastName = contact.familyName;
                birthDayComponent = contact.birthday;
                if (birthDayComponent == nil) {
                    // NSLog(@"Component: %@",birthDayComponent);
                    birthDayStr = @"DOB not available";
                }else{
                    birthDayComponent = contact.birthday;
                    NSInteger day = [birthDayComponent day];
                    NSInteger month = [birthDayComponent month];
                    NSInteger year = [birthDayComponent year];
                    // NSLog(@"Year: %ld, Month: %ld, Day: %ld",(long)year,(long)month,(long)day);
                    birthDayStr = [NSString stringWithFormat:@"%ld/%ld/%ld",(long)day,(long)month,(long)year];
                }
                if (lastName == nil) {
                    fullName=[NSString stringWithFormat:@"%@",firstName];
                }else if (firstName == nil){
                    fullName=[NSString stringWithFormat:@"%@",lastName];
                }
                else{
                    fullName=[NSString stringWithFormat:@"%@ %@",firstName,lastName];
                }
                UIImage *image = [UIImage imageWithData:contact.imageData];
                if (image != nil) {
                    profileImage = image;
                }else{
                    profileImage = [UIImage imageNamed:@"placeholder.png"];
                }
                for (CNLabeledValue *label in contact.phoneNumbers) {
                    phone = [label.value stringValue];
                    if ([phone length] > 0) {
                        [contactNumbersArray addObject:phone];
                    }
                }
                ////Get all E-Mail addresses from contacts
                for (CNLabeledValue *label in contact.emailAddresses) {
                    email = label.value;
                    if ([email length] > 0) {
                        [emailArray addObject:email];
                    }
                }
                //NSLog(@"EMAIL: %@",email);
                NSDictionary* personDict = [[NSDictionary alloc] initWithObjectsAndKeys: fullName,@"fullName",profileImage,@"userImage",phone,@"PhoneNumbers",birthDayStr,@"BirthDay",email,@"userEmailId", nil];
                // NSLog(@"Response: %@",personDict);
                [self.contactsArray addObject:personDict];
            }
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.tableViewRef reloadData];
            });
        }
    }
}];
}

但是我无法获取一些联系人的姓名,请帮帮我,我得到的是没有名字和姓氏的号码。 - user3306145
@user3306145,你是否使用了我在上面提到的相同代码? - Mannam Brahmam
你是唯一一个提到使用上述代码无法获取联系人姓名(名字和姓氏)的人。请检查您的通讯录,确保所有联系人都是按名字和姓氏的引用保存的。 - Mannam Brahmam
让我们在聊天中继续这个讨论 - Mannam Brahmam

6

@rocolitis 在 Swift 中的回答!根据 Apple 的文档,他的回答是执行此操作的最正确方式。

let contactStore = CNContactStore()
let keys = [CNContactPhoneNumbersKey, CNContactFamilyNameKey, CNContactGivenNameKey, CNContactNicknameKey] as [CNKeyDescriptor]
let request = CNContactFetchRequest(keysToFetch: keys)

try? contactStore.enumerateContacts(with: request) { (contact, error) in

    // Do something with contact

}

你应该先检查你对联系人的访问权限!

let authorization = CNContactStore.authorizationStatus(for: CNEntityType.contacts)

switch authorization {
case .authorized: break
case .denied: break
case .restricted: break
case .notDetermined: break
}

1
我之前完全没有想明白"keysToFetch"应该是什么。直到我将字符串数组转换为CNKeyDescriptor数组后,才恍然大悟。谢谢! - ghostatron
没问题!请点赞我的回答,这样更多的人就能看到它了! - Reid

6
swift 3和Xcode 8中,您可以获取所有联系人列表。
let keys = [CNContactGivenNameKey ,CNContactImageDataKey,CNContactPhoneNumbersKey]
        var message: String!
        //let request=CNContactFetchRequest(keysToFetch: keys)
        let contactsStore = AppDelegate.AppDel.contactStore
        // Get all the containers
        var allContainers: [CNContainer] = []
        do {
            allContainers = try contactsStore.containers(matching: nil)
        } catch {
            print("Error fetching containers")
        }



        // Iterate all containers and append their contacts to our results array
        for container in allContainers {
            let fetchPredicate = CNContact.predicateForContactsInContainer(withIdentifier: container.identifier)

            do {
                let containerResults = try contactsStore.unifiedContacts(matching: fetchPredicate, keysToFetch: keys as [CNKeyDescriptor])
                self.results.append(contentsOf: containerResults)
                self.tableView.reloadData()
                message="\(self.results.count)"
            } catch {
                print("Error fetching results for container")
            }
        }

5
首先获取默认容器标识符,然后使用谓词匹配容器标识符。
let keysToFetch = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey]
let containerId = CNContactStore().defaultContainerIdentifier()
let predicate: NSPredicate = CNContact.predicateForContactsInContainerWithIdentifier(containerId)
let contacts = try CNContactStore().unifiedContactsMatchingPredicate(predicate, keysToFetch: keysToFetch)

这个怎么能获取所有联系人呢?这不只是从默认容器中获取联系人吗?其他容器呢? - flohei

4

iOS 9 中的 CNContact

Objective C

#import "ViewController.h"
#import <Contacts/Contacts.h>
@interface ViewController ()
{
  NSMutableArray *arrayTableData;
}

@end

@implementation ViewController

-(void)viewDidLoad
{
  [self fetchContactsandAuthorization];
}


//This method is for fetching contacts from iPhone.Also It asks authorization permission.
-(void)fetchContactsandAuthorization
{
   // Request authorization to Contacts
   CNContactStore *store = [[CNContactStore alloc] init];
   [store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
   if (granted == YES)
   {
      //keys with fetching properties
      NSArray *keys = @[CNContactFamilyNameKey, CNContactGivenNameKey, CNContactPhoneNumbersKey, CNContactImageDataKey];
      NSString *containerId = store.defaultContainerIdentifier;
      NSPredicate *predicate = [CNContact predicateForContactsInContainerWithIdentifier:containerId];
      NSError *error;
      NSArray *cnContacts = [store unifiedContactsMatchingPredicate:predicate keysToFetch:keys error:&error];
      if (error) 
      {
         NSLog(@"error fetching contacts %@", error);
      } 
      else 
      {
            NSString *phone;
            NSString *fullName;
            NSString *firstName;
            NSString *lastName;
            UIImage *profileImage;
            NSMutableArray *contactNumbersArray = [[NSMutableArray alloc]init];
            for (CNContact *contact in cnContacts) {
                // copy data to my custom Contacts class.
                firstName = contact.givenName;
                lastName = contact.familyName;
                if (lastName == nil) {
                    fullName=[NSString stringWithFormat:@"%@",firstName];
                }else if (firstName == nil){
                    fullName=[NSString stringWithFormat:@"%@",lastName];
                }
                else{
                    fullName=[NSString stringWithFormat:@"%@ %@",firstName,lastName];
                }
                UIImage *image = [UIImage imageWithData:contact.imageData];
                if (image != nil) {
                    profileImage = image;
                }else{
                    profileImage = [UIImage imageNamed:@"person-icon.png"];
                }
                for (CNLabeledValue *label in contact.phoneNumbers) {
                    phone = [label.value stringValue];
                    if ([phone length] > 0) {
                        [contactNumbersArray addObject:phone];
                    }
                }
                NSDictionary* personDict = [[NSDictionary alloc] initWithObjectsAndKeys: fullName,@"fullName",profileImage,@"userImage",phone,@"PhoneNumbers", nil];
                [arrayTableData addObject:[NSString stringWithFormat:@"%@",[personDict objectForKey:@"fullName"]]];
                NSLog(@"The contactsArray are - %@",arrayTableData);
            }
            dispatch_async(dispatch_get_main_queue(), ^{
                [tableViewContactData reloadData];
            });
        }
    }
}];
}
@end

输出结果是:
The contactsArray are - (
"John Appleseed",
"Kate Bell",
"Anna Haro",
"Daniel Higgins",
"David Taylor",
"Hank Zakroff"
}

仅显示了一些联系人.. 如何获取我iPhone上的所有联系人..... - Hari Narayanan
请从您的原始设备上检查它。 - user3182143
谢谢@user3182143,我使用联系人获取请求来解决问题CNContactFetchRequest *fetchRequest = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys]; 然后将其保存在数组中[store enumerateContactsWithFetchRequest:fetchRequest error:nil usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) { [contact_array addObject:contact]; //将所有联系人列表的对象添加到数组中 NSLog(@"mutable_copy count : %lu",(unsigned long)contact_array.count); }]; - Hari Narayanan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接