Accès au carnet d'adresses iOS avec Swift: nombre de tableaux égal à zéro

j'essaie d'écrire une méthode simple pour demander à un utilisateur d'accéder à son carnet d'adresses et ensuite 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 objective-C, mais j'ai du mal à les convertir en swift.

voilà ce que j'ai fait jusqu'à présent. Le bloc ci-dessous s'exécute dans la méthode my viewDidLoad() et vérifie si l'Utilisateur a autorisé l'accès au carnet d'adresses ou non, s'il n'a pas accès autorisé, le premier si-déclaration de demander 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 réussi à obtenir ceci pour compiler en ajoutant la méthode takereteainedvalue() afin de convertir le tableau retourné par ABAddressBookCopyArrayOfAllPeople d'un tableau non géré en un tableau géré, cela me permet ensuite de convertir le CFArrayRef à un NSArray.

le problème que je rencontre est que le tableau contactList finit par avoir un compte de 0 et la boucle for Est donc sautée. Dans mon simulateur, le carnet d'adresses contient 6 ou 7 enregistrements, donc je m'attends à 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, elle renvoie -1.

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

basé sur ce lien ABAddressBookGetPersonCount retourne -1 en iOS , il semble que cette fonction renvoyant -1 pourraient être liés à accorder, mais je doute avoir demandé la permission dans le code ci-dessus (et accordé quand je lance l'application dans le simulateur)

45
demandé sur Community 2014-07-15 11:46:05

9 réponses

C'est maintenant beaucoup plus simple. La principale chose à surveiller est que si vous créez un carnet D'adresses sans autorisation, vous obtenez un carnet d'adresses maléfique - il n'est pas nul mais il n'est pas bon pour rien non plus. Voici comment je vous 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
répondu matt 2014-11-03 15:03:54

il semble y avoir un bug soit avec le compilateur soit avec le framework où ABAddressBookRef est déclaré une typographie de AnyObject , mais il faut qu'il soit NSObject pour le déballer du Unmanaged<ABAddressBookRef>! retourné par ABAddressBookCreateWithOptions . Une solution consiste à le convertir en et à partir d'un pointeur c opaque. Le code suivant fonctionne, mais il devrait probablement faire beaucoup plus de vérification d'erreur (et il y a probablement une meilleure façon de travailler autour de cette question):

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
répondu Wes Campaigne 2014-07-24 04:45:40

pour ceux qui recherchent la solution complète de travail, voici comment imprimer seulement les noms de contact , en modifiant le code ci-dessus. Invoquer getAddressBookNames() pour accéder au carnet d'adresses, p.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 au noms de contact et e - mails - ceci est fait en utilisant les méthodes d'aide 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
répondu Gergely Orosz 2015-01-23 10:38:38

si quelqu'un essaie aussi d'obtenir les adresses email des contacts, j'ai trouvé que je devais créer deux méthodes supplémentaires similaires à la nouvelle Wes 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
répondu user1031648 2014-07-25 02:30:11

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
répondu Alexander Volkov 2015-03-09 06:02:04

c'est une question ancienne, mais une autre réponse peut encore ê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 ya beaucoup d'emballages pour ABAddressBook là-bas qui peut vous aider à éviter les problèmes comme celui que vous avez demandé à propos entièrement. Ainsi, je considère le lien comme une "réponse" au problème (bien qu'il ne soit pas répondre à la façon de résoudre votre code)

2
répondu TAKeanice 2016-04-13 18:10:14

pour ajouter à l'info ici, c'est ma solution reconstituée à partir de divers endroits (y a-t-il un bon site Apple qui décrit vraiment ceci, Les docs que j'ai trouvé fournissent essentiellement presque rien de plus que ce que les noms d'args/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 pour s'assurer qu'il ya une image si vous voulez y accéder; et vous devez vérifier qu'Il ya au moins un e-mail pour un contact avant d'essayer de les extraire (pourquoi est-ce pas juste retourner une liste vide à la place???).

la classe 'EmailContact' est quelque chose que j'ai fait pour capturer les résultats, elle n'est pas montrée, mais l'extrait de code montre comment extraire les informations pour la version actuelle de swift/ios.

aussi, je note que les paramètres de site web semblent apparaître dans L'EmailArray pour les contacts aussi bien que les e-mails réels. Pour l'instant, je cherche juste un signe " @ "pour déterminer si c'est vraiment un email, mais y a-t-il une meilleure façon ou "officielle" de le faire qui?

enfin, j'espère que c'est sans fuite de mémoire.

Oh, bien sûr, cela se fait après l'obtention de l'autorisation, si vous n'êtes pas sûr de savoir comment faire cela, alors ce site est bonne: http://www.raywenderlich.com/63885/address-book-tutorial-in-ios

1
répondu user1857742 2016-01-03 21:40:59

D'autres réponses fournies ici étaient 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 présente un certain nombre de simplifications et d'améliorations en matière de sécurité.

L'Usage est simplement d'appeler AddressBookService.getContactNames

il y a de bonnes raisons de continuer à utiliser le cadre ABAddressBook , car CNContact ne fournit pas certaines données clés, y compris les dates de création et de modification, par exemple. Le obsolète les avertissements de méthode sont quelque peu distrayants en travaillant avec le code, donc ce code supprime les avertissements que les méthodes ABAddressBook ont été dépréciées à partir de iOS 9, au lieu de fournir juste 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
répondu Duncan Babbage 2017-08-04 03:42:04

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
répondu Manuel Manzanera 2015-01-08 16:54:08