ManagingContactsUI/ContactViewController/ContactViewController.swift
/* |
Copyright (C) 2017 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Shows how to create and display a new contact, an unknown contact, or existing |
contact using the CNContactViewController class. Use the |
highlightProperty(withKey:identifier:) property to highlight the contact's |
iPhone number. Creates and pushes the contact view controller. Demonstrate how |
to dismiss the contact view controller when using init(forNewContact:) to |
create a new contact. Uses CNContactViewControllerDelegate's |
contactViewController(_:shouldPerformDefaultActionFor:) to prevent users from |
performing default actions such as dialing a phone number. |
*/ |
import UIKit |
import Contacts |
import ContactsUI |
class ContactViewController: UITableViewController { |
// MARK: - Types |
struct PhoneNumber { |
static let iPhone = "(408) 555-0126" |
static let mobile = "(415) 123-4567" |
} |
struct Name { |
static let family = "Appleseed" |
static let given = "Jane" |
} |
struct Address { |
static let street = "1 Infinite Loop" |
static let city = "Cupertino" |
static let state = "CA" |
static let postalCode = "95014" |
} |
struct CreateNewContact { |
static let textLabel = "Create New Contact" |
static let subtitle = "Enter data for a new contact." |
} |
struct CreateNewContactWithSomeData { |
static let textLabel = "Create New Contact With Existing Data" |
static let subtitle = "Save the new contact." |
} |
struct EditUnknownContact { |
static let textLabel = "Edit Unknown Contact" |
static let subtitle = "Add data to an existing person or use them to create a new contact." |
} |
struct DisplayOrEditContact { |
static let textLabel = "Display and Edit Contact" |
static let subtitle = "Show and edit a contact." |
} |
// MARK: - Properties |
var store = CNContactStore() |
/// Keep track of the selected table row. |
var tableRowSelected: IndexPath? |
/// Key to be highlighted in the view controller. |
var phoneNumberKey = "phoneNumbers" |
/// Message, which includes the contact's name that was successfully added. |
var message: String? |
/** |
Data model for the UI. The "Display and Edit Contact" feature uses |
fetchContact(with:completion:) that requires access to Contacts. |
The data property contains the CreateNewContact, |
CreateNewContactWithSomeData, EditUnknownContact, and |
DisplayOrEditContact structures when access was |
granted and CreateNewContact, CreateNewContactWithSomeData, |
EditUnknownContact, otherwise. |
*/ |
var data = [Section(section: [LabelValue(label: ContactViewController.CreateNewContact.textLabel, |
value: ContactViewController.CreateNewContact.subtitle)]), |
Section(section: [LabelValue(label: ContactViewController.CreateNewContactWithSomeData.textLabel, |
value: ContactViewController.CreateNewContactWithSomeData.subtitle)]), |
Section(section: [LabelValue(label: ContactViewController.EditUnknownContact.textLabel, |
value: ContactViewController.EditUnknownContact.subtitle)])] |
// MARK: - View Life Cycle |
override func viewDidLoad() { |
super.viewDidLoad() |
checkContactsAccess() |
} |
override func viewDidAppear(_ animated: Bool) { |
super.viewDidAppear(animated) |
/* |
If message exists, then a contact was successfully added. |
Let's show an alert to indicate it. |
*/ |
if let myMessage = message { |
alert(with: myMessage) |
message = nil |
} |
} |
// MARK: - Display Alert |
/// Create and display an alert. |
func alert(with message: String) { |
let alert = Helper.alert(with: message) |
navigationController?.present(alert, animated: true, completion: nil) |
} |
// MARK: - Contacts Access |
/** |
Checks the authorization status for Contacts. Requests access if the |
returned status is .notDetermined. |
*/ |
func checkContactsAccess() { |
switch CNContactStore.authorizationStatus(for: .contacts) { |
// Access was granted. Update the UI with the default navigation menu. |
case .authorized: configureMenuNavigation() |
case .notDetermined: self.store.requestAccess(for: .contacts, completionHandler: {(granted, _) in |
if granted { |
self.configureMenuNavigation() |
}}) |
// Access was denied or restricted. |
case .restricted, .denied: print(AppConfiguration.Messages.accessDeniedOrRestricted) |
} |
} |
/// Provides the full navigation menu when access was granted to Contacts. |
func configureMenuNavigation() { |
DispatchQueue.main.async { |
self.data = [Section(section: [LabelValue(label: ContactViewController.CreateNewContact.textLabel, |
value: ContactViewController.CreateNewContact.subtitle)]), |
Section(section: [LabelValue(label: ContactViewController.CreateNewContactWithSomeData.textLabel, |
value: ContactViewController.CreateNewContactWithSomeData.subtitle)]), |
Section(section: [LabelValue(label: ContactViewController.EditUnknownContact.textLabel, |
value: ContactViewController.EditUnknownContact.subtitle)]), |
Section(section: [LabelValue(label: ContactViewController.DisplayOrEditContact.textLabel, |
value: ContactViewController.DisplayOrEditContact.subtitle)])] |
self.tableView.reloadData() |
} |
} |
// MARK: - UITableViewDataSource |
override func numberOfSections(in tableView: UITableView) -> Int { |
return data.count |
} |
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { |
return data[section].section.count |
} |
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { |
return tableView.dequeueReusableCell(withIdentifier: AppConfiguration.TableViewCellIdentifiers.cell, for: indexPath) |
} |
// MARK: - UITableViewDelegate |
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { |
let section = data[indexPath.section].section |
let item = section[indexPath.row] |
cell.textLabel?.text = item.label |
cell.detailTextLabel?.text = item.value |
} |
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { |
tableRowSelected = indexPath |
switch indexPath.section { |
case 0: createContact() |
case 1: createContactWithSomeData() |
case 2: editUnknownContact() |
case 3: displayContact() |
default: break |
} |
} |
// MARK: - Create New Contact |
/** |
Called when users tap "Create New Contact" in the UI. Create and launch |
an empty contacts view controller. |
*/ |
func createContact() { |
// Create an empty contact view controller. |
let contactViewController = CNContactViewController(forNewContact: nil) |
// Set its delegate. |
contactViewController.delegate = self |
// Push it using the navigation controller. |
navigationController?.pushViewController(contactViewController, animated: true) |
} |
/** |
Called when users tap "Create New Contact With Existing Data" in the UI. |
Create and launch a contacts view controller with pre-filled fields. |
*/ |
func createContactWithSomeData() { |
let contact = CNMutableContact() |
// Given and family names. |
contact.familyName = ContactViewController.Name.family |
contact.givenName = ContactViewController.Name.given |
// Phone numbers. |
contact.phoneNumbers = [CNLabeledValue(label: CNLabelPhoneNumberiPhone, |
value: CNPhoneNumber(stringValue:ContactViewController.PhoneNumber.iPhone)), |
CNLabeledValue(label: CNLabelPhoneNumberMobile, |
value: CNPhoneNumber(stringValue:ContactViewController.PhoneNumber.mobile))] |
// Postal address. |
let homeAddress = CNMutablePostalAddress() |
homeAddress.street = ContactViewController.Address.street |
homeAddress.city = ContactViewController.Address.city |
homeAddress.state = ContactViewController.Address.state |
homeAddress.postalCode = ContactViewController.Address.postalCode |
contact.postalAddresses = [CNLabeledValue(label: CNLabelHome, value: homeAddress)] |
// Create a contact view controller with the above contact. |
let contactViewController = CNContactViewController(forNewContact: contact) |
// Set its delegate. |
contactViewController.delegate = self |
// Push it using the navigation controller. |
navigationController?.pushViewController(contactViewController, animated: true) |
} |
// MARK: - Find Contact |
/// - returns: Exising contacts matching the specified name. |
func fetchContact(with name: String, completion: @escaping (_ contacts: [CNContact]) -> Void) { |
var result = [CNContact]() |
do { |
result = try store.unifiedContacts(matching: CNContact.predicateForContacts(matchingName: name), |
keysToFetch: [CNContactViewController.descriptorForRequiredKeys()]) |
} catch let error as NSError { |
print("\(AppConfiguration.Messages.error) \(error.localizedDescription)") |
} |
DispatchQueue.main.async { |
completion(result) |
} |
} |
// MARK: - Display Existing Contact |
/** |
Called when users tap "Display and Edit Contact" in the UI. Searches |
for the contact specified whose last name and first name are respectively |
specified by contact.family and contact.given. If the search was |
successful, display the contact, allow users to edit the contact's |
information and to perform actions. Show an alert, otherwise. |
*/ |
func displayContact() { |
let name = "\(ContactViewController.Name.given) \(ContactViewController.Name.family)" |
fetchContact(with: name, completion: ({(contacts: [CNContact]) in |
DispatchQueue.main.async { |
if !contacts.isEmpty { |
let contactViewController = CNContactViewController(for: contacts.first!) |
contactViewController.allowsEditing = true |
contactViewController.allowsActions = true |
contactViewController.delegate = self |
/* |
Set the view controller's highlightProperty if |
highlightedPropertyIdentifier exists. Thus, ensuring |
that the contact's phone number specified by |
highlightedPropertyIdentifier will be highlighted in the |
UI. |
*/ |
if let highlightedPropertyIdentifiers = contacts.first?.phoneNumbers.first?.identifier { |
contactViewController.highlightProperty(withKey: self.phoneNumberKey, identifier: highlightedPropertyIdentifiers) |
} |
// Show the view controller. |
self.navigationController?.pushViewController(contactViewController, animated: true) |
} else { |
self.alert(with: "\(AppConfiguration.Messages.couldNotFind) \(name) \(AppConfiguration.Messages.inContacts)") |
if let selectedIndexPath = self.tableRowSelected { |
self.tableView.deselectRow(at: selectedIndexPath, animated: false) |
} |
} |
} |
})) |
} |
// MARK: - Display Unknown Contact |
/** |
Called when users tap "Edit Unknown Contact" in the UI. The view |
controller displays some contact information that you can either add to |
an existing contact or use them to create a new contact. |
*/ |
func editUnknownContact() { |
let contact = CNMutableContact() |
// Phone number. |
contact.phoneNumbers = [CNLabeledValue(label: CNLabelPhoneNumberiPhone, |
value: CNPhoneNumber(stringValue: ContactViewController.PhoneNumber.mobile))] |
// Postal address. |
let homeAddress = CNMutablePostalAddress() |
homeAddress.street = ContactViewController.Address.street |
homeAddress.city = ContactViewController.Address.city |
homeAddress.state = ContactViewController.Address.state |
homeAddress.postalCode = ContactViewController.Address.postalCode |
contact.postalAddresses = [CNLabeledValue(label: CNLabelHome, value: homeAddress)] |
// Create a view controller that allows editing. |
let contactViewController = CNContactViewController(forUnknownContact: contact) |
contactViewController.allowsEditing = true |
contactViewController.contactStore = CNContactStore() |
contactViewController.delegate = self |
// Push the unknown contact in the view controler. |
navigationController?.pushViewController(contactViewController, animated: true) |
} |
} |
/** |
Extends `ContactViewController` to conform to the |
`CNContactViewControllerDelegate` protocol. |
*/ |
extension ContactViewController: CNContactViewControllerDelegate { |
/** |
Setting it to false prevents users to perform default actions such as |
dialing a phone number, when they select a contact property. |
*/ |
func contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) -> Bool { |
return false |
} |
/** |
Used to dismiss the view controller when using init(forNewContact:) to |
create a new contact. |
*/ |
func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) { |
if let tableRowSelected = tableRowSelected?.section, tableRowSelected == 0 || tableRowSelected == 1 { |
defer { |
_ = self.navigationController?.popViewController(animated: true) |
} |
guard let contact = contact else { |
return |
} |
message = "\(contact.formattedName) \(AppConfiguration.Messages.added)" |
} |
} |
} |
Copyright © 2017 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2017-07-20