J'essaie d'écrire une méthode simple pour demander à un utilisateur d'accéder à son carnet d'adresses, puis d'imprimer le nom de chaque personne dans le carnet d'adresses. J'ai vu un certain nombre de tutoriels expliquant comment faire cela dans l'objectif-C, mais j'ai du mal à les convertir en Swift.
Voici ce que j'ai fait jusqu'à présent. Le bloc ci-dessous s'exécute dans ma méthode viewDidLoad () et vérifie si l'utilisateur a autorisé l'accès au carnet d'adresses ou non, s'il n'a pas encore autorisé l'accès, la première instruction if demandera l'accès. Cette section fonctionne comme prévu.
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()
}
Une fois que je sais que l'utilisateur a accordé l'accès, j'exécute la méthode getContactNames () qui est ci-dessous. Après beaucoup de va-et-vient, j'ai finalement pu obtenir cela à compiler en ajoutant la méthode takeRetainedValue () afin de convertir le tableau renvoyé par ABAddressBookCopyArrayOfAllPeople d'un tableau non géré en un tableau géré, cela me permet ensuite de convertir le CFArrayRef en un NSArray.
Le problème que je rencontre est que le tableau contactList finit par avoir un compte de 0 et que la boucle for est donc ignorée. Dans mon simulateur, le carnet d'adresses a 6 ou 7 enregistrements, donc je m'attendrais à ce que le tableau soit de cette longueur. Des idées?
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 point supplémentaire - si j'utilise la méthode ABAddressBookGetPersonCount, il renvoie -1.
var count: CFIndex = ABAddressBookGetPersonCount(addressBook);
println("records in the array \(count)") // returns -1
Sur la base de ce lien ABAddressBookGetPersonCount renvoie -1 dans iOS , il semble que cette fonction renvoyant -1 pourrait être liée à la permission non accordée, mais j'ai certainement demandé la permission dans le code ci-dessus (et je l'ai accordée) quand je lance l'application dans le simulateur)
C'est maintenant beaucoup plus simple. La principale chose à surveiller est que si vous créez un ABAddressBook sans autorisation, vous obtenez un mauvais carnet d'adresses - ce n'est pas nul mais ce n'est bon pour rien non plus. Voici comment je recommande actuellement de configurer le statut d'autorisation et de demander l'autorisation si nécessaire:
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
}
}
Et voici comment parcourir toutes les personnes et imprimer leurs noms:
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())
}
}
Il semble y avoir un bogue avec le compilateur ou le framework où ABAddressBookRef
est déclaré une typealias de AnyObject
, mais il doit être NSObject
pour le déballer de la Unmanaged<ABAddressBookRef>!
retourné par ABAddressBookCreateWithOptions
. Une solution de contournement consiste à le convertir vers et depuis un pointeur C opaque. Le code suivant fonctionne, mais il devrait probablement faire beaucoup plus de vérification d'erreurs (et il existe également probablement une meilleure façon de contourner ce problème):
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)")
}
}
Pour ceux qui recherchent la solution de travail complète, voici comment imprimer niquement les noms des contacts, en modifiant le code ci-dessus. Appelez getAddressBookNames()
pour accéder au carnet d'adresses, par ex. dans la méthode 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
}
Et voici le code complet pour accéder aux noms des contacts et e-mails - cela se fait en utilisant les méthodes d'assistance définies dans certaines des autres réponses.
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
}
Si quelqu'un essaie également d'obtenir les adresses e-mail des contacts, j'ai trouvé que je devais créer deux méthodes supplémentaires similaires à la nouvelle que Wes avait montré.
Voici la version mise à jour de la fonction 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)")
}
}
}
Et voici les deux fonctions supplémentaires que j'ai créées:
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
}
Merci encore à Wes pour son aide sur ma question initiale qui m'a aidé à comprendre ce qui précède.
Si vous avez besoin de email en plus de la réponse 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)")
}
}
}
C'est une vieille question, mais une autre réponse peut toujours être utile: j'ai fait une approche pour résoudre les problèmes avec le carnet d'adresses dans Swift ici: https://github.com/ SocialbitGmbH/SwiftAddressBook
Je dois mentionner qu'il existe de nombreux wrappers pour ABAddressBook qui peuvent vous aider à éviter des problèmes tels que celui que vous avez demandé. Je considère donc le lien comme une "réponse" au problème (bien qu'il ne réponde pas à la façon de corriger votre code)
Les autres réponses fournies ici ont été utiles et ont guidé cette réponse, mais comportaient des erreurs et/ou n'ont pas été mises à jour pour Swift 3. La classe suivante fournit un certain nombre de simplifications et d'améliorations de sécurité.
L'utilisation consiste simplement à appeler AddressBookService.getContactNames
Il y a de bonnes raisons de devoir encore utiliser le framework ABAddressBook
, car CNContact
ne fournit pas certaines données clés, y compris les dates de création et de modification par exemple. Les avertissements de méthode obsolètes sont quelque peu gênants lorsque vous travaillez avec le code, donc ce code supprime les avertissements selon lesquels les méthodes ABAddressBook ont été obsolètes à partir d'iOS 9, fournissant plutôt un seul avertissement à cet effet partout où vous appelez la classe ci-dessous.
//
// 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))")
}
}
}
}
Pour ajouter aux informations ici, voici ma solution reconstituée à partir de divers endroits (existe-t-il un bon site Apple qui décrit vraiment cela, les documents que j'ai trouvés ne fournissent pratiquement rien de plus que ce que les noms des arguments/membres sont):
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)
}
}
}
}
}
}
Curieusement, vous devez vérifier qu'il y a bien une image si vous voulez y accéder; et vous devez vérifier qu'il y a au moins un e-mail pour un contact avant d'essayer de les extraire (pourquoi ne renvoie-t-il pas simplement une liste vide à la place ???).
La classe 'EmailContact "est quelque chose que j'ai fait pour capturer les résultats, ce n'est pas affiché, mais l'extrait de code montre comment extraire les informations pour la version actuelle de Swift/ios.
En outre, je note que les paramètres du site Web semblent apparaître dans l'EmailArray pour les contacts ainsi que les e-mails réels. Pour l'instant, je vérifie simplement un signe "@" pour déterminer s'il s'agit vraiment d'un e-mail, mais existe-t-il une meilleure façon ou "officielle" de le faire?
Enfin, j'espère que cela est sûr contre les fuites de mémoire.
Oh, bien sûr, cela se fait après avoir obtenu la permission, si vous ne savez pas comment le faire, alors ce site est bon: http://www.raywenderlich.com/63885/address-book-tutorial-in- ios
Pas la meilleure solution mais jusqu'à ce que je trouve ce travail
let records = ABAddressBookCopyArrayOfAllPeople(self.addressBook).takeRetainedValue()
as NSArray as [ABRecord]
sleep(2)
println(records.count);