Вступление
С iOS 9, OS X El Capitan и watchOS 2 компания Apple представила совершенно новый фреймворк Contacts . Эта структура обеспечивает объектно-ориентированный подход к работе с контактной информацией пользователя и заменяет функциональную структуру адресной книги .
В этом руководстве мы собираемся переопределить основные функции приложения «Контакты» на iOS, чтобы вы могли узнать, как работают эти новые API.
Предпосылки
Это руководство требует, чтобы вы работали с Xcode 7+ в OS X Yosemite или более поздней версии. Вам также необходимо скачать стартовый проект с GitHub .
1. Доступ к контактам
Сначала мы собираемся использовать структуру контактов для доступа к контактам пользователя и их отображения в виде таблицы. Откройте стартовый проект и перейдите к MasterViewController.swift .
Если вы прокрутите до верхней части файла, вы увидите, что я уже добавил оператор импорта для платформы контактов. Это дает нам доступ к классам, протоколам и константам, определенным в платформе.
1
|
import Contacts
|
В классе MasterViewController
замените пустую реализацию метода getContacts()
на следующую. Убедитесь, что вы также добавили метод retrieveContactsWithStore(_:)
показанный ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
func getContacts() {
let store = CNContactStore()
if CNContactStore.authorizationStatusForEntityType(.Contacts) == .NotDetermined {
store.requestAccessForEntityType(.Contacts, completionHandler: { (authorized: Bool, error: NSError?) -> Void in
if authorized {
self.retrieveContactsWithStore(store)
}
})
} else if CNContactStore.authorizationStatusForEntityType(.Contacts) == .Authorized {
self.retrieveContactsWithStore(store)
}
}
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
func retrieveContactsWithStore(store: CNContactStore) {
do {
let groups = try store.groupsMatchingPredicate(nil)
let predicate = CNContact.predicateForContactsInGroupWithIdentifier(groups[0].identifier)
//let predicate = CNContact.predicateForContactsMatchingName(«John»)
let keysToFetch = [CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName), CNContactEmailAddressesKey]
let contacts = try store.unifiedContactsMatchingPredicate(predicate, keysToFetch: keysToFetch)
self.objects = contacts
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
} catch {
print(error)
}
}
|
Давайте пройдемся по этому коду шаг за шагом. Мы создаем экземпляр CNContactStore
и этот объект используется для непосредственного взаимодействия с системой контактов на iOS. Затем мы проверяем статус авторизации CNContactStore
. Если он не определен, мы запрашиваем авторизацию и извлекаем контакты в случае успеха. Если приложение уже авторизовано, мы немедленно получаем контакты пользователя.
В методе retrieveContactsWithStore(_:)
мы заключаем наш код в оператор do-catch
потому что два метода, которые мы используем, — это методы throwing. Вы можете прочитать больше о методах метания и обработке ошибок в Envato Tuts + .
В предложении do
мы получаем группы контактов на устройстве. Используя класс CNContact
, мы создаем объект NSPredicate
который соответствует всем контактам в первой из групп, которые мы только что получили.
Затем мы создаем массив, который содержит несколько постоянных ключей. Эти ключи напрямую связаны с информацией, к которой имеет доступ ваше приложение. Для любых клавиш, которые вы не указали (например, номер телефона), ваше приложение не сможет получить доступ к этой информации. При работе с платформой контактов это называется частичным контактом, поскольку у вас нет доступа ко всей информации о контакте.
Используя объект store
, мы получаем контакты, соответствующие предикату, который мы создали ранее, и с указанными ключами. Мы присваиваем результат массиву objects
контроллера представления для отображения в табличном представлении. Мы заставляем представление таблицы перезагрузить основной поток. Это важно, потому что выборка контактов выполняется в фоновом потоке.
Есть несколько ключевых моментов, на которые следует обратить внимание:
- Метод класса
descriptorForRequiredKeysForStyle(_:)
используемый вCNContactFormatter
— это удобный способ легко добавить все ключи, необходимые для просмотра имени контакта. - Созданный вами предикат не обязательно должен быть для группы контактов. Это может быть одной из многих вещей, включая поиск подходящего имени, например.
1
|
let predicate = CNContact.predicateForContactsMatchingName(«John»)
|
- В структуре «Контакты» ваше приложение не имеет прямого доступа к каждому контакту. Вот почему мы используем приведенный выше код для получения первой группы контактов, а не всех контактов.
- При получении контактов мы используем метод
unifiedContactsMatchingPredicate(_:keysToFetch:)
. Что означает «объединенный» в этом контексте? Если у пользователя есть несколько контактов, которые относятся к одному и тому же человеку, они могут связать их вместе в приложении «Контакты». Когда ваше приложение пытается получить доступ к контактам этого пользователя, а не возвращать несколько экземпляровCNContact
, структура контактов объединяет их вместе в один объект, чтобы ваше приложение могло правильно отображать информацию и легко ее интерпретировать.
Далее нам нужно отобразить информацию о контакте в табличном представлении. В классе MasterViewController
замените реализацию tableView(_:cellForRowAtIndexPath:)
следующим tableView(_:cellForRowAtIndexPath:)
:
01
02
03
04
05
06
07
08
09
10
11
|
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(«Cell», forIndexPath: indexPath)
let contact = self.objects[indexPath.row]
let formatter = CNContactFormatter()
cell.textLabel?.text = formatter.stringFromContact(contact)
cell.detailTextLabel?.text = contact.emailAddresses.first?.value as?
return cell
}
|
Мы используем класс CNContactFormatter
чтобы легко получить значение String
имени контакта. Мы также получаем первый адрес электронной почты для контакта (представленный объектом CNLabeledValue
) и получаем его значение. Объект CNLabeledValue
представляет любую контактную информацию, где может потребоваться фрагмент контекстной информации. Эти объекты содержат только одну метку и одно значение. В следующем примере слова, выделенные жирным шрифтом, представляют метку элемента, а слова, выделенные курсивом, представляют их значение.
- Домашний адрес
- Рабочий телефон
- Личная электронная почта
Создайте и запустите ваше приложение в симуляторе. Когда вы запускаете приложение в первый раз, вы должны увидеть следующее предупреждение.
После нажатия OK в табличном представлении должен отображаться один элемент, как показано ниже.
Теперь пришло время заполнить подробный вид, когда контакт выбран из табличного представления. При извлечении контактов мы получили только достаточно ключей для доступа к имени контакта и адресам электронной почты. Для подробного просмотра нашего приложения мы также хотим отобразить их адрес и изображение профиля. Для этого мы могли бы добавить дополнительные ключи в класс MasterViewController
при получении контактов. Однако мы собираемся снова получить тот же контакт с помощью необходимых нам ключей, используя идентификатор контакта.
Откройте DetailViewController.swift и замените реализацию configureView()
следующим.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
func configureView() {
// Update the user interface for the detail item.
if let oldContact = self.contactItem {
let store = CNContactStore()
do {
let keysToFetch = [CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName), CNContactEmailAddressesKey, CNContactPostalAddressesKey, CNContactImageDataKey, CNContactImageDataAvailableKey]
let contact = try store.unifiedContactWithIdentifier(oldContact.identifier, keysToFetch: keysToFetch)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if contact.imageDataAvailable {
if let data = contact.imageData {
self.contactImage.image = UIImage(data: data)
}
}
self.fullName.text = CNContactFormatter().stringFromContact(contact)
self.email.text = contact.emailAddresses.first?.value as?
if contact.isKeyAvailable(CNContactPostalAddressesKey) {
if let postalAddress = contact.postalAddresses.first?.value as?
self.address.text = CNPostalAddressFormatter().stringFromPostalAddress(postalAddress)
} else {
self.address.text = «No Address»
}
}
})
} catch {
print(error)
}
}
}
|
Давайте разберем реализацию. Мы получаем развернутую ссылку на элемент контакта, полученный от MasterViewController
и создаем еще CNContactStore
экземпляр CNContactStore
.
В операторе do-catch
мы создаем еще keysToFetch
массив keysToFetch
, на этот раз с доступными ключами для почтовых адресов, данных изображений и данных изображений. Затем мы используем хранилище контактов для извлечения нового экземпляра CNContact
с необходимой нам информацией, используя метод unifiedContactWithIdentifier(_:keysToFetch:)
.
Еще раз отметим, что мы обновляем пользовательский интерфейс в главном потоке. Мы проверяем, есть ли у контакта данные изображения, доступные для загрузки, и по возможности превращаем их в объект UIImage
. Мы наполняем ярлыки fullName
и email
правильной информацией.
Хотя в этом примере это не является строго необходимым, поскольку мы знаем, какие ключи доступны, мы проверяем, может ли наше приложение получить доступ к информации о почтовых адресах контакта. Обратите внимание, что этот шаг является лишь примером, но его всегда следует выполнять с контактами, если вы не уверены, к какой информации вы можете получить доступ.
Мы извлекаем первый почтовый адрес контакта (представленный классом CNPostalAddress
) и форматируем его в строку, используя экземпляр CNPostalAddressFormatter
. Класс CNPostalAddress
функционирует аналогично классу CNContact
но имеет различные свойства, такие как улица, провинция и страна.
Создайте и запустите приложение в симуляторе и выберите контакт из списка. Отображаемый подробный вид должен выглядеть примерно так:
2. Создание и обновление контактов
Помимо извлечения контактов, вы также можете создавать и обновлять существующие контакты, используя CNMutableContact
и CNSaveRequest
. Откройте AddContactViewController.swift и замените свойство contact
следующей реализацией:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
var contact: CNContact {
get {
let store = CNContactStore()
let contactToAdd = CNMutableContact()
contactToAdd.givenName = self.firstName.text ??
contactToAdd.familyName = self.lastName.text ??
let mobileNumber = CNPhoneNumber(stringValue: (self.mobileNumber.text ?? «»))
let mobileValue = CNLabeledValue(label: CNLabelPhoneNumberMobile, value: mobileNumber)
contactToAdd.phoneNumbers = [mobileValue]
let email = CNLabeledValue(label: CNLabelHome, value: (self.homeEmail.text ?? «»))
contactToAdd.emailAddresses = [email]
if let image = self.contactImage.image {
contactToAdd.imageData = UIImagePNGRepresentation(image)
}
let saveRequest = CNSaveRequest()
saveRequest.addContact(contactToAdd, toContainerWithIdentifier: nil)
do {
try store.executeSaveRequest(saveRequest)
} catch {
print(error)
}
return contactToAdd
}
}
|
Мы создаем объект CNMutableContact
и присваиваем ему givenName
и familyName
. Обратите внимание, что мы используем ??
или оператор слияния ноль. Если значение слева от оператор равен nil
, вместо него присваивается значение справа.
Мы создаем объект CNPhoneNumber
для представления номера мобильного телефона, введенного в текстовое поле. Затем мы помещаем это число в объект CNLabeledValue
с постоянной CNLabelPhoneNumberMobile
. Использование класса CNPhoneNumber
необходимо, поскольку номера телефонов могут быть отформатированы различными способами в разных регионах. Этот класс принимает строку и создает значение телефонного номера, с которым затем может работать остальная часть структуры контактов. Затем mobileValue
помещается в массив и присваивается свойству phoneNumbers
изменяемого контакта.
Мы создаем аналогичное значение CNLabeledValue
для электронной почты, CNLabelHome
ярлык CNLabelHome
. Это значение затем присваивается свойству emailAddresses
контакта. Мы проверяем, было ли изображение назначено контакту, и, если это так, назначаем его необработанные данные свойству imageData
контакта.
Чтобы сохранить контакт, мы создаем объект CNSaveRequest
и вызываем его addContact(_:toContainerWithIdentifier:)
чтобы сообщить платформе Contacts, что мы хотим создать новый контакт. Передав nil
в качестве идентификатора, новый контакт будет сохранен в группе контактов по умолчанию.
В другом операторе do-catch
мы сообщаем хранилищу контактов выполнить запрос на сохранение. Наконец, мы возвращаем недавно созданный контакт для использования в остальной части приложения.
Создайте и запустите приложение и нажмите кнопку «плюс» в правом верхнем углу, чтобы добавить новый контакт. Заполните форму, добавьте фотографию и нажмите Готово . Ваш новый контакт должен быть добавлен в табличное представление главного контроллера представления, как показано ниже.
Обновление существующего контакта очень похоже на создание нового. Хотя мы не будем реализовывать это в приложении, код для обновления существующего контакта будет выглядеть следующим образом:
1
2
3
4
5
6
|
let contactToUpdate = existingContact.mutableCopy()
contactToUpdate.emailAddresses.append(CNLabeledValue(label: CNLabelWork, value: emailToAdd))
let saveRequest = CNSaveRequest()
saveRequest.updateContact(contactToUpdate)
try store.executeSaveRequest(saveRequest)
|
3. Контроллер выбора контактов
Как я уже упоминал в первой части этого руководства, платформа контактов не имеет API для прямого доступа к каждому контакту на устройстве пользователя. Это необходимо для защиты конфиденциальности пользователя, чтобы приложения не могли читать все его контакты и собирать информацию.
К счастью, платформа предоставляет подкласс UIViewController
, CNContactPickerViewController
, который предоставляет пользователю доступ ко всем контактам, хранящимся на устройстве.
Повторно посетите MasterViewController.swift и добавьте оператор импорта в верхней части для ContactUI .
1
|
import ContactsUI
|
Затем MasterViewController
класс MasterViewController
соответствие с протоколом CNContactPickerDelegate
.
1
|
class MasterViewController: UITableViewController, CNContactPickerDelegate
|
Замените реализацию метода addExistingContact()
класса MasterViewController
следующим:
1
2
3
4
5
|
func addExistingContact() {
let contactPicker = CNContactPickerViewController()
contactPicker.delegate = self
self.presentViewController(contactPicker, animated: true, completion: nil)
}
|
Наконец, добавьте следующий метод протокола MasterViewController
класс MasterViewController
:
1
2
3
|
func contactPicker(picker: CNContactPickerViewController, didSelectContact contact: CNContact) {
NSNotificationCenter.defaultCenter().postNotificationName(«addNewContact», object: nil, userInfo: [«contactToAdd»: contact])
}
|
Создайте и запустите приложение в последний раз и нажмите « Добавить существующий» в верхнем левом углу. Должен появиться контроллер вида, подобный следующему:
Если контакт в контроллере представления средства выбора контактов выбран, контроллер представления отклоняется, и выбранный контакт добавляется в представление таблицы главного контроллера представления.
Контроллер представления выбора контактов также поддерживает несколько вариантов выбора в зависимости от методов, которые реализует делегат. Его также можно настроить для доступа к определенным свойствам, а также для фильтрации отображаемых контактов на основе предикатов. Для получения дополнительной информации я рекомендую прочитать CNContactPickerViewController
класс CNContactPickerViewController
и ссылку на протокол CNContactPickerDelegate
.
Вывод
Как видите, новая платформа Contacts в iOS 9, OS X El Capitan и watchOS 2 — это очень хорошо разработанная и простая в использовании коллекция API. Теперь вам должно быть удобно получать доступ, создавать и обновлять контакты на устройстве пользователя. Как всегда, обязательно оставляйте свои комментарии и отзывы в комментариях ниже.