[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ừ ...
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 :D
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
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ả ABRecord và CNContact, và trong Swift cũng có 1 người đầy tớ trung thành chuyên xử lý việc này là #available và @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 :D