Xamarin.iOS での iCloud の使用

iOS 5 の iCloud ストレージ API を使用すると、アプリケーションはユーザードキュメントとアプリケーション固有のデータを一元的な場所に保存し、すべてのユーザーのデバイスからそれらのアイテムにアクセスできます。

使用可能なストレージには、次の 4 種類があります。

  • キー値ストレージ - ユーザーの他のデバイスで少量のデータをアプリケーションと共有します。

  • UIDocument ストレージ - UIDocument のサブクラスを使用して、ユーザーの iCloud アカウントにドキュメントやその他のデータを格納します。

  • CoreData - SQLite データベース ストレージ。

  • 個々のファイルとディレクトリ - ファイル システムで多数の異なるファイルを直接管理します。

このドキュメントでは、最初の 2 種類 (キーと値のペアと UIDocument サブクラス) と、Xamarin.iOS でこれらの機能を使用する方法について説明します。

重要

Apple からは、開発者が欧州連合の一般データ保護規則 (GDPR) を適切に処理するためのツールが提供されています。

要件

  • Xamarin.iOS の最新の安定バージョン
  • Xcode 10
  • Visual Studio for Mac または Visual Studio 2019。

iCloud 開発の準備

アプリケーションは、Apple プロビジョニング ポータルとプロジェクト自体の両方で iCloud を使用するように構成する必要があります。 iCloud 用に開発する (またはサンプルを試す) 前に、次の手順に従います。

iCloud にアクセスするようにアプリケーションを正しく構成するには:

  • TeamID を見つける - developer.apple.comログインし、メンバー センター>のアカウント>開発者アカウントの概要にアクセスして、チーム ID (または単一の開発者の個人 ID) を取得します。 これは 10 文字の文字列 ( たとえばA93A5CM278 ) になります。これは"コンテナー識別子" の一部を形成します。

  • 新しいアプリ ID を作成する - アプリ ID を作成するには、Device Provisioning ガイドの「ストア テクノロジのプロビジョニング」セクションに記載されている手順に従い、許可されたサービスとして iCloud をチェックしてください。

Check iCloud as an allowed service

  • 新しいプロビジョニング プロファイルを作成する - プロビジョニング プロファイルを作成するには、Device Provisioning ガイドに記載されている手順に従います。

  • Entitlements.plist にコンテナー識別子を追加する - コンテナー識別子の形式は .TeamID.BundleID 詳細については、「権利の 操作」ガイドを 参照してください。

  • プロジェクトのプロパティを構成する - Info.plist ファイルで、バンドル ID がアプリ ID の作成時に設定されたバンドル ID一致することを確認します。iOS バンドル署名では、iCloud App Service と選択されたカスタムエンタイトルメント ファイルを含むアプリ ID を含むプロビジョニング プロファイル使用されます。 これはすべて、プロジェクトの [プロパティ] ウィンドウの下の Visual Studio で実行できます。

  • デバイスで iCloud を有効にする - 設定 > iCloud移動し、デバイスがログインしていることを確認します。 [ドキュメントとデータ] オプションを選択してオンにします。

  • iCloud をテストするにはデバイスを使用する必要があります。シミュレーターでは動作しません。 実際には、iCloudが動作しているのを見るために、2つ以上のデバイスがすべて同じApple IDでサインインしている必要があります。

キー値ストレージ

キー値ストレージは、書籍や雑誌で最後に表示したページなど、ユーザーがデバイス間で保持する必要がある少量のデータを対象としています。 キー値ストレージは、データのバックアップには使用しないでください。

キー値ストレージを使用する場合は、いくつかの制限事項に注意する必要があります。

  • 最大キー サイズ - キー名は 64 バイトを超えることはできません。

  • 最大値のサイズ - 1 つの値に 64 KB を超える値を格納することはできません。

  • アプリ の最大キー値ストア サイズ - アプリケーションは、合計で最大 64 KB のキー値データのみを格納できます。 その制限を超えてキーを設定しようとすると失敗し、前の値が保持されます。

  • データ型 - 文字列、数値、ブール値などの基本型のみを格納できます。

iCloudKeyValue例では、その動作を示します。 このサンプル コードでは、デバイスごとに名前付きのキーを作成します。このキーを 1 つのデバイスに設定し、値が他のデバイスに伝達されるのを見ることができます。 また、任意のデバイスで編集できる "共有" というキーも作成されます。多数のデバイスで一度に編集すると、iCloud は (変更時にタイムスタンプを使用して) どの値が "優先" されるかを決定し、伝達されます。

このスクリーンショットは、使用中のサンプルを示しています。 変更通知が iCloud から受信されると、画面下部のスクロール テキスト ビューに出力され、入力フィールドで更新されます。

The flow of messages between devices

データの設定と取得

このコードは、文字列値を設定する方法を示しています。

var store = NSUbiquitousKeyValueStore.DefaultStore;
store.SetString("testkey", "VALUE IN THE CLOUD");  // key and value
store.Synchronize();

Synchronize を呼び出すと、値がローカル ディスク ストレージにのみ保持されます。 iCloud への同期はバックグラウンドで行われ、アプリケーション コードで "強制" することはできません。 ネットワーク接続が良好な場合、同期は 5 秒以内に行われることが多くなりますが、ネットワークの状態が悪い (または切断された) 場合は、更新にはるかに長い時間がかかる可能性があります。

次のコードを使用して値を取得できます。

var store = NSUbiquitousKeyValueStore.DefaultStore;
display.Text = store.GetString("testkey");

値はローカル データ ストアから取得されます。このメソッドは、iCloud サーバーに接続して "最新" の値を取得しようとしません。 iCloud は、独自のスケジュールに従ってローカル データ ストアを更新します。

データの削除

キーと値のペアを完全に削除するには、次のように Remove メソッドを使用します。

var store = NSUbiquitousKeyValueStore.DefaultStore;
store.Remove("testkey");
store.Synchronize();

変更の監視

アプリケーションは、オブザーバーを追加することによって、iCloud によって値が変更されたときに通知を NSNotificationCenter.DefaultCenter受け取ることもできます。 KeyValueViewController.csViewWillAppear メソッドの次のコードは、これらの通知をリッスンし、変更されたキーの一覧を作成する方法を示しています。

keyValueNotification =
NSNotificationCenter.DefaultCenter.AddObserver (
    NSUbiquitousKeyValueStore.DidChangeExternallyNotification, notification => {
    Console.WriteLine ("Cloud notification received");
    NSDictionary userInfo = notification.UserInfo;

    var reasonNumber = (NSNumber)userInfo.ObjectForKey (NSUbiquitousKeyValueStore.ChangeReasonKey);
    nint reason = reasonNumber.NIntValue;

    var changedKeys = (NSArray)userInfo.ObjectForKey (NSUbiquitousKeyValueStore.ChangedKeysKey);
    var changedKeysList = new List<string> ();
    for (uint i = 0; i < changedKeys.Count; i++) {
        var key = changedKeys.GetItem<NSString> (i); // resolve key to a string
        changedKeysList.Add (key);
    }
    // now do something with the list...
});

その後、コードは、変更されたキーの一覧を使用して何らかのアクションを実行できます。たとえば、変更されたキーのローカル コピーの更新や、新しい値を使用した UI の更新などです。

変更の理由としては、ServerChange (0)、InitialSyncChange (1)、QuotaViolationChange (2) などがあります。 理由にアクセスし、必要に応じて異なる処理を実行できます (たとえば、QuotaViolationChange の結果として一部のキーを削除する必要がある場合など)。

ドキュメント ストレージ

iCloud Document Storage は、アプリ (およびユーザー) にとって重要なデータを管理するように設計されています。 アプリで実行する必要があるファイルやその他のデータを管理すると同時に、すべてのユーザーのデバイスで iCloud ベースのバックアップと共有機能を提供できます。

この図は、すべてがどのように一緒に収まるかを示しています。 各デバイスにはローカル ストレージ (UbiquityContainer) にデータが保存されており、オペレーティング システムの iCloud デーモンがクラウドでのデータの送受信を処理します。 同時アクセスを防止するには、UbiquityContainer へのすべてのファイル アクセスを FilePresenter/FileCoordinator 経由で行う必要があります。 このクラスは UIDocument それらを実装します。この例では、UIDocument の使用方法を示します。

The document storage overview

iCloudUIDoc の例では、1 つのテキスト フィールドを含む単純な UIDocument サブクラスを実装しています。 テキストが a UITextView でレンダリングされ、編集内容が iCloud によって他のデバイスに伝達され、通知メッセージが赤で表示されます。 サンプル コードでは、競合解決などのより高度な iCloud 機能は扱われません。

このスクリーンショットは、サンプル アプリケーションを示しています。テキストを変更して UpdateChangeCount キーを押すと、ドキュメントは iCloud 経由で他のデバイスに同期されます。

This screenshot shows the sample application after changing the text and pressing UpdateChangeCount

iCloudUIDoc サンプルには、次の 5 つの部分があります。

  1. UbiquityContainer へのアクセス - iCloud が有効になっているかどうか、および有効になっている場合は、アプリケーションの iCloud ストレージ領域へのパスを決定します。

  2. UIDocument サブクラス の作成 - iCloud ストレージとモデル オブジェクト間の中間クラスを作成します。

  3. iCloud ドキュメント を検索して開く - iCloud ドキュメントを使用 NSFileManager して NSPredicate 検索し、開きます。

  4. iCloud ドキュメント の表示 - UI コントロールと対話できるように、プロパティ UIDocument を公開します。

  5. iCloud ドキュメント を保存する - UI で行われた変更がディスクと iCloud に保持されていることを確認します。

すべての iCloud 操作は非同期的に実行 (または実行する必要があります) して、何かが発生するのを待っている間にブロックしないようにします。 このサンプルでは、次の 3 つの異なる方法でこれを実現できます。

スレッド - 最初の呼び出しGetUrlForUbiquityContainerではAppDelegate.FinishedLaunching、メインスレッドをブロックしないように別のスレッドで実行されます。

NotificationCenter - 完了などの NSMetadataQuery.StartQuery 非同期操作時の通知の登録。

完了ハンドラー - 次のような UIDocument.Open非同期操作の完了時に実行するメソッドを渡します。

UbiquityContainer へのアクセス

iCloud Document Storage を使用する最初の手順は、iCloud が有効になっているかどうかと、それが "ユビキティ コンテナー" (iCloud 対応ファイルがデバイスに格納されているディレクトリ) の場所を判断することです。

このコードはサンプルの AppDelegate.FinishedLaunching メソッドにあります。

// GetUrlForUbiquityContainer is blocking, Apple recommends background thread or your UI will freeze
ThreadPool.QueueUserWorkItem (_ => {
    CheckingForiCloud = true;
    Console.WriteLine ("Checking for iCloud");
    var uburl = NSFileManager.DefaultManager.GetUrlForUbiquityContainer (null);
    // OR instead of null you can specify "TEAMID.com.your-company.ApplicationName"

    if (uburl == null) {
        HasiCloud = false;
        Console.WriteLine ("Can't find iCloud container, check your provisioning profile and entitlements");

        InvokeOnMainThread (() => {
            var alertController = UIAlertController.Create ("No \uE049 available",
            "Check your Entitlements.plist, BundleId, TeamId and Provisioning Profile!", UIAlertControllerStyle.Alert);
            alertController.AddAction (UIAlertAction.Create ("OK", UIAlertActionStyle.Destructive, null));
            viewController.PresentViewController (alertController, false, null);
        });
    } else { // iCloud enabled, store the NSURL for later use
        HasiCloud = true;
        iCloudUrl = uburl;
        Console.WriteLine ("yyy Yes iCloud! {0}", uburl.AbsoluteUrl);
    }
    CheckingForiCloud = false;
});

サンプルではそうしませんが、アプリがフォアグラウンドに来るたびに、Apple は GetUrlForUbiquityContainer を呼び出すことをお勧めします。

UIDocument サブクラスの作成

すべての iCloud ファイルとディレクトリ (つまり、UbiquityContainer ディレクトリに格納されているもの) は、NSFileManager メソッドを使用して管理し、NSFilePresenter プロトコルを実装し、NSFileCoordinator 経由で書き込む必要があります。 そのすべてを行う最も簡単な方法は、自分で記述するのではなく、それをすべて行うUIDocumentをサブクラス化することです。

iCloud を操作するには、UIDocument サブクラスに実装する必要があるメソッドは 2 つだけです。

  • LoadFromContents - モデル クラス/es にアンパックするために、ファイルの内容の NSData を渡します。

  • ContentsForType - ディスク (およびクラウド) に保存するモデル クラス/es の NSData 表現を指定するように要求します。

iCloudUIDoc\MonkeyDocument.cs のこのサンプル コードは、UIDocument を実装する方法を示しています。

public class MonkeyDocument : UIDocument
{
    // the 'model', just a chunk of text in this case; must easily convert to NSData
    NSString dataModel;
    // model is wrapped in a nice .NET-friendly property
    public string DocumentString {
        get {
            return dataModel.ToString ();
        }
        set {
            dataModel = new NSString (value);
        }
    }
    public MonkeyDocument (NSUrl url) : base (url)
    {
        DocumentString = "(default text)";
    }
    // contents supplied by iCloud to display, update local model and display (via notification)
    public override bool LoadFromContents (NSObject contents, string typeName, out NSError outError)
    {
        outError = null;

        Console.WriteLine ("LoadFromContents({0})", typeName);

        if (contents != null)
            dataModel = NSString.FromData ((NSData)contents, NSStringEncoding.UTF8);

        // LoadFromContents called when an update occurs
        NSNotificationCenter.DefaultCenter.PostNotificationName ("monkeyDocumentModified", this);
        return true;
    }
    // return contents for iCloud to save (from the local model)
    public override NSObject ContentsForType (string typeName, out NSError outError)
    {
        outError = null;

        Console.WriteLine ("ContentsForType({0})", typeName);
        Console.WriteLine ("DocumentText:{0}",dataModel);

        NSData docData = dataModel.Encode (NSStringEncoding.UTF8);
        return docData;
    }
}

この場合のデータ モデルは非常に単純で、1 つのテキスト フィールドです。 データ モデルは、Xml ドキュメントやバイナリ データなど、必要に応じて複雑になる場合があります。 UIDocument 実装の主な役割は、モデル クラスと、ディスクに保存/読み込み可能な NSData 表現の間で変換することです。

iCloud ドキュメントの検索と開く

サンプル アプリでは、test.txt という 1 つのファイルのみを処理するため、AppDelegate.cs のコードによって作成NSPredicateされNSMetadataQuery、そのファイル名が具体的に検索されます。 非同期 NSMetadataQuery 的に実行され、完了すると通知が送信されます。 DidFinishGathering は通知オブザーバーによって呼び出され、クエリを停止し、LoadDocument を呼び出します。LoadDocument は、完了ハンドラーを使用してメソッドを使用 UIDocument.Open してファイルを読み込んで表示 MonkeyDocumentViewControllerします。

string monkeyDocFilename = "test.txt";
void FindDocument ()
{
    Console.WriteLine ("FindDocument");
    query = new NSMetadataQuery {
        SearchScopes = new NSObject [] { NSMetadataQuery.UbiquitousDocumentsScope }
    };

    var pred = NSPredicate.FromFormat ("%K == %@", new NSObject[] {
        NSMetadataQuery.ItemFSNameKey, new NSString (MonkeyDocFilename)
    });

    Console.WriteLine ("Predicate:{0}", pred.PredicateFormat);
    query.Predicate = pred;

    NSNotificationCenter.DefaultCenter.AddObserver (
        this,
        new Selector ("queryDidFinishGathering:"),
        NSMetadataQuery.DidFinishGatheringNotification,
        query
    );

    query.StartQuery ();
}

[Export ("queryDidFinishGathering:")]
void DidFinishGathering (NSNotification notification)
{
    Console.WriteLine ("DidFinishGathering");
    var metadataQuery = (NSMetadataQuery)notification.Object;
    metadataQuery.DisableUpdates ();
    metadataQuery.StopQuery ();

    NSNotificationCenter.DefaultCenter.RemoveObserver (this, NSMetadataQuery.DidFinishGatheringNotification, metadataQuery);
    LoadDocument (metadataQuery);
}

void LoadDocument (NSMetadataQuery metadataQuery)
{
    Console.WriteLine ("LoadDocument");

    if (metadataQuery.ResultCount == 1) {
        var item = (NSMetadataItem)metadataQuery.ResultAtIndex (0);
        var url = (NSUrl)item.ValueForAttribute (NSMetadataQuery.ItemURLKey);
        doc = new MonkeyDocument (url);

        doc.Open (success => {
            if (success) {
                Console.WriteLine ("iCloud document opened");
                Console.WriteLine (" -- {0}", doc.DocumentString);
                viewController.DisplayDocument (doc);
            } else {
                Console.WriteLine ("failed to open iCloud document");
            }
        });
    } // TODO: if no document, we need to create one
}

iCloud ドキュメントの表示

UIDocument の表示は、他のモデル クラスと異なってはいけません。プロパティは UI コントロールに表示され、ユーザーが編集した後、モデルに書き戻される可能性があります。

iCloudUIDoc\MonkeyDocumentViewController.cs の例では、MonkeyDocument テキストUITextViewが . ViewDidLoad は、メソッドで送信された通知を MonkeyDocument.LoadFromContents リッスンします。 LoadFromContents は、iCloud がファイルの新しいデータを持っている場合に呼び出されるため、通知はドキュメントが更新されたことを示します。

NSNotificationCenter.DefaultCenter.AddObserver (this,
    new Selector ("dataReloaded:"),
    new NSString ("monkeyDocumentModified"),
    null
);

サンプル コード通知ハンドラーは、競合の検出や解決を行わずに、UI を更新するメソッドを呼び出します。

[Export ("dataReloaded:")]
void DataReloaded (NSNotification notification)
{
    doc = (MonkeyDocument)notification.Object;
    // we just overwrite whatever was being typed, no conflict resolution for now
    docText.Text = doc.DocumentString;
}

iCloud ドキュメントの保存

iCloud に UIDocument を追加するには、直接呼び出すか UIDocument.Save (新しいドキュメントの場合のみ)、または < a0/> を使用して NSFileManager.DefaultManager.SetUbiquitious既存のファイルを移動します。 このコード例では、このコードを使用して、ユビキティ コンテナーに新しいドキュメントを直接作成します (ここには、操作用と Open 用の Save 2 つの完了ハンドラーがあります)。

var docsFolder = Path.Combine (iCloudUrl.Path, "Documents"); // NOTE: Documents folder is user-accessible in Settings
var docPath = Path.Combine (docsFolder, MonkeyDocFilename);
var ubiq = new NSUrl (docPath, false);
var monkeyDoc = new MonkeyDocument (ubiq);
monkeyDoc.Save (monkeyDoc.FileUrl, UIDocumentSaveOperation.ForCreating, saveSuccess => {
Console.WriteLine ("Save completion:" + saveSuccess);
if (saveSuccess) {
    monkeyDoc.Open (openSuccess => {
        Console.WriteLine ("Open completion:" + openSuccess);
        if (openSuccess) {
            Console.WriteLine ("new document for iCloud");
            Console.WriteLine (" == " + monkeyDoc.DocumentString);
            viewController.DisplayDocument (monkeyDoc);
        } else {
            Console.WriteLine ("couldn't open");
        }
    });
} else {
    Console.WriteLine ("couldn't save");
}

ドキュメントに対する後続の変更は直接 "保存" されません。代わりに、ドキュメントが変更UpdateChangeCountされたことが通知UIDocumentされ、ディスクへの保存操作が自動的にスケジュールされます。

doc.UpdateChangeCount (UIDocumentChangeKind.Done);

iCloud ドキュメントの管理

ユーザーは、設定を使用して、アプリケーションの外部にある "ユビキティ コンテナー" のドキュメント ディレクトリ内の iCloud ドキュメントを管理できます。ファイル一覧を表示し、スワイプして削除できます。 アプリケーション コードは、ユーザーによってドキュメントが削除される状況を処理できる必要があります。 内部アプリケーション データをドキュメント ディレクトリに格納しないでください。

Managing iCloud Documents workflow

また、ユーザーは、デバイスから iCloud 対応アプリケーションを削除しようとしたときに、そのアプリケーションに関連する iCloud ドキュメントの状態を通知するために、異なる警告を受け取ります。

Screenshot shows a warning for Document Updates Pending.

Screenshot shows a warning for Delete i Cloud.

iCloud バックアップ

iCloud へのバックアップは開発者が直接アクセスする機能ではありませんが、アプリケーションの設計方法がユーザー エクスペリエンスに影響を与える可能性があります。 Apple は、開発者が iOS アプリケーションで従う iOS Data Storage ガイドラインを提供します。

最も重要な考慮事項は、ユーザーが生成しない大きなファイル (たとえば、問題ごとに 100 MB のコンテンツを格納する雑誌リーダー アプリケーション) をアプリに格納するかどうかです。 Apple は、この種のデータを iCloud にバックアップし、ユーザーの iCloud クォータを不必要に埋める場所に保存しないことを好みます。

このような大量のデータを格納するアプリケーションは、バックアップされていないユーザー ディレクトリのいずれかに格納する必要があります (例:キャッシュまたは tmp) を使用するか、バックアップ操作中に iCloud によって無視されるように、これらのファイルにフラグを適用するために使用 NSFileManager.SetSkipBackupAttribute します。

まとめ

この記事では、iOS 5 に含まれる新しい iCloud 機能について説明しました。 iCloud を使用するようにプロジェクトを構成するために必要な手順を調べた後、iCloud 機能を実装する方法の例を示しました。

キー値ストレージの例では、nSUserPreferences の格納方法と同様に、iCloud を使用して少量のデータを格納する方法を示しました。 UIDocument の例では、iCloud を介して複数のデバイス間で、より複雑なデータを格納および同期する方法を示しました。

最後に、iCloud Backupの追加がアプリケーションの設計にどのように影響するかを簡単に説明しました。