[iOS][Swift] Contacts cho iOS8 và iOS9

Mở đầu

Nhân dịp dự án có 1 phần làm về Contacts, lại bí ý tưởng viết bài Viblo nên tôi mạo muội xin đăng lại vấn đề cũ rích là Contact của iOS 8 và iOS 9 khác gì nhau. Lý thuyết thì ngắn thôi (đây cũng có này) nên tôi sẽ làm 1 demo nhỏ để cho nó thêm phần dài dòng. Let's start

Demo

Bắt đầu từ bài viết này, theo yêu cầu của 1 số bạn trẻ giấu tên, tôi sẽ làm tiếp phần demo để nó có thể support cho cả IOS 8 😄

May quá, sau 1 hồi hì hục chuyển nó từ Swift 2.2 sang Swift 3 thì nó đã chạy thành công. Giờ việc đơn giản chỉ là chuyển Deployment Target sang 8.0 là xong. Nhưng ...

Sure, CNContact chỉ support từ iOS 9 mà, dù sao chúng ta cũng đã đạt được 1/2 chặng đường rồi. Giờ chỉ cần viết nốt 1 Contact Service convert nốt sang iOS 8 là xong

keep-calm-and-support-ios-8.jpg

Support IOS 8

Ngược dòng lịch sử về iOS8, khi mà CNContact chưa được chào đời thì chúng ta cũng đã có 1 công cụ hỗ trợ rất đầy đủ việc đó là AddressBook.

Có lẽ lý do duy nhất khiến nó bị khai tử là vì "Apple thích thì Apple deprecated thôi" =)) hoặc nếu có 1 lý do nào khác thì đó chính là điều mà chúng ta nhận ra khi làm tiếp phần dưới đây

Vì CNContact chỉ có trên iOS 9 trở đi, chúng ta cần tạo ra 1 contact object trung gian để support cho cả ABRecordCNContact, và trong Swift cũng có 1 người đầy tớ trung thành chuyên xử lý việc này là #available@available

class ContactObject: NSObject {
    var identifier: String!
    var fullName: String!
    var phoneNumbers: [String]!
    var phoneNumbersString: String!
    var avatar: UIImage?

    @@available(iOS 9.0, *) (vì cái markdown nó hơi ngu nên tôi đã để 2 dấu @@, hãy xóa đi 1 ký tự @ trong code nhé)
    convenience init(contact: CNContact) {
        self.init()
        self.identifier = contact.identifier
        self.fullName = contact.fullName
        self.phoneNumbers = [String]()
        for phoneNumberObject: CNLabeledValue in contact.phoneNumbers {
            let phoneNumber = (phoneNumberObject.value ).stringValue
            self.phoneNumbers.append(phoneNumber)
        }
        self.phoneNumbersString = phoneNumberString(phoneNumbers: self.phoneNumbers)
        self.avatar = contact.getAvatarImage()
    }

    convenience init(record: ABRecord) {
        self.init()
        self.identifier = String(ABRecordGetRecordID(record))
        if let nameRef = ABRecordCopyCompositeName(record)?.takeRetainedValue() {
            self.fullName = nameRef as String
        }
        self.phoneNumbers = [String]()
        if let phonesMultivalueRef = ABRecordCopyValue(record, kABPersonPhoneProperty)?.takeRetainedValue() {
            let phonesRef = ABMultiValueCopyArrayOfAllValues(phonesMultivalueRef)?.takeRetainedValue()
            self.phoneNumbers = phonesRef as! [String]
        }
        self.phoneNumbersString = phoneNumberString(phoneNumbers: self.phoneNumbers)
        if ABPersonHasImageData(record) {
            let imageData = ABPersonCopyImageData(record).takeRetainedValue() as Data
            self.avatar = UIImage(data: imageData)
        }
    }

    private func phoneNumberString(phoneNumbers: [String]) -> String {
        if phoneNumbers.count > 0 {
            var suffixPhoneNumberString = ""
            if phoneNumbers.count > 1 {
                suffixPhoneNumberString = " and \(phoneNumbers.count - 1) more"
            }
            let firstNumberString = phoneNumbers[0]
            return firstNumberString + suffixPhoneNumberString
        }
        return ""
    }
}

Các bước làm việc với Contact như trước đây chúng ta cũng đều chuyển hết về ABAddressBook

Xin quyền


    typealias GetContactsCompleteHandle = (_ contacts: [ContactObject], _ error: NSError?) -> Void

func iOS8RequestContacts(_ completion: @escaping GetContactsCompleteHandle) {
        switch ABAddressBookGetAuthorizationStatus() {
        case .denied, .restricted:
            let settingURL = URL(string: UIApplicationOpenSettingsURLString)!
            if UIApplication.shared.canOpenURL(settingURL) {
                showAlert(title: kCantAccessContactAlertTitle, message: kCantAccessContactAlertMessage,
                          okButtonTitle: "Settings", okButtonAction: {
                            UIApplication.shared.openURL(settingURL)
                    }, cancelButtonTitle: "OK", cancelButtonAction: {
                })
            } else {
                showAlert(title: kCantAccessContactAlertTitle,
                          message: kCantAccessContactAlertMessage,
                          okButtonTitle: "OK", okButtonAction:nil,
                          cancelButtonTitle: nil, cancelButtonAction: nil)
            }
        case .notDetermined:
            ABAddressBookRequestAccessWithCompletion(self.addressBook, { (granted, error) in
                if !granted {
//                    completion([], error as NSError?)
                } else {
                    self.iOS8GetContacts(completion)
                }
            })
        case .authorized:
            self.iOS8GetContacts(completion)
        }
    }

và Get Contacts

    func iOS8GetContacts(_ completion: GetContactsCompleteHandle) {
        let abPeople = ABAddressBookCopyArrayOfAllPeople(self.addressBook).takeRetainedValue() as Array
        var contactList = [ContactObject]()
        for abPerson in abPeople {
            let contactObject = ContactObject(record: abPerson)
            contactList.append(contactObject)
        }
        completion(contactList, nil)
    }

Tạm kết

Rõ ràng việc support cho iOS 8 làm chúng ta mất khá nhiều thời gian, chưa kể đến việc bạn phải quay trở lại cách code của Objective-C và C++ ngày xưa rất khó chịu. Đó có lẽ cũng chính là động lực để chúng ta chuyển dần sang Contacts framework thay thế. Bạn có thể chỉ sử dụng Addressbook không thôi cũng được. Nhưng thời điểm mà chúng ta bỏ support iOS 8 cũng không còn xa nữa, hãy sẵn sàng thôi 😄