Статьи

iOS 9: введение в платформу контактов

С iOS 9, OS X El Capitan и watchOS 2 компания Apple представила совершенно новый фреймворк Contacts . Эта структура обеспечивает объектно-ориентированный подход к работе с контактной информацией пользователя и заменяет функциональную структуру адресной книги .

В этом руководстве мы собираемся переопределить основные функции приложения «Контакты» на iOS, чтобы вы могли узнать, как работают эти новые API.

Это руководство требует, чтобы вы работали с Xcode 7+ в OS X Yosemite или более поздней версии. Вам также необходимо скачать стартовый проект с GitHub .

Сначала мы собираемся использовать структуру контактов для доступа к контактам пользователя и их отображения в виде таблицы. Откройте стартовый проект и перейдите к 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 но имеет различные свойства, такие как улица, провинция и страна.

Создайте и запустите приложение в симуляторе и выберите контакт из списка. Отображаемый подробный вид должен выглядеть примерно так:

Просмотр контактных данных

Помимо извлечения контактов, вы также можете создавать и обновлять существующие контакты, используя 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)

Как я уже упоминал в первой части этого руководства, платформа контактов не имеет 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. Теперь вам должно быть удобно получать доступ, создавать и обновлять контакты на устройстве пользователя. Как всегда, обязательно оставляйте свои комментарии и отзывы в комментариях ниже.