Xamarin.iOS の Contacts と ContactsUI

この記事では、Xamarin.iOS アプリでの新しい連絡先と連絡先 UI フレームワークの操作について説明します。 これらのフレームワークは、iOS の以前のバージョンで使用されていた既存のアドレス帳とアドレス帳 UI を置き換えます。

iOS 9 の導入に伴い、Apple は、iOS 8 以前で使われていた既存のアドレス帳とアドレス帳 UI フレームワークを置き換える 2 つの新しいフレームワーク ContactsContactsUI をリリースしました。

この 2 つの新しいフレームワークには、次の機能が含まれています:

  • Contacts - ユーザーの連絡先リスト データへのアクセスを提供します。 ほとんどのアプリは読み取り専用アクセスのみを必要とするため、このフレームワークはスレッドセーフな読み取り専用アクセス用に最適化されています。

  • ContactsUI - iOS デバイスで連絡先を表示、編集、選択、作成するための Xamarin.iOS UI 要素を提供します。

An example Contact Sheet on an iOS device

重要

iOS 8 (およびそれ以前) で使用されている既存の AddressBook および AddressBookUI フレームワークは、iOS 9 では非推奨となり、既存の Xamarin.iOS アプリ用にできるだけ早く新しい Contacts および ContactsUI フレームワークに置き換える必要があります。 新しいアプリは、新しいフレームワークに対応するよう作成する必要があります。

以降のセクションでは、これらの新しいフレームワークと、これらを Xamarin.iOS アプリで実装する方法について説明します。

Contacts フレームワーク

Contacts フレームワークは、Xamarin.iOS にユーザーの連絡先情報へのアクセスを提供します。 ほとんどのアプリは読み取り専用アクセスのみを必要とするため、このフレームワークはスレッドセーフな読み取り専用アクセス用に最適化されています。

連絡先オブジェクト

CNContact クラスは、名前、住所、電話番号など、連絡先のプロパティへのスレッド セーフな読み取り専用アクセスを提供します。 CNContactNSDictionary のように機能し、複数の読み取り専用プロパティのコレクション (アドレスや電話番号など) を含みます:

Contact Object overview

複数の値 (メール アドレスや電話番号など) を持つプロパティは、NSLabeledValue オブジェクトの配列として表されます。 NSLabeledValue は、読み取り専用のラベルと値のセットで構成されるスレッド セーフなタプルです。ラベルによってユーザーに値が定義されます (自宅または勤務先のメールなど)。 Contacts フレームワークには、アプリで使用できる定義済みのラベル (CNLabelKey および CNLabelPhoneNumberKey 静的クラスを使用) が用意されています。また、ニーズに合わせてカスタム ラベルを定義することもできます。

既存の連絡先の値を調整する (または新しい連絡先を作成する) 必要がある Xamarin.iOS アプリには、クラスとそのサブ クラス (CNMutablePostalAddress など) の NSMutableContact バージョンを使用します。

たとえば、次のコードは、新しい連絡先を作成し、ユーザーの連絡先のコレクションに追加します:

// Create a new Mutable Contact (read/write)
var contact = new CNMutableContact();

// Set standard properties
contact.GivenName = "John";
contact.FamilyName = "Appleseed";

// Add email addresses
var homeEmail = new CNLabeledValue<NSString>(CNLabelKey.Home, new NSString("john.appleseed@mac.com"));
var workEmail = new CNLabeledValue<NSString>(CNLabelKey.Work, new NSString("john.appleseed@apple.com"));
contact.EmailAddresses = new CNLabeledValue<NSString>[] { homeEmail, workEmail };

// Add phone numbers
var cellPhone = new CNLabeledValue<CNPhoneNumber>(CNLabelPhoneNumberKey.iPhone, new CNPhoneNumber("713-555-1212"));
var workPhone = new CNLabeledValue<CNPhoneNumber>("Work", new CNPhoneNumber("408-555-1212"));
contact.PhoneNumbers = new CNLabeledValue<CNPhoneNumber>[] { cellPhone, workPhone };

// Add work address
var workAddress = new CNMutablePostalAddress()
{
    Street = "1 Infinite Loop",
    City = "Cupertino",
    State = "CA",
    PostalCode = "95014"
};
contact.PostalAddresses = new CNLabeledValue<CNPostalAddress>[] { new CNLabeledValue<CNPostalAddress>(CNLabelKey.Work, workAddress) };

// Add birthday
var birthday = new NSDateComponents()
{
    Day = 1,
    Month = 4,
    Year = 1984
};
contact.Birthday = birthday;

// Save new contact
var store = new CNContactStore();
var saveRequest = new CNSaveRequest();
saveRequest.AddContact(contact, store.DefaultContainerIdentifier);

// Attempt to save changes
NSError error;
if (store.ExecuteSaveRequest(saveRequest, out error))
{
    Console.WriteLine("New contact saved");
}
else
{
    Console.WriteLine("Save error: {0}", error);
}

このコードが iOS 9 デバイスで実行された場合、新しい連絡先がユーザーのコレクションに追加されます。 次に例を示します。

A new contact added to the user's collection

連絡先の書式設定とローカライズ

Contacts フレームワークには、ユーザーに表示するコンテンツの書式設定とローカライズに役立つ複数のオブジェクトとメソッドが含まれています。 たとえば、次のコードは、連絡先の名前と住所を表示用に正しく書式設定します:

Console.WriteLine(CNContactFormatter.GetStringFrom(contact, CNContactFormatterStyle.FullName));
Console.WriteLine(CNPostalAddressFormatter.GetStringFrom(workAddress, CNPostalAddressFormatterStyle.MailingAddress));

アプリの UI に表示するプロパティ ラベルの場合、Contact フレームワークには、これらの文字列をローカライズするためのメソッドもあります。 これもまた、アプリが実行されている iOS デバイスの現在のロケールに基づいて行われます。 次に例を示します。

// Localized properties
Console.WriteLine(CNContact.LocalizeProperty(CNContactOptions.Nickname));
Console.WriteLine(CNLabeledValue<NSString>.LocalizeLabel(CNLabelKey.Home));

既存の連絡先のフェッチ

CNContactStore クラスのインスタンスを使用すると、ユーザーの連絡先データベースから連絡先情報をフェッチできます。 CNContactStore には、データベースから連絡先とグループをフェッチまたは更新するために必要なすべてのメソッドが含まれています。 これらのメソッドは同期的であるため、UI をブロックしないようにバックグラウンド スレッドで実行することをお勧めします。

述語 (CNContact クラスから構築) を使用すると、データベースから連絡先をフェッチするときに返される結果をフィルター処理できます。 文字列 Appleseed を含む連絡先のみをフェッチするには、次のコードを使用します:

// Create predicate to locate requested contact
var predicate = CNContact.GetPredicateForContacts("Appleseed");

重要

Contacts フレームワークでは、ジェネリック述語と複合述語はサポートされていません。

たとえば、フェッチを連絡先の GivenName および FamilyName プロパティのみに制限するには、次のコードを使用します:

// Define fields to be searched
var fetchKeys = new NSString[] {CNContactKey.GivenName, CNContactKey.FamilyName};

最後に、データベースを検索して結果を返すには、次のコードを使用します:

// Grab matching contacts
var store = new CNContactStore();
NSError error;
var contacts = store.GetUnifiedContacts(predicate, fetchKeys, out error);

このコードが上記の Contacts オブジェクト セクションで作成したサンプルの後に実行された場合は、先ほど作成した "John Appleseed" の連絡先が返されます。

連絡先へのアクセスにおけるプライバシー

エンド ユーザーはアプリケーションごとに連絡先情報へのアクセスを許可または拒否できるため、CNContactStore への初回の呼び出しを行うと、アプリによるアクセスを許可するように求めるダイアログが表示されます。

アクセス許可要求はアプリが初めて実行されたときに 1 回だけ表示され、それ以降の CNContactStore の実行または呼び出しでは、初回の要求時にユーザーが選択したアクセス許可が使用されます。

アプリは、ユーザーが連絡先データベースへのアクセスを拒否した場合に適切に対応するよう設計する必要があります。

部分連絡先のフェッチ

部分連絡先とは、使用可能なプロパティの一部のみが連絡先ストアからフェッチされた連絡先です。 以前にフェッチされていないプロパティにアクセスしようとすると、例外が発生します。

CNContact インスタンスの IsKeyAvailable または AreKeysAvailable メソッドを使用して、特定の連絡先に必要なプロパティがあるかどうかを簡単に確認できます。 次に例を示します。

// Does the contact contain the requested key?
if (!contact.IsKeyAvailable(CNContactOption.PostalAddresses)) {
    // No, re-request to pull required info
    var fetchKeys = new NSString[] {CNContactKey.GivenName, CNContactKey.FamilyName, CNContactKey.PostalAddresses};
    var store = new CNContactStore();
    NSError error;
    contact = store.GetUnifiedContact(contact.Identifier, fetchKeys, out error);
}

重要

CNContactStore クラスの GetUnifiedContact および GetUnifiedContacts メソッドは、指定されたフェッチ キーから要求されたプロパティに限定された部分連絡先のみを返します。

統合連絡先

ユーザーによっては、連絡先データベース (iCloud、Facebook、Google Mail など) 内の 1 人の連絡先に対して異なる連絡先情報のソースがある場合があります。 iOS および OS X アプリでは、この連絡先情報は自動的にリンクされ、1 つの統合連絡先としてユーザーに表示されます:

Unified Contacts overview

統合連絡先は、リンクされた連絡先情報の一時的なメモリ内ビューであり、それぞれ一意識別子が与えられます (この識別子は必要に応じて連絡先を再フェッチするために使用されます)。 既定では、Contacts フレームワークは可能な限り統合連絡先を返します。

連絡先の作成と更新

連絡先オブジェクト セクションで説明したように、CNContactStoreCNMutableContact のインスタンスを使用して新しい連絡先を作成し、作成した連絡先は CNSaveRequest を使用してユーザーの連絡先データベースに書き込まれます:

// Create a new Mutable Contact (read/write)
var contact = new CNMutableContact();

// Set standard properties
contact.GivenName = "John";
contact.FamilyName = "Appleseed";

// Save new contact
var store = new CNContactStore();
var saveRequest = new CNSaveRequest();
saveRequest.AddContact(contact, store.DefaultContainerIdentifier);

NSError error;
if (store.ExecuteSaveRequest(saveRequest, out error)) {
    Console.WriteLine("New contact saved");
} else {
    Console.WriteLine("Save error: {0}", error);
}

CNSaveRequest を使用して、複数の連絡先とグループの変更を 1 つの操作にキャッシュし、それらの変更を CNContactStore にバッチ処理することもできます。

フェッチ操作で取得した変更不可能な連絡先を更新するには、まず変更可能なコピーを要求してから、変更後に連絡先ストアに保存する必要があります。 次に例を示します。

// Get mutable copy of contact
var mutable = contact.MutableCopy() as CNMutableContact;
var newEmail = new CNLabeledValue<NSString>(CNLabelKey.Home, new NSString("john.appleseed@xamarin.com"));

// Append new email
var emails = new NSObject[mutable.EmailAddresses.Length+1];
mutable.EmailAddresses.CopyTo(emails,0);
emails[mutable.EmailAddresses.Length+1] = newEmail;
mutable.EmailAddresses = emails;

// Update contact
var store = new CNContactStore();
var saveRequest = new CNSaveRequest();
saveRequest.UpdateContact(mutable);

NSError error;
if (store.ExecuteSaveRequest(saveRequest, out error)) {
    Console.WriteLine("Contact updated.");
} else {
    Console.WriteLine("Update error: {0}", error);
}

連絡先の変更通知

連絡先が変更されるたびに、連絡先ストアは既定の通知センターに CNContactStoreDidChangeNotification を投稿します。 連絡先をキャッシュした場合、または現在連絡先を表示している場合、連絡先ストア (CNContactStore) からそれらのオブジェクトを更新する必要があります。

コンテナーとグループ

ユーザーの連絡先は、ユーザーのデバイス上にローカルに保存することも、1 つまたは複数のサーバー アカウント (Facebook や Google など) からデバイスに同期することもできます。 連絡先の各プールには独自のコンテナーがあり、特定の連絡先は 1 つのコンテナーにのみ存在できます。

Containers and Groups overview

一部のコンテナーでは、連絡先を 1 つ以上のグループまたはサブグループに配置できます。 この動作は、特定のコンテナーのバッキング ストアに依存します。 たとえば、iCloud にはコンテナーが 1 つしかありませんが、多数のグループを持つことができます (ただし、サブグループは使用できません)。 一方、Microsoft Exchange はグループをサポートしていませんが、複数のコンテナー (Exchange フォルダーごとに 1 つ) を持つことができます。

Overlap within Containers and Groups

ContactsUI フレームワーク

アプリケーションでカスタム UI を表示する必要がない場合は、ContactsUI フレームワークを使用して、Xamarin.iOS アプリで連絡先を表示、編集、選択、作成するための UI 要素を表示できます。

Apple の組み込みコントロールを使用すると、Xamarin.iOS アプリで連絡先をサポートするために作成する必要があるコードの量が減るだけでなく、アプリのユーザーに一貫したインターフェイスを提供できます。

連絡先ピッカー ビュー コントローラー

連絡先ピッカー ビュー コントローラー (CNContactPickerViewController) は、ユーザーが連絡先データベースから連絡先または連絡先プロパティを選択できるようにする標準の連絡先ピッカー ビューを管理します。 ユーザーは (用途に基づき) 1 つまたは複数の連絡先を選択でき、連絡先ピッカー ビュー コントローラーは、ピッカーを表示する前にアクセス許可を求めません。

CNContactPickerViewController クラスを呼び出す前に、ユーザーが選択できるプロパティと、連絡先のプロパティの表示と選択を制御する述語を定義します。

ユーザーによるピッカーの操作に応答するには、CNContactPickerDelegate から継承するクラスのインスタンスを使用します。 次に例を示します。

using System;
using System.Linq;
using UIKit;
using Foundation;
using Contacts;
using ContactsUI;

namespace iOS9Contacts
{
    public class ContactPickerDelegate: CNContactPickerDelegate
    {
        #region Constructors
        public ContactPickerDelegate ()
        {
        }

        public ContactPickerDelegate (IntPtr handle) : base (handle)
        {
        }
        #endregion

        #region Override Methods
        public override void ContactPickerDidCancel (CNContactPickerViewController picker)
        {
            Console.WriteLine ("User canceled picker");

        }

        public override void DidSelectContact (CNContactPickerViewController picker, CNContact contact)
        {
            Console.WriteLine ("Selected: {0}", contact);
        }

        public override void DidSelectContactProperty (CNContactPickerViewController picker, CNContactProperty contactProperty)
        {
            Console.WriteLine ("Selected Property: {0}", contactProperty);
        }
        #endregion
    }
}

ユーザーがデータベース内の連絡先からメール アドレスを選択できるようにするには、次のコードを使用できます:

// Create a new picker
var picker = new CNContactPickerViewController();

// Select property to pick
picker.DisplayedPropertyKeys = new NSString[] {CNContactKey.EmailAddresses};
picker.PredicateForEnablingContact = NSPredicate.FromFormat("emailAddresses.@count > 0");
picker.PredicateForSelectionOfContact = NSPredicate.FromFormat("emailAddresses.@count == 1");

// Respond to selection
picker.Delegate = new ContactPickerDelegate();

// Display picker
PresentViewController(picker,true,null);

連絡先ビュー コントローラー

連絡先ビュー コントローラー (CNContactViewController) クラスは、標準の連絡先ビューをエンド ユーザーに表示するためのコントローラーを提供します。 連絡先ビューには新しい新規、不明、または既存の連絡先を表示できます。適切な静的コンストラクター (FromNewContactFromUnknownContactFromContact) を呼び出して、ビューを表示する前に、型を指定する必要があります。 例:

// Create a new contact view
var view = CNContactViewController.FromContact(contact);

// Display the view
PresentViewController(view, true, null);

まとめ

この記事では、Xamarin.iOS アプリケーションでの連絡先および連絡先 UI フレームワークの操作について詳しく説明しました。 はじめに、Contact フレームワークが提供するさまざまな種類のオブジェクトと、それらを使用して新規連絡先を作成したり、既存の連絡先にアクセスしたりする方法について説明しました。 また、連絡先 UI フレームワークで既存の連絡先を選択し、連絡先情報を表示する方法も解説しました。