open ios swift abaddressbook

open - Acceso a la libreta de direcciones de iOS con Swift: matriz de cero



uiimagepickercontroller swift 4 (9)

Estoy tratando de escribir un método simple para pedirle a un usuario el acceso a su libreta de direcciones y luego imprimir el nombre de cada persona en la libreta de direcciones. He visto una serie de tutoriales que explican cómo hacer esto en el objetivo C, pero me resulta difícil convertirlos en rápidos.

Esto es lo que he hecho hasta ahora. El bloque a continuación se ejecuta en mi método viewDidLoad () y verifica si el usuario tiene acceso autorizado a la libreta de direcciones o, si aún no ha autorizado el acceso, la primera instrucción if pedirá acceso. Esta sección funciona como se espera.

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() }

Una vez que sé que el usuario ha otorgado acceso, ejecuto el método getContactNames () que se encuentra a continuación. Después de mucho ir y venir, finalmente pude compilar esto agregando el método takeRetainedValue () para convertir la matriz devuelta por ABAddressBookCopyArrayOfAllPeople de una matriz no administrada a una matriz administrada, esto me permite convertir el CFArrayRef en una NSArray.

El problema con el que me estoy topando es que la matriz contactList termina teniendo una cuenta de 0 y, por lo tanto, el bucle for se omite. En mi simulador, la libreta de direcciones tiene 6 o 7 registros, por lo que espero que la matriz sea de esa longitud. ¿Algunas ideas?

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)") } }

Un punto adicional: si uso el método ABAddressBookGetPersonCount, devuelve -1.

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

Según este enlace, ABAddressBookGetPersonCount devuelve -1 en iOS , parece que esta función que devuelve -1 podría estar relacionada con el permiso no otorgado, pero definitivamente he pedido permiso en el código anterior (y se me otorgó cuando ejecuto la aplicación en el simulador)


Esta es una pregunta antigua, pero otra respuesta puede ser útil: aquí hice un enfoque para resolver los problemas con la libreta de direcciones: https://github.com/SocialbitGmbH/SwiftAddressBook

Debo mencionar que hay muchos envoltorios para ABAddressBook por ahí que pueden ayudarlo a evitar problemas como el que usted solicitó por completo. Por lo tanto, considero que el enlace es una "respuesta" al problema (aunque no está respondiendo cómo arreglar su código)


Esto es ahora mucho más simple. Lo más importante a tener en cuenta es que si crea una ABAddressBook sin autorización, obtiene una mala libreta de direcciones: no es nula, pero tampoco sirve para nada. Así es como actualmente recomiendo que configure el estado de autorización y solicite autorización si es necesario:

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 } }

Y aquí está la forma de recorrer todas las personas e imprimir sus nombres:

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()) } }


No es la mejor solución pero hasta que encuentre este trabajo.

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


Otras respuestas proporcionadas aquí fueron útiles y guiaron esta respuesta, pero tuvieron errores y / o no se actualizaron para Swift 3. La siguiente clase proporciona una serie de simplificaciones y mejoras de seguridad.

El uso es simplemente llamar a AddressBookService.getContactNames

Hay buenas razones para seguir necesitando usar el marco ABAddressBook , ya que CNContact no proporciona algunos datos clave, como las fechas de creación y modificación, por ejemplo. Las advertencias de métodos obsoletos distraen un poco al trabajar con el código, por lo que este código suprime las advertencias de que los métodos ABAddressBook fueron obsoletos desde iOS 9 en adelante, en lugar de eso, solo brindan una advertencia a este efecto dondequiera que llame a la clase a continuación.

// // 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))") } } } }


Para agregar a la información aquí, esta es mi solución combinada desde varios lugares (¿hay un buen sitio de Apple que realmente lo describa, los documentos que encontré básicamente no proporcionan nada más que lo que son los nombres de args / miembros):

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) } } } } } }

Curiosamente, tienes que verificar para asegurarte de que hay una imagen si quieres acceder a ella; y debes verificar que haya al menos un correo electrónico para un contacto antes de intentar extraerlo (¿por qué no devuelve una lista vacía en su lugar?).

La clase ''EmailContact "es algo que hice para capturar los resultados, no se muestra, pero el fragmento de código muestra cómo extraer la información de la versión actual de swift / ios.

Además, observo que la configuración del sitio web parece aparecer en el EmailArray para contactos, así como correos electrónicos reales. Por ahora, solo busco un signo "@" para determinar si realmente es un correo electrónico, pero ¿hay una forma mejor o "oficial" de hacerlo?

Por último, espero que esto sea seguro de fuga de memoria.

Oh, claro que esto se hace después de obtener el permiso, si no está seguro de cómo hacerlo, entonces este sitio es bueno: http://www.raywenderlich.com/63885/address-book-tutorial-in-ios


Para aquellos que buscan la solución de trabajo completa, aquí está cómo imprimir solo los nombres de los contactos , modificando el código anterior. Invoque getAddressBookNames() para acceder a la libreta de direcciones, por ejemplo, en el método 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 }

Y aquí está el código completo para acceder a los nombres de contacto y correos electrónicos , esto se hace usando los métodos de ayuda definidos en algunas de las otras respuestas.

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 }


Parece que hay un error en el compilador o en el marco donde ABAddressBookRef se declara como tipografía de AnyObject , ¡pero debe ser NSObject para poder desenvolverlo desde el Unmanaged<ABAddressBookRef>! devuelto por ABAddressBookCreateWithOptions . Una solución es convertirlo desde y hacia un puntero C opaco. El siguiente código funciona, pero probablemente debería estar haciendo mucho más control de errores (y probablemente también haya una mejor manera de solucionar este problema):

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)") } }


Si alguien también está tratando de obtener las direcciones de correo electrónico de los contactos, descubrí que necesitaba crear dos métodos adicionales similares al nuevo que mostró Wes.

Aquí está la versión actualizada de la función 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)") } } }

Y aquí están las dos funciones adicionales que he creado:

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 }

Gracias de nuevo a Wes por su ayuda en mi pregunta inicial que me ayudó a entender lo anterior.


Si necesita un correo electrónico adicional a la respuesta de Matt:

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)") } } }