使用 Swift 访问 iOS 地址簿:数组计数为零

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24752627/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 01:23:23  来源:igfitidea点击:

Accessing iOS Address Book with Swift: array count of zero

iosswiftabaddressbook

提问by user1031648

I am trying to write a simple method to ask a user for access to their address book and then print out the name of each person in the address book. I've seen a number of tutorials explaining how to do this in objective-C, but am having a hard time converting them to swift.

我正在尝试编写一个简单的方法来要求用户访问他们的地址簿,然后打印出地址簿中每个人的姓名。我看过很多教程解释了如何在 Objective-C 中做到这一点,但是我很难将它们转换为 swift。

Here's what I've done so far. The below block runs in my viewDidLoad() method and checks to see whether the user has authorized access to the address book or not, if they have not authorized access yet, the first if-statement will ask for access. This section works as expected.

这是我到目前为止所做的。下面的块在我的 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()
        }

Once I know the user has granted access, I run the getContactNames() method which is below. After much back and forth, I was finally able to get this to compile by adding the takeRetainedValue() method in order to convert the array returned by ABAddressBookCopyArrayOfAllPeople from an unmanaged array to a managed array, this then allows me to convert the CFArrayRef to an NSArray.

一旦我知道用户已授予访问权限,我就会运行下面的 getContactNames() 方法。经过多次来回,我终于能够通过添加 takeRetainedValue() 方法来编译它,以便将 ABAddressBookCopyArrayOfAllPeople 返回的数组从非托管数组转换为托管数组,然后这允许我将 CFArrayRef 转换为数组。

The issue I'm running into is that the contactList array ends up having a count of 0 and the for loop therefore gets skipped. In my simulator, the address book has 6 or 7 records, so I would expect the array to be of that length. Any ideas?

我遇到的问题是 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)")
        }
    }

One additional point - if I use the ABAddressBookGetPersonCount method, it returns -1.

还有一点 - 如果我使用 ABAddressBookGetPersonCount 方法,它会返回 -1。

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

Based on this link ABAddressBookGetPersonCount returns -1 in iOS, it seems that this function returning -1 could be related to permission not being granted, but I definitely have asked for permission in the code above (and granted it when I run the app in the simulator)

基于此链接ABAddressBookGetPersonCount 在 iOS 中返回 -1,似乎返回 -1 的此函数可能与未授予权限有关,但我确实在上面的代码中请求了权限(并在我在模拟器)

回答by matt

This is now all much simpler. The chief thing to watch out for is that if you create an ABAddressBook without authorization, you get an evil address book - it isn't nil but it isn't good for anything either. Here's how I currently recommend that you set up authorization status and request authorization if necessary:

现在这一切都简单多了。要注意的主要事情是,如果您在未经授权的情况下创建 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
    }
}

And here's how to cycle through all persons and print out their names:

以下是如何循环遍历所有人并打印出他们的名字:

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

回答by Wes Campaigne

There seems to be a bug either with the compiler or the framework where ABAddressBookRefis declared a typealias of AnyObject, but it needs to be NSObjectin order to unwrap it from the Unmanaged<ABAddressBookRef>!returned by ABAddressBookCreateWithOptions. A workaround is to convert it to and from an opaque C pointer. The following code works, but it should probably be doing a lot more error checking (and there is also probably a better way of working around this issue):

编译器或框架似乎存在一个错误,其中ABAddressBookRef声明了 的类型别名AnyObject,但需要这样做NSObject才能从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)")
    }
}

回答by Gergely Orosz

For those looking for the complete working solution, here is how to print out only the contact names, modifying the above code. Invoke getAddressBookNames()to access the address book, e.g. in the viewDidLoad()method.

对于那些寻找完整的工作解决方案的人,这里是如何打印出联系人姓名,修改上面的代码。调用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
}

And here is the complete code to access the contact names and emails- this is done using the helper methods defined in some of the other answers.

这是访问联系人姓名和电子邮件的完整代码- 这是使用其他一些答案中定义的辅助方法完成的。

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
}

回答by user1031648

If anyone is also trying to get the email addresses of the contacts, I found that I needed to create two additional methods similar to the new one Wes showed.

如果有人也试图获取联系人的电子邮件地址,我发现我需要创建两种类似于 Wes 展示的新方法的附加方法。

Here's the updated version of the getContactNames() function:

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

And here are the two additional functions I created:

这是我创建的两个附加函数:

  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
}

Thanks again to Wes for his help on my initial question which helped me figure the above out.

再次感谢 Wes 对我最初问题的帮助,这帮助我弄清楚了上述问题。

回答by Alexander Volkov

If you need emailadditionally to matt's answer:

如果您还需要电子邮件到马特的回答:

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

回答by TAKeanice

This is an old question, but another answer may still be useful: I made an approach to solve the problems with address book in swift here: https://github.com/SocialbitGmbH/SwiftAddressBook

这是一个老问题,但另一个答案可能仍然有用:我在这里制定了一种解决地址簿问题的方法:https: //github.com/SocialbitGmbH/SwiftAddressBook

I should mention that there are many wrappers for ABAddressBook out there which can help you avoid issues like the one you asked about entirely. Thus I consider the link an "answer" to the problem (though it is not answering how to fix your code)

我应该提到的是,有许多 ABAddressBook 包装器可以帮助您避免像您完全询问的问题那样的问题。因此,我认为该链接是问题的“答案”(尽管它没有回答如何修复您的代码)

回答by user1857742

To add to the info here, this is my solution pieced together from various places (is there a good Apple site that really describes this, the docs I've found basically provide almost nothing more than what the names of args/members are):

要添加到此处的信息,这是我从各个地方拼凑而成的解决方案(是否有一个很好的 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)
                            }
                        }
                    }
                }
            }
        }

Oddly, you have to check to make sure there is an image if you want to access it; and you have to check that there is at least one email for a contact before trying to extract them (why doesn't it just return an empty list instead???).

奇怪的是,如果你想访问它,你必须检查以确保有一个图像;并且您必须在尝试提取联系人之前检查他们是否至少有一封电子邮件(为什么它不只返回一个空列表????)。

The 'EmailContact" class is something that I made to capture the results, its not shown but the code snippet does show how to extract the info for the current version of swift/ios.

“EmailContact”类是我用来捕获结果的东西,它没有显示,但代码片段确实显示了如何提取当前版本的 swift/ios 的信息。

Also, I note that web site settings seem to come up in the EmailArray for contacts as well as actual emails. For now I just check for an "@" sign to determine if its really an email, but is there a better or 'official' way to do that?

另外,我注意到网站设置似乎出现在用于联系人和实际电子邮件的 EmailArray 中。现在我只是检查“@”符号以确定它是否真的是一封电子邮件,但有没有更好的或“官方”的方法来做到这一点?

Finally, hopefully this is memory leak safe.

最后,希望这是内存泄漏安全的。

Oh, of course this is done after getting permission, if you are not sure how to do that then this site is good: http://www.raywenderlich.com/63885/address-book-tutorial-in-ios

哦,当然这是在获得许可后完成的,如果您不确定该怎么做,那么这个网站很好:http: //www.raywenderlich.com/63885/address-book-tutorial-in-ios

回答by Duncan Babbage

Other answers provided here were useful, and guided this answer, but had errors and/or were not updated for Swift 3. The following class provides a number of simplifications and safety improvements.

此处提供的其他答案很有用,并指导了此答案,但有错误和/或未针对 Swift 3 进行更新。以下课程提供了许多简化和安全改进。

Usage is simply to call AddressBookService.getContactNames

用法只是调用 AddressBookService.getContactNames

There are good reasons to still need to use the ABAddressBookframework, as CNContactdoes not provide some key data, including creation and modification dates for instance. The deprecated method warnings are somewhat distracting when working with the code, so this code suppresses the warnings that the ABAddressBook methods were deprecated from iOS 9 onwards, instead providing just a single warning to this effect wherever you call the class below.

仍然需要使用该ABAddressBook框架是有充分理由的,因为CNContact它没有提供一些关键数据,例如创建和修改日期。不推荐使用的方法警告在处理代码时会有些分散注意力,因此这段代码抑制了从 iOS 9 开始不推荐使用 ABAddressBook 方法的警告,而在您调用下面的类的任何地方只提供一个警告。

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

回答by Manuel Manzanera

Not the best solution but until I find this work

不是最好的解决方案,但直到我找到这项工作

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