web-dev-qa-db-ja.com

Swiftを使用したiOSアドレス帳へのアクセス:配列カウント0

アドレス帳へのアクセスをユーザーに要求し、アドレス帳の各人の名前を印刷する簡単な方法を作成しようとしています。 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()メソッドを実行します。何度もやり取りした後、私は最終的にABAddressBookCopyArrayOfAllPeopleによって返された配列をアンマネージ配列からマネージ配列に変換するためにtakeRetainedValue()メソッドを追加することでコンパイルできるようになりました。これにより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)")
        }
    }

もう1つのポイント-ABAddressBookGetPersonCountメソッドを使用すると、-1が返されます。

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

このリンクに基づいて ABAddressBookGetPersonCountはiOSで-1を返します 、-1を返すこの関数は許可されていない許可に関連しているようですが、私は間違いなく上記のコードで許可を求めました(そして許可しました)シミュレータでアプリを実行すると)

45
user1031648

これは今ではすべてずっと簡単です。注意すべき主なことは、許可なしに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())
    }
}
27
matt

ABAddressBookRefAnyObjectのタイプエイリアスとして宣言されているコンパイラまたはフレームワークにバグがあるようですが、それを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)")
    }
}
15
Wes Campaigne

完全な実用的なソリューションをお探しの方は、上記のコードを変更して連絡先名のみを印刷する方法をご覧ください。 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
}
8
Gergely Orosz

誰かが連絡先の電子メールアドレスを取得しようとしている場合、Wesが示した新しいメソッドに似た2つの追加メソッドを作成する必要があることがわかりました。

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

そして、ここに私が作成した2つの追加関数があります。

  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
user1031648

マットの答えに加えて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)")
        }
    }
}
3

これは古い質問ですが、別の答えがまだ役に立つかもしれません:Swift here: https://github.com/ SocialbitGmbH/SwiftAddressBook

ABAddressBookには、完全に尋ねたような問題を回避するのに役立つ多くのラッパーがあることに言及する必要があります。したがって、リンクは問題への「答え」であると考えます(ただし、コードの修正方法は答えていません)

2
TAKeanice

ここで提供される他の回答は有用であり、この回答を導きましたが、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))")
            }
        }
    }
}
1
Duncan Babbage

ここに情報を追加するために、これはさまざまな場所から集められた私のソリューションです(これを実際に説明する良いAppleサイトがあります、私が見つけたドキュメントは基本的にほとんど何も提供しません引数/メンバーの名前は次のとおりです):

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

奇妙なことに、アクセスしたい場合は画像があることを確認する必要があります。また、連絡先を抽出する前に、連絡先に少なくとも1つの電子メールがあることを確認する必要があります(代わりに空のリストを返すのはなぜですか?)。

「EmailContact」クラスは結果をキャプチャするために作成したもので、表示されていませんが、コードスニペットは現在のバージョンのSwift/iosの情報を抽出する方法を示しています。

また、Webサイトの設定は、実際のメールだけでなく、連絡先のEmailArrayにも表示されるようです。今のところ、「@」記号をチェックして、そのメールが本当にメールかどうかを判断していますが、それを行うためのより良いまたは「公式の」方法はありますか?

最後に、うまくいけば、これはメモリリークセーフです。

ああ、もちろんこれは許可を得た後に行われます。その方法がわからない場合、このサイトは良いです: http://www.raywenderlich.com/63885/address-book-tutorial-in- ios

1
user1857742

最良の解決策ではありませんが、この作品が見つかるまで

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