Подтвердить что ты не робот

Доступ к адресной книге iOS с помощью Swift: количество массивов в ноль

Я пытаюсь написать простой метод, чтобы попросить пользователя получить доступ к их адресной книге, а затем распечатать имя каждого человека в адресной книге. Я видел несколько руководств, объясняющих, как это сделать в objective-C, но мне трудно преобразовать их в swift.

Вот что я сделал до сих пор. Следующий блок работает в моем методе 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 из неуправляемого массива в управляемый массив, тогда это позволяет мне преобразовать CFArrayRef в NSArray.

Проблема, с которой я сталкиваюсь, заключается в том, что массив contactList заканчивается тем, что имеет число 0, и цикл for поэтому пропускается. В моем симуляторе адресная книга имеет 6 или 7 записей, поэтому я ожидаю, что массив будет такой длины. Есть идеи?

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 возвращает -1 в iOS, похоже, что эта функция, возвращающая -1, может быть связана с предоставлением разрешения, но я определенно спросил для разрешения в приведенном выше коде (и предоставил его при запуске приложения в симуляторе)

4b9b3361

Ответ 1

Теперь это намного проще. Главное, на что нужно обратить внимание, это то, что если вы создадите книгу ABAddressBook без авторизации, вы получите злую адресную книгу - это не ноль, но это ни для чего не подходит. Здесь, как я сейчас рекомендую вам установить статус авторизации и авторизацию запроса, если необходимо:

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

Ответ 2

Кажется, что есть ошибка либо с компилятором, либо с каркасом, где ABAddressBookRef объявляется typealias AnyObject, но для его разворачивания из 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)")
    }
}

Ответ 3

Для тех, кто ищет полное рабочее решение, вот как распечатать только имена контактов, изменив приведенный выше код. Вызовите 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
}

Ответ 4

Если кто-то пытается получить адреса электронной почты контактов, я обнаружил, что мне нужно создать два дополнительных метода, похожих на новый, показанный 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)")
            }
        }
    }

И вот две дополнительные функции, которые я создал:

  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
}

Еще раз спасибо Уэсу за помощь в моем первоначальном вопросе, который помог мне разобраться в этом.

Ответ 5

Если вам нужен email дополнительно к матовому ответу:

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

Ответ 6

Это старый вопрос, но еще может быть полезен еще один ответ: я применил подход к решению проблем с адресной книгой: https://github.com/SocialbitGmbH/SwiftAddressBook

Я должен упомянуть, что есть много оберток для ABAddressBook, которые могут помочь вам избежать проблем, подобных тому, о котором вы просили полностью. Таким образом, я считаю ссылку "ответом" на проблему (хотя она не отвечает, как исправить ваш код)

Ответ 7

Чтобы добавить к информации здесь, это мое решение, собранное из разных мест (есть хороший сайт Apple, который действительно описывает это, документы, которые я нашел, в основном обеспечивают почти ничего, кроме того, что имена args/members есть):

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

Как ни странно, вам нужно проверить, есть ли изображение, если вы хотите получить к нему доступ; и вы должны проверить, что есть хотя бы одно электронное письмо для контакта, прежде чем пытаться извлечь их (почему он просто не возвращает пустой список вместо?).

Класс "EmailContact" - это то, что я сделал для захвата результатов, но не показано, но фрагмент кода показывает, как извлечь информацию для текущей версии swift/ios.

Кроме того, я отмечаю, что настройки веб-сайта, похоже, появляются в EmailArray для контактов, а также фактических писем. На данный момент я просто проверяю знак "@", чтобы определить, действительно ли это письмо, но есть ли лучший или "официальный" способ сделать это?

Наконец, надеюсь, это утечка памяти.

О, конечно, это делается после получения разрешения, если вы не знаете, как это сделать, тогда этот сайт хорош: http://www.raywenderlich.com/63885/address-book-tutorial-in-ios

Ответ 8

Другие предоставленные здесь ответы были полезны и руководствовались этим ответом, но имели ошибки и/или не были обновлены для Swift 3. Следующий класс предоставляет ряд упрощений и улучшений безопасности.

Использование просто для вызова AddressBookService.getContactNames

Есть веские причины, по-прежнему необходимо использовать структуру ABAddressBook, поскольку CNContact не предоставляет некоторые ключевые данные, включая даты создания и изменения, например. Предупреждения об устаревших методах несколько отвлекают при работе с кодом, поэтому этот код подавляет предупреждения о том, что методы ABAddressBook были устаревшими с iOS 9 и далее, вместо этого предоставляя только одно предупреждение для этого эффекта, когда вы вызываете класс ниже.

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

Ответ 9

Не лучшее решение, но пока я не найду эту работу

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