iOS 6通讯录无法工作?

14

通过编程从通讯录中检索电子邮件地址的方法在iOS 6设备上似乎不再起作用。它在iOS 5中有效,并且奇怪的是,在iOS 6模拟器中仍然有效。是否有一种新的方式可以从用户的通讯录中编程检索联系人?

ABAddressBookRef addressBook = ABAddressBookCreate();
CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFIndex nPeople = ABAddressBookGetPersonCount(addressBook);

self.contacts = [[NSMutableArray alloc] init];

int contactIndex = 0;
for (int i = 0; i < nPeople; i++) {
    // Get the next address book record.
    ABRecordRef record = CFArrayGetValueAtIndex(allPeople, i);        

    // Get array of email addresses from address book record.
    ABMultiValueRef emailMultiValue = ABRecordCopyValue(record, kABPersonEmailProperty);
    NSArray *emailArray = (__bridge_transfer NSArray *)ABMultiValueCopyArrayOfAllValues(emailMultiValue);

    [self.contacts addObject:emailArray];
}

为了澄清,上面的代码不会导致崩溃,它只是返回了空结果。ABAddressBookCopyArrayOfAllPeople返回一个空数组。谢谢!

4个回答

52

我创建了一个帮助类AddressBookHelper,来处理向后兼容性。以下是核心代码:

-(BOOL)isABAddressBookCreateWithOptionsAvailable {
    return &ABAddressBookCreateWithOptions != NULL;
}

-(void)loadContacts {
    ABAddressBookRef addressBook;
    if ([self isABAddressBookCreateWithOptionsAvailable]) {
        CFErrorRef error = nil;
        addressBook = ABAddressBookCreateWithOptions(NULL,&error);
        ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
            // callback can occur in background, address book must be accessed on thread it was created on
            dispatch_async(dispatch_get_main_queue(), ^{
                if (error) {
                    [self.delegate addressBookHelperError:self];
                } else if (!granted) {
                    [self.delegate addressBookHelperDeniedAcess:self];
                } else {
                    // access granted
                    AddressBookUpdated(addressBook, nil, self);
                    CFRelease(addressBook);
                }
            });
        });
    } else {
        // iOS 4/5
        addressBook = ABAddressBookCreate();
        AddressBookUpdated(addressBook, NULL, self);
        CFRelease(addressBook);
    }
}

void AddressBookUpdated(ABAddressBookRef addressBook, CFDictionaryRef info, void *context) {
    AddressBookHelper *helper = (AddressBookHelper *)context;
    ABAddressBookRevert(addressBook);
    CFArrayRef people = ABAddressBookCopyArrayOfAllPeople(addressBook);

    // process the contacts to return
    NSArray *contacts = ...    

    [[helper delegate] addressBookHelper:helper finishedLoading:contacts];
};

嗨,我想知道为什么要调用dispatch_async()?我尝试过不调用它也可以完美地工作,并且在重新加载通讯录时不会阻塞我的UI。 - jklp
文档指出:“完成处理程序在任意队列上调用。如果您的应用程序在整个应用程序中使用地址簿,则需要确保将该地址簿的所有使用都分派到单个队列以确保正确的线程安全操作。” 在测试中,我遇到了一些崩溃(无法回忆细节),这表明存在线程安全问题,这促使我查看文档。 - Christopher Pickslay
dispatch_get_current_queue的文档指出:“dispatch_get_current_queue()函数仅建议用于调试和日志记录。除非它是全局队列或代码本身创建的队列之一,否则代码不得对返回的队列做任何假设。”因此,我倾向于保持原样,假设它在主队列上调用。我想loadContacts的整个体系结构可以分派到一个本地创建的队列中。 - Christopher Pickslay
为什么要调用ABAddressBookRevert?(顺便感谢您) - ettore
它可能对大多数使用情况不相关,但它基于ABExternalChangeCallback文档:"addressBook对象不会采取任何操作来刷新或同步缓存状态与通讯录数据库。 如果您想确保addressBook不包含过期值,请使用ABAddressBookRevert。" - Christopher Pickslay
显示剩余3条评论

12

尝试使用以下方法: 在程序中以编程方式访问地址簿之前,必须授予权限。这是我最终采取的方法。

  #import <AddressBookUI/AddressBookUI.h>

  // Request authorization to Address Book
  ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, NULL);

  if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined) {
    ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) {
      // First time access has been granted, add the contact
      [self _addContactToAddressBook];
    });
  }
  else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
    // The user has previously given access, add the contact
    [self _addContactToAddressBook];
  }
  else {
    // The user has previously denied access
    // Send an alert telling user to change privacy setting in settings app
  }

12

这可能与新的隐私控制有关。自iOS 6以来,应用程序在未得到用户许可的情况下无法访问用户的联系人。相关文件说明如下:

在iOS 6.0及更高版本中,如果调用方无法访问通讯录数据库:

• 对于链接到iOS 6.0及更高版本的应用程序,此函数返回NULL。

• 对于链接到早期iOS版本的应用程序,此函数返回一个空的只读数据库。

如果您没有看到权限警报弹出("SomeApp想要访问您的联系人"),那么直接的通讯录API可能会假定它们无法访问并静默失败。您可能需要从AddressBookUI框架中显示一些内容来触发它。


2
没错,你说得对。需要使用ABAddressBookRequestAccessWithCompletion()方法。但是我该如何检查这个方法是否存在(在iOS 6以下的版本中)?我在这里提出了后续问题:https://dev59.com/72jWa4cB1Zd3GeqPs62x - Keller

0

可能与新的隐私控制有关,自iOS 6以来,在设备上,应用程序未经用户许可无法访问用户的联系人。

代码:

-(void)addressBookValidation
{



NSUserDefaults *prefs=[NSUserDefaults standardUserDefaults];
ABAddressBookRef addressbook = ABAddressBookCreate();
__block BOOL accessGranted = NO;

if (ABAddressBookRequestAccessWithCompletion != NULL)
{
    if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined)
    {
        dispatch_semaphore_t sema = dispatch_semaphore_create(0);
        ABAddressBookRequestAccessWithCompletion(addressbook, ^(bool granted, CFErrorRef error)
                                                 {
                                                     accessGranted = granted;
                                                     dispatch_semaphore_signal(sema);
                                                 });
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        dispatch_release(sema);
    }
    else if(ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized)
    {
        accessGranted = YES;
    }
    else if (ABAddressBookGetAuthorizationStatus()==kABAuthorizationStatusDenied)
    {
        accessGranted = NO;
    }
    else if (ABAddressBookGetAuthorizationStatus()==kABAuthorizationStatusRestricted){
        accessGranted = NO;
    }
    else
    {
        accessGranted = YES;
    }


}
else
{
    accessGranted = YES;
}
[prefs setBool:accessGranted forKey:@"addressBook"];

NSLog(@"[prefs boolForKey:@'addressBook']--->%d",[prefs boolForKey:@"addressBook"]);
[prefs synchronize];
CFRelease(addressbook);
}

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