使用Swift访问iOS地址簿:数组数量为零

我试图写一个简单的方法来要求用户访问他们的地址簿,然后打印出地址簿中每个人的名字。 我已经看到了很多教程,解释了如何在Objective-C中做到这一点,但是我们很难将其转换为快速。

这是迄今为止我所做的。 下面的块在我的viewDidLoad()方法中运行,并检查用户是否授权访问通讯录,如果他们还没有授权访问,第一个if语句将要求访问。 本节按预期工作。

var emptyDictionary: CFDictionaryRef? var addressBook: ABAddressBookRef? if (ABAddressBookGetAuthorizationStatus() == ABAuthorizationStatus.NotDetermined) { println("requesting access...") addressBook = !ABAddressBookCreateWithOptions(emptyDictionary,nil) ABAddressBookRequestAccessWithCompletion(addressBook,{success, error in if success { self.getContactNames(); } else { println("error") } }) } } else if (ABAddressBookGetAuthorizationStatus() == ABAuthorizationStatus.Denied || ABAddressBookGetAuthorizationStatus() == ABAuthorizationStatus.Restricted) { println("access denied") } else if (ABAddressBookGetAuthorizationStatus() == ABAuthorizationStatus.Authorized) { println("access granted") getContactNames() } 

一旦我知道用户已经授予访问权限,我就运行下面的getContactNames()方法。 经过很多来回之后,我终于可以通过添加takeRetainedValue()方法来将其编译,以便将ABAddressBookCopyArrayOfAllPeople从非托pipe数组返回的数组转换为托pipe数组,然后允许将CFArrayRef转换为NSArray的。

我遇到的问题是,contactList数组结束计数为0,因此for循环跳过。 在我的模拟器中,地址簿有6或7条logging,所以我期望数组的长度。 有任何想法吗?

 func getContactNames() { addressBook = !ABAddressBookCreateWithOptions(emptyDictionary,nil) var contactList: NSArray = ABAddressBookCopyArrayOfAllPeople(addressBook).takeRetainedValue() println("records in the array \(contactList.count)") // returns 0 for record:ABRecordRef in contactList { var contactPerson: ABRecordRef = record var contactName: String = ABRecordCopyCompositeName(contactPerson).takeRetainedValue() println ("contactName \(contactName)") } } 

另一点 – 如果我使用ABAddressBookGetPersonCount方法,它返回-1。

  var count: CFIndex = ABAddressBookGetPersonCount(addressBook); println("records in the array \(count)") // returns -1 

根据这个链接, ABAddressBookGetPersonCount在iOS中返回-1 ,看起来这个返回-1的函数可能与未被授予的权限有关,但是我肯定已经在上面的代码中请求了许可(并且在我在模拟器)

这现在更简单了。 首要的是要注意,如果你在未经授权的情况下创build了一个ABAddressBook,你会得到一个邪恶的地址簿 – 这不是零,而是对任何事情都不好。 以下是我目前如何build议您设置授权状态并在必要时请求授权:

 var adbk : ABAddressBook! func createAddressBook() -> Bool { if self.adbk != nil { return true } var err : Unmanaged<CFError>? = nil let adbk : ABAddressBook? = ABAddressBookCreateWithOptions(nil, &err).takeRetainedValue() if adbk == nil { println(err) self.adbk = nil return false } self.adbk = adbk return true } func determineStatus() -> Bool { let status = ABAddressBookGetAuthorizationStatus() switch status { case .Authorized: return self.createAddressBook() case .NotDetermined: var ok = false ABAddressBookRequestAccessWithCompletion(nil) { (granted:Bool, err:CFError!) in dispatch_async(dispatch_get_main_queue()) { if granted { ok = self.createAddressBook() } } } if ok == true { return true } self.adbk = nil return false case .Restricted: self.adbk = nil return false case .Denied: self.adbk = nil return false } } 

这里是如何循环所有人并打印出他们的名字:

 func getContactNames() { if !self.determineStatus() { println("not authorized") return } let people = ABAddressBookCopyArrayOfAllPeople(adbk).takeRetainedValue() as NSArray as [ABRecord] for person in people { println(ABRecordCopyCompositeName(person).takeRetainedValue()) } } 

编译器或者ABAddressBookRef被声明为AnyObject的AnyObject的框架似乎存在一个bug,但是它需要是NSObject才能将其从Unmanaged<ABAddressBookRef>!ABAddressBookCreateWithOptions返回。 解决方法是将其转换为不透明的C指针。 下面的代码工作,但它应该做更多的错误检查(也可能有更好的方法来解决这个问题):

 var addressBook: ABAddressBookRef? func extractABAddressBookRef(abRef: Unmanaged<ABAddressBookRef>!) -> ABAddressBookRef? { if let ab = abRef { return Unmanaged<NSObject>.fromOpaque(ab.toOpaque()).takeUnretainedValue() } return nil } func test() { if (ABAddressBookGetAuthorizationStatus() == ABAuthorizationStatus.NotDetermined) { println("requesting access...") var errorRef: Unmanaged<CFError>? = nil addressBook = extractABAddressBookRef(ABAddressBookCreateWithOptions(nil, &errorRef)) ABAddressBookRequestAccessWithCompletion(addressBook, { success, error in if success { self.getContactNames() } else { println("error") } }) } else if (ABAddressBookGetAuthorizationStatus() == ABAuthorizationStatus.Denied || ABAddressBookGetAuthorizationStatus() == ABAuthorizationStatus.Restricted) { println("access denied") } else if (ABAddressBookGetAuthorizationStatus() == ABAuthorizationStatus.Authorized) { println("access granted") self.getContactNames() } } func getContactNames() { var errorRef: Unmanaged<CFError>? addressBook = extractABAddressBookRef(ABAddressBookCreateWithOptions(nil, &errorRef)) var contactList: NSArray = ABAddressBookCopyArrayOfAllPeople(addressBook).takeRetainedValue() println("records in the array \(contactList.count)") for record:ABRecordRef in contactList { var contactPerson: ABRecordRef = record var contactName: String = ABRecordCopyCompositeName(contactPerson).takeRetainedValue() as NSString println ("contactName \(contactName)") } } 

对于那些寻找完整的工作解决scheme,这里是如何打印只有联系人名称 ,修改上述代码。 调用getAddressBookNames()来访问通讯录,例如在viewDidLoad()方法中。

 func getAddressBookNames() { let authorizationStatus = ABAddressBookGetAuthorizationStatus() if (authorizationStatus == ABAuthorizationStatus.NotDetermined) { NSLog("requesting access...") var emptyDictionary: CFDictionaryRef? var addressBook = !ABAddressBookCreateWithOptions(emptyDictionary, nil) ABAddressBookRequestAccessWithCompletion(addressBook,{success, error in if success { self.getContactNames(); } else { NSLog("unable to request access") } }) } else if (authorizationStatus == ABAuthorizationStatus.Denied || authorizationStatus == ABAuthorizationStatus.Restricted) { NSLog("access denied") } else if (authorizationStatus == ABAuthorizationStatus.Authorized) { NSLog("access granted") getContactNames() } } func getContactNames() { var errorRef: Unmanaged<CFError>? var addressBook: ABAddressBookRef? = extractABAddressBookRef(ABAddressBookCreateWithOptions(nil, &errorRef)) var contactList: NSArray = ABAddressBookCopyArrayOfAllPeople(addressBook).takeRetainedValue() println("number of contacts: \(contactList.count)") for record:ABRecordRef in contactList { var contactName: String = ABRecordCopyCompositeName(record).takeRetainedValue() as NSString NSLog("contactName: \(contactName)") } } func extractABAddressBookRef(abRef: Unmanaged<ABAddressBookRef>!) -> ABAddressBookRef? { if let ab = abRef { return Unmanaged<NSObject>.fromOpaque(ab.toOpaque()).takeUnretainedValue() } return nil } 

这里是完整的代码来访问联系人名称和电子邮件 – 这是使用一些其他答案中定义的帮助器方法完成的。

 func getAddressBookNames() { let authorizationStatus = ABAddressBookGetAuthorizationStatus() if (authorizationStatus == ABAuthorizationStatus.NotDetermined) { NSLog("requesting access...") var emptyDictionary: CFDictionaryRef? var addressBook = !ABAddressBookCreateWithOptions(emptyDictionary, nil) ABAddressBookRequestAccessWithCompletion(addressBook,{success, error in if success { self.processContactNames(); } else { NSLog("unable to request access") } }) } else if (authorizationStatus == ABAuthorizationStatus.Denied || authorizationStatus == ABAuthorizationStatus.Restricted) { NSLog("access denied") } else if (authorizationStatus == ABAuthorizationStatus.Authorized) { NSLog("access granted") processContactNames() } } func processContactNames() { var errorRef: Unmanaged<CFError>? var addressBook: ABAddressBookRef? = extractABAddressBookRef(ABAddressBookCreateWithOptions(nil, &errorRef)) var contactList: NSArray = ABAddressBookCopyArrayOfAllPeople(addressBook).takeRetainedValue() println("records in the array \(contactList.count)") for record:ABRecordRef in contactList { processAddressbookRecord(record) } } func processAddressbookRecord(addressBookRecord: ABRecordRef) { var contactName: String = ABRecordCopyCompositeName(addressBookRecord).takeRetainedValue() as NSString NSLog("contactName: \(contactName)") processEmail(addressBookRecord) } func processEmail(addressBookRecord: ABRecordRef) { let emailArray:ABMultiValueRef = extractABEmailRef(ABRecordCopyValue(addressBookRecord, kABPersonEmailProperty))! for (var j = 0; j < ABMultiValueGetCount(emailArray); ++j) { var emailAdd = ABMultiValueCopyValueAtIndex(emailArray, j) var myString = extractABEmailAddress(emailAdd) NSLog("email: \(myString!)") } } func extractABAddressBookRef(abRef: Unmanaged<ABAddressBookRef>!) -> ABAddressBookRef? { if let ab = abRef { return Unmanaged<NSObject>.fromOpaque(ab.toOpaque()).takeUnretainedValue() } return nil } func extractABEmailRef (abEmailRef: Unmanaged<ABMultiValueRef>!) -> ABMultiValueRef? { if let ab = abEmailRef { return Unmanaged<NSObject>.fromOpaque(ab.toOpaque()).takeUnretainedValue() } return nil } func extractABEmailAddress (abEmailAddress: Unmanaged<AnyObject>!) -> String? { if let ab = abEmailAddress { return Unmanaged.fromOpaque(abEmailAddress.toOpaque()).takeUnretainedValue() as CFStringRef } return nil } 

如果有人也试图获得联系人的电子邮件地址,我发现我需要创build两个类似于Wes展示的新的方法。

以下是getContactNames()函数的更新版本:

  func getContactNames() { var errorRef: Unmanaged<CFError>? addressBook = extractABAddressBookRef(ABAddressBookCreateWithOptions(nil, &errorRef)) var contactList: NSArray = ABAddressBookCopyArrayOfAllPeople(addressBook).takeRetainedValue() println("records in the array \(contactList.count)") for record:ABRecordRef in contactList { var contactPerson: ABRecordRef = record var contactName: String = ABRecordCopyCompositeName(contactPerson).takeRetainedValue() as NSString println ("contactName \(contactName)") var emailArray:ABMultiValueRef = extractABEmailRef(ABRecordCopyValue(contactPerson, kABPersonEmailProperty))! for (var j = 0; j < ABMultiValueGetCount(emailArray); ++j) { var emailAdd = ABMultiValueCopyValueAtIndex(emailArray, j) var myString = extractABEmailAddress(emailAdd) println("email: \(myString)") } } } 

以下是我创build的两个附加function:

  func extractABEmailRef (abEmailRef: Unmanaged<ABMultiValueRef>!) -> ABMultiValueRef? { if let ab = abEmailRef { return Unmanaged<NSObject>.fromOpaque(ab.toOpaque()).takeUnretainedValue() } return nil } func extractABEmailAddress (abEmailAddress: Unmanaged<AnyObject>!) -> String? { if let ab = abEmailAddress { return Unmanaged.fromOpaque(abEmailAddress.toOpaque()).takeUnretainedValue() as CFStringRef } return nil } 

再次感谢Wes在我最初的问题上的帮助,这帮助我了解了上述情况。

如果你需要额外的邮件给马特的答案:

 func getContacts() { if !self.determineStatus() { println("not authorized") } let people = ABAddressBookCopyArrayOfAllPeople(adbk).takeRetainedValue() as NSArray as [ABRecord] for person in people { // Name let name = ABRecordCopyCompositeName(person).takeRetainedValue() // Email let emails: ABMultiValueRef = ABRecordCopyValue(person, kABPersonEmailProperty).takeRetainedValue() for (var i = 0; i < ABMultiValueGetCount(emails); i++) { let email: String = ABMultiValueCopyValueAtIndex(emails, i).takeRetainedValue() as String println("email=\(email)") } } } 

这是一个老问题,但另一个答案可能仍然是有用的:我在这里快速地解决了地址簿的问题: https : //github.com/SocialbitGmbH/SwiftAddressBook

我应该提一下,ABAddressBook有很多包装器,可以帮助你避免像完全问你的问题。 因此我认为这个链接是对这个问题的“回答”(尽pipe它没有回答如何修复你的代码)

为了join到这里的信息,这是我的解决scheme拼凑在一起从各个地方(是否有一个很好的苹果网站,真正描述这个,我发现的文档基本上提供几乎没有什么比args /成员的名字):

  let addrBook = ABAddressBookCreateWithOptions(nil,nil).takeRetainedValue() let contacts = ABAddressBookCopyArrayOfAllPeople(addrBook).takeRetainedValue() as NSArray as [ABRecordRef] for contact in contacts { let fname = ABRecordCopyValue(contact, kABPersonFirstNameProperty).takeRetainedValue() as! NSString let lname = ABRecordCopyValue(contact, kABPersonLastNameProperty).takeRetainedValue() as! NSString let name = String(fname) + " " + String(lname) var image:UIImage? = nil if ABPersonHasImageData(contact) { image = UIImage(data: ABPersonCopyImageDataWithFormat(contact, kABPersonImageFormatThumbnail).takeRetainedValue() as NSData) } if let emailRefs: ABMultiValueRef = ABRecordCopyValue(contact, kABPersonEmailProperty).takeRetainedValue() { let nEmailsForContact = ABMultiValueGetCount(emailRefs) if nEmailsForContact > 0 { if let emailArray: NSArray = ABMultiValueCopyArrayOfAllValues(emailRefs).takeRetainedValue() as NSArray { for emailW in emailArray { let email = String(emailW) if email.containsString("@") { let c: EmailContact = EmailContact(n: name, e: email, a: false, i: image) mEmailContacts.append(c) } } } } } } 

奇怪的是,你必须检查,以确保有一个图像,如果你想访问它; 并且在尝试提取联系人之前,必须检查是否至less有一个联系人的电子邮件(为什么它不只是返回一个空列表呢?)。

'EmailContact'类是我用来捕获结果的东西,它没有显示,但代码片断显示了如何提取当前版本的swift / ios的信息。

此外,我注意到,网站设置似乎出现在EmailArray的联系人以及实际的电子邮件。 现在我只是检查一个“@”符号来确定它是否真的是一个电子邮件,但有没有一个更好的或“官方”的方式来做到这一点?

最后,希望这是内存泄漏安全。

哦,当然这是获得许可后,如果你不知道该怎么做,那么这个网站是好的: http : //www.raywenderlich.com/63885/address-book-tutorial-in-ios

这里提供的其他答案是有用的,并指导了这个答案,但有错误和/或没有更新Swift 3.下面的类提供了一些简化和安全性的改进。

用法只是调用AddressBookService.getContactNames

仍然有很好的理由仍然需要使用ABAddressBook框架,因为CNContact不提供一些关键数据,包括创build和修改date。 不赞成使用的方法警告在处理代码时有些分散注意力,所以这段代码禁止了从iOS 9开始不推荐使用ABAddressBook方法的警告,而只是在下面的任何地方调用这个类时提供一个警告。

 // // AddressBookService.swift // import AddressBook @available(iOS, deprecated: 9.0) class AddressBookService: NSObject { class func getContactNames() { let authorizationStatus = ABAddressBookGetAuthorizationStatus() switch authorizationStatus { case .authorized: retrieveContactNames() break case .notDetermined: print("Requesting Address Book access...") let addressBook = AddressBookService.addressBook ABAddressBookRequestAccessWithCompletion(addressBook, {success, error in if success { print("Address book access granted") retrieveContactNames() } else { print("Unable to obtain Address Book access.") } }) break case .restricted, .denied: print("Address book access denied") break } } private class func retrieveContactNames() { let addressBook = ABAddressBookCreate().takeRetainedValue() let contactList = ABAddressBookCopyArrayOfAllPeople(addressBook).takeRetainedValue() as NSArray as [ABRecord] for (index, record) in contactList.enumerated() { if let contactName = ABRecordCopyCompositeName(record)?.takeRetainedValue() as String? { print("Contact \(index): \(contactName))") } } } } 

不是最好的解决scheme,但直到我find这个工作

 let records = ABAddressBookCopyArrayOfAllPeople(self.addressBook).takeRetainedValue() as NSArray as [ABRecord] sleep(2) println(records.count);