【Swift UIKit】CNContactで連絡先と連携させて情報を取得する方法!
この記事からわかること
- SwiftのUIKitでContactsフレームワークを使ってユーザーの連絡先と連携する方法
- 連絡先の氏名や誕生日などを取得するには?
- CNContactやCNContactFetchRequestクラスの使い方
- 取得できる情報のキー(CNKeyDescriptor)の種類
- CNMutableContactとCNSaveRequestを使った連絡先の追加方法
- テーブルビューへの組み込み方法
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
SwiftのContactsフレームワークを導入して標準の連絡先との連携方法をまとめていきます。
Contactsフレームワークとは?
ContactsフレームワークとはiOSアプリ開発において、ユーザーの連絡先情報に、連絡先への連携や操作を可能にできるユーザーインターフェースを提供しているフレームワークです。
Xcodeには既に導入されているフレームワークなのでimport
を記述することで簡単に導入することが可能です。
import Contacts
info.plistへのキー追加
Contactsを用いることでユーザーの連絡先に、開発したアプリからアクセスすることができますが、その前に開発するアプリのinfo.plistへのキー:NSContactsUsageDescriptionの追加が必要になります。NSCalendarsUsageDescription
とキーに入力するとPrivacy - Contacts Usage Description
と自動変換されます。
キーを追加していないとアプリがクラッシュしてしまうので注意してください。
連絡先ではなく、カレンダーアプリやリマインダーなどにアクセスするためのEventKitフレームワークも用意されています。基本的な使い方は同じなので導入してみてください。
Contactsの仕組み
ContactsではCNContactStore
(コンタクトストア)クラスとして連絡先へのアクセスをサポートしています。
class CNContactStore : NSObject
このコンタクトストアごとにアクセスへの許可や操作などを行います。複数インスタンス化することもできますが、アプリ内では1つのインスタンスを生成し、そのインスタンスごとに許可申請や操作をしないとエラーの元になるので注意してください。
let contactStore: CNContactStore = CNContactStore()
requestAccessで許可申請
連絡先へのアクセスはユーザーの許可がないとアクセスすることはできません。申請を出すにはインスタンス化したEKEventStoreからrequestAccess
メソッドを呼び出してポップアップを表示させユーザーへ許可を申請します。
func authStatusContactRequest(){
if CNContactStore.authorizationStatus(for: .contacts) == .notDetermined{
contactStore.requestAccess(for: .contacts, completionHandler: { (granted, error) in
if granted && error == nil {
print("許可") }
})
}
}
authorizationStatusでステータスを取得
許可申請を出す前に現在の許可状態を取得し、未許可であれば申請を出すようにします。authorizationStatus
からCNAuthorizationStatus
型の現在の許可状態が取得できるのでnotDetermined
(未申請)の場合のみ申請を出すようにしておきます。
公式リファレンス:authorizationStatusメソッド
enum CNAuthorizationStatus : Int, @unchecked Sendable {
case authorized // 承認済
case denied // 明示的に拒否
case notDetermined // 未選択
case restricted // 未承認
}
使用しやすくするために独自のContactControllerクラスを用意してContactsを定義しておきます。許可申請や連絡先の取得などを関数などにまとめて呼び出しやすいようにしておきました。
import Foundation
import Contacts
import UIKit
class ContactController {
let contactStore: CNContactStore = CNContactStore()
var contacts: [CNContact]?
let keys = [CNContactGivenNameKey as CNKeyDescriptor,
CNContactBirthdayKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor]
init(){
authStatusContactRequest()
loadContacts()
}
func authStatusContactRequest(){
if CNContactStore.authorizationStatus(for: .contacts) == .notDetermined{
contactStore.requestAccess(for: .contacts, completionHandler: { (granted, error) in
if granted && error == nil {
print("許可")
}
})
}
}
func loadContacts() {
}
func addContact(given:String,family:String){
}
}
連絡先データの取得
- 取得したい情報のキーを定義
- CNContactFetchRequestクラスにキーを渡して取得リクエストを作成
- enumerateContactsメソッドで連絡先を取得
- 一連の処理はバックグラウンドスレッドで実行
func loadContacts() {
let globalQ = DispatchQueue.global(qos: .background)
globalQ.async {
let request = CNContactFetchRequest(keysToFetch: self.keys)
var contacts:[CNContact] = []
do {
try self.contactStore.enumerateContacts(with: request) { contact, pointer in
contacts.append(contact)
print(contact.givenName)
print(contact.familyName)
print(contact.birthday)
print(contact.phoneNumbers)
}
} catch {
print("error")
}
self.contacts = contacts
}
}
連絡先取得の1連の操作はバックグラウンドスレッドの実行が公式よりオススメされているようです。
”連絡先ストアは、すべての I/O 操作をカプセル化し、連絡先とグループの取得と保存を担当します。連絡先ストア メソッドは同期的であるため、バックグラウンド スレッドで使用することをお勧めします。”
取得したい情報のキーを定義
連絡先の情報を取得するにはあらかじめ取得したい情報のキーを定義しておきます。キーの全ての種類は以下の公式ページを参照してください。
例えば以下は名前(姓名)と誕生日、電話番号を取得したい時のキー定義です。
let keys = [CNContactGivenNameKey as CNKeyDescriptor,
CNContactBirthdayKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor]
CNContactFetchRequestクラス
公式リファレンス:CNContactFetchRequestクラス
let request = CNContactFetchRequest(keysToFetch: self.keys)
キー定義ができたらCNContactFetchRequest
クラスを使ってリクエストオブジェクトを生成します。引数には取得したい連絡先情報のキーを渡します。
enumerateContactsメソッド
公式リファレンス:enumerateContactsメソッド
func enumerateContacts(
with fetchRequest: CNContactFetchRequest,
usingBlock block: (CNContact, UnsafeMutablePointer<ObjCBool>) -> Void
) throws
実際に連絡先を取得するにはenumerateContacts
メソッドを使用します。引数に先程定義したリクエスト情報を渡し、引数block
内でマッチする連絡先ごとに連絡先情報にアクセスできます。
throws
キーワードがついているのでtry
文でエラーキャッチができる
ようにしておきます。
do {
try self.contactStore.enumerateContacts(with: request) { contact, pointer in
contacts.append(contact)
print(contact.givenName)
print(contact.familyName)
print(contact.birthday)
print(contact.phoneNumbers)
}
} catch {
print("error")
}
連絡先データの追加
- CNSaveRequestオブジェクトを生成
- CNMutableContactオブジェクトに個人情報を格納
- リクエストに個人情報を追加
- コンタクトストアからリクエストを実行
func addContact(given:String,family:String){
let request = CNSaveRequest()
let contact = CNMutableContact()
contact.givenName = given // "名"
contact.familyName = family // "姓"
contact.phoneticGivenName = "なまえ"
contact.phoneticFamilyName = "みょうじ"
contact.phoneNumbers = [CNLabeledValue<CNPhoneNumber>(label: CNLabelPhoneNumberMain, value: CNPhoneNumber(stringValue: "000-1234-5678"))]
contact.emailAddresses = [CNLabeledValue<NSString>(label: CNLabelHome, value: NSString(string: "ame@sample.com"))]
request.add(contact, toContainerWithIdentifier: contactStore.defaultContainerIdentifier())
do {
print("追加")
try contactStore.execute(request)
} catch {
print(error)
}
}
テーブルビューに表示させてみる
これで連絡先の情報を取得/追加できるようになったのでテーブルビューに当てはめて表示させていきます。
import Foundation
import Contacts
import UIKit
class ContactController {
let contactStore: CNContactStore = CNContactStore()
var contacts: [CNContact]?
let keys = [CNContactGivenNameKey as CNKeyDescriptor,
CNContactBirthdayKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor]
init(){
authStatusContactRequest()
loadContacts()
}
func authStatusContactRequest(){
if CNContactStore.authorizationStatus(for: .contacts) == .notDetermined{
contactStore.requestAccess(for: .contacts, completionHandler: { (granted, error) in
if granted && error == nil {
print("許可")
}
})
}
}
func loadContacts() {
let globalQ = DispatchQueue.global(qos: .background)
globalQ.async {
let request = CNContactFetchRequest(keysToFetch: self.keys)
var contacts:[CNContact] = []
do {
try self.contactStore.enumerateContacts(with: request) { contact, pointer in
contacts.append(contact)
print(contact.givenName)
print(contact.familyName)
print(contact.birthday)
print(contact.phoneNumbers)
}
} catch {
print("error")
}
self.contacts = contacts
}
}
func addContact(given:String,family:String){
let request = CNSaveRequest()
let contact = CNMutableContact()
contact.givenName = given // "名"
contact.familyName = family // "姓"
contact.phoneticGivenName = "なまえ"
contact.phoneticFamilyName = "みょうじ"
contact.phoneNumbers = [CNLabeledValue<CNPhoneNumber>(label: CNLabelPhoneNumberMain, value: CNPhoneNumber(stringValue: "000-1234-5678"))]
contact.emailAddresses = [CNLabeledValue<NSString>(label: CNLabelHome, value: NSString(string: "ame@sample.com"))]
request.add(contact, toContainerWithIdentifier: contactStore.defaultContainerIdentifier())
do {
print("追加")
try contactStore.execute(request)
} catch {
print(error)
}
}
}
import UIKit
class ContactViewController: UIViewController ,UITableViewDataSource,UITableViewDelegate {
let contactController = ContactController()
@IBOutlet var givenNametextField:UITextField!
@IBOutlet var familyNameField:UITextField!
@IBOutlet var entryBtn:UIButton!
@IBOutlet var tableView:UITableView!
override func viewDidLoad() {
super.viewDidLoad()
entryBtn.addTarget(self, action: #selector(self.entryBtnTapped), for: .touchUpInside)
}
@objc func entryBtnTapped(){
if givenNametextField.hasText && familyNameField.hasText {
contactController.addContact(given: givenNametextField.text!, family: familyNameField.text!)
}
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contactController.contacts?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: "myCell")
let name = contactController.contacts![indexPath.row].givenName + " " + contactController.contacts![indexPath.row].familyName
cell.textLabel!.text = name
return cell
}
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。