web-dev-qa-db-fra.com

Accès au carnet d'adresses iOS avec Swift: nombre de tableaux nul

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)

45
user1031648

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

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)")
    }
}
15
Wes Campaigne

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
}
8
Gergely Orosz

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.

4
user1031648

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)")
        }
    }
}
3
Alexander Volkov

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)

2
TAKeanice

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))")
            }
        }
    }
}
1
Duncan Babbage

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

1
user1857742

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);
0
Manuel Manzanera