iCloudの基本とコードサンプル[クローズ]


86

初心者として、私はiCloudに苦労しています。いくつかのサンプルがありますが、それらは通常非常に詳細です(開発者フォーラムには、iCloudとCoreData用の膨大なサンプルがあります)。リンゴのドキュメントはOKですが、私はまだ大きな画像を見ることができません。ですから、ご容赦ください。これらの質問のいくつかは非常に基本的なものですが、おそらく答えるのは簡単です。

コンテキスト:非常に単純なiCloudアプリを実行しています(以下の完全なサンプルコード)。ユーザーに表示されるUITextViewは1つだけで、ユーザーの入力はtext.txtというファイルに保存されます。

ここに画像の説明を入力してください

txtファイルはクラウドにプッシュされ、すべてのデバイスで利用できるようになります。完全に機能しますが、次のようになります。

主な問題:iCloudを使用していないユーザーはどうですか?

アプリを起動すると(以下のコードを参照)、ユーザーがiCloudを有効にしているかどうかを確認します。iCloudが有効になっている場合は、すべて問題ありません。アプリは先に進み、クラウドでtext.txtを探します。見つかった場合は、それをロードしてユーザーに表示します。text.txtがクラウドで見つからない場合は、新しいtext.txtを作成し、それをユーザーに表示します。

ユーザーがiCloudを有効にしていない場合、何も起こりません。iCloud以外のユーザーが引き続き私のテキストアプリで作業できるようにするにはどうすればよいですか?それとも単にそれらを無視しますか?iCloud以外のユーザーのために別々の関数を書く必要がありますか?つまり、ドキュメントフォルダからtext.txtをロードするだけの関数ですか?

Appleはこう書いている

アプリサンドボックス内の他のすべてのファイルを処理するのと同じ方法で、iCloud内のファイルを処理します。

ただし、私の場合、「通常の」アプリサンドボックスはもうありません。クラウドにあります。または、常に最初にディスクからtext.txtをロードしてから、iCloudで最新のものがあるかどうかを確認しますか?

関連する問題:ファイル構造-サンドボックスとクラウド

おそらく私の主な問題は、iCloudがどのように機能するかについての根本的な誤解です。UIDocumentの新しいインスタンスを作成するときは、2つのメソッドを上書きする必要があります。最初- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outErrorにクラウドからファイルを取得し、次に-(id)contentsForType:(NSString *)typeName error:(NSError **)outErrorファイルをクラウドに取得します。

text.txtのローカルコピーもサンドボックスに保存する個別の関数を組み込む必要がありますか?これはiCloud以外のユーザーでも機能しますか?私がiCloudを理解しているので、text.txtのローカルコピーが自動的に保存されます。したがって、アプリの「古い」サンドボックスに何かを保存する必要はないはずです(つまり、iCloud以前の古いものでした)。現在、私のサンドボックスは完全に空ですが、これが正しいかどうかはわかりません。text.txtの別のコピーをそこに保持する必要がありますか?これは私のデータ構造を乱雑にしているように感じます...クラウドに1つのtext.txtがあり、1つはデバイスのiCloudサンドボックスにあり(オフラインでも機能します)、3つ目はの古き良きサンドボックスにあります私のアプリ...


私のコード:簡単なiCloudサンプルコード

これは、開発者フォーラムとWWDCセッションビデオで見つけた例に大まかに基づいています。私はそれを最小限に取り除いた。私のMVC構造が良いかどうかはわかりません。モデルはAppDelegateにありますが、これは理想的ではありません。それをより良くするためのどんな提案も歓迎します。


編集:私は主な質問を抽出して[ここに]投稿しようとしました。4


概要:

概要概要

クラウドからtext.txtをロードする最も重要なビット:

//  AppDelegate.h
//  iCloudText

#import <UIKit/UIKit.h>

@class ViewController;
@class MyTextDocument;

@interface AppDelegate : UIResponder <UIApplicationDelegate> {
    NSMetadataQuery *_query;
}

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) ViewController *viewController;
@property (strong, nonatomic) MyTextDocument *document;

@end

//  AppDelegate.m
//  iCloudText

#import "AppDelegate.h"
#import "MyTextDocument.h"
#import "ViewController.h"

@implementation AppDelegate

@synthesize window = _window;
@synthesize viewController = _viewController;
@synthesize document = _document;

- (void)dealloc
{
    [_window release];
    [_viewController release];
    [super dealloc];
}

- (void)loadData:(NSMetadataQuery *)query {

    // (4) iCloud: the heart of the load mechanism: if texts was found, open it and put it into _document; if not create it an then put it into _document

    if ([query resultCount] == 1) {
        // found the file in iCloud
        NSMetadataItem *item = [query resultAtIndex:0];
        NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];

        MyTextDocument *doc = [[MyTextDocument alloc] initWithFileURL:url];
        //_document = doc;
        doc.delegate = self.viewController;
        self.viewController.document = doc;

        [doc openWithCompletionHandler:^(BOOL success) {
            if (success) {
                NSLog(@"AppDelegate: existing document opened from iCloud");
            } else {
                NSLog(@"AppDelegate: existing document failed to open from iCloud");
            }
        }];
    } else {
        // Nothing in iCloud: create a container for file and give it URL
        NSLog(@"AppDelegate: ocument not found in iCloud.");

        NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
        NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:@"Documents"] URLByAppendingPathComponent:@"text.txt"];

        MyTextDocument *doc = [[MyTextDocument alloc] initWithFileURL:ubiquitousPackage];
        //_document = doc;
        doc.delegate = self.viewController;
        self.viewController.document = doc;

        [doc saveToURL:[doc fileURL] forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
            NSLog(@"AppDelegate: new document save to iCloud");
            [doc openWithCompletionHandler:^(BOOL success) {
                NSLog(@"AppDelegate: new document opened from iCloud");
            }];
        }];
    }
}

- (void)queryDidFinishGathering:(NSNotification *)notification {

    // (3) if Query is finished, this will send the result (i.e. either it found our text.dat or it didn't) to the next function

    NSMetadataQuery *query = [notification object];
    [query disableUpdates];
    [query stopQuery];

    [self loadData:query];

    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:query];
    _query = nil; // we're done with it
}

-(void)loadDocument {

    // (2) iCloud query: Looks if there exists a file called text.txt in the cloud

    NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
    _query = query;
    //SCOPE
    [query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
    //PREDICATE
    NSPredicate *pred = [NSPredicate predicateWithFormat: @"%K == %@", NSMetadataItemFSNameKey, @"text.txt"];
    [query setPredicate:pred];
    //FINISHED?
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query];
    [query startQuery];

}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSLog(@"AppDelegate: app did finish launching");
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];

    // Override point for customization after application launch.
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        self.viewController = [[[ViewController alloc] initWithNibName:@"ViewController_iPhone" bundle:nil] autorelease];
    } else {
        self.viewController = [[[ViewController alloc] initWithNibName:@"ViewController_iPad" bundle:nil] autorelease];
    }

    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];

    // (1) iCloud: init

    NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
    if (ubiq) {
        NSLog(@"AppDelegate: iCloud access!");
        [self loadDocument];
    } else {
        NSLog(@"AppDelegate: No iCloud access (either you are using simulator or, if you are on your phone, you should check settings");
    }


    return YES;
}

@end

UIDocument

//  MyTextDocument.h
//  iCloudText

#import <Foundation/Foundation.h>
#import "ViewController.h"

@interface MyTextDocument : UIDocument {

    NSString *documentText;
    id delegate;

}

@property (nonatomic, retain) NSString *documentText;
@property (nonatomic, assign) id delegate;

@end

//  MyTextDocument.m
//  iCloudText

#import "MyTextDocument.h"
#import "ViewController.h"

@implementation MyTextDocument

@synthesize documentText = _text;
@synthesize delegate = _delegate;

// ** READING **

- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError
{
    NSLog(@"UIDocument: loadFromContents: state = %d, typeName=%@", self.documentState, typeName);

    if ([contents length] > 0) {
        self.documentText = [[NSString alloc] initWithBytes:[contents bytes] length:[contents length] encoding:NSUTF8StringEncoding];
    }
    else {
        self.documentText = @"";
    }

    NSLog(@"UIDocument: Loaded the following text from the cloud: %@", self.documentText);


    // update textView in delegate...
    if ([_delegate respondsToSelector:@selector(noteDocumentContentsUpdated:)]) {
        [_delegate noteDocumentContentsUpdated:self];
    }

    return YES;

}

// ** WRITING **

-(id)contentsForType:(NSString *)typeName error:(NSError **)outError
{
    if ([self.documentText length] == 0) {
        self.documentText = @"New Note";
    }

    NSLog(@"UIDocument: Will save the following text in the cloud: %@", self.documentText);

    return [NSData dataWithBytes:[self.documentText UTF8String] length:[self.documentText length]];
}
@end

ビューコントローラー

//
//  ViewController.h
//  iCloudText

#import <UIKit/UIKit.h>

@class MyTextDocument;

@interface ViewController : UIViewController <UITextViewDelegate> {

    IBOutlet UITextView *textView;

}

@property (nonatomic, retain) UITextView *textView;
@property (strong, nonatomic) MyTextDocument *document;

-(void)noteDocumentContentsUpdated:(MyTextDocument *)noteDocument;

@end

//  ViewController.m
//  iCloudText

#import "ViewController.h"
#import "MyTextDocument.h"

@implementation ViewController

@synthesize textView = _textView;
@synthesize document = _document;

-(IBAction)dismissKeyboard:(id)sender {

    [_textView resignFirstResponder];

}

-(void)noteDocumentContentsUpdated:(MyTextDocument *)noteDocument
{
    NSLog(@"VC: noteDocumentsUpdated");
    _textView.text = noteDocument.documentText;
}

-(void)textViewDidChange:(UITextView *)theTextView {

     NSLog(@"VC: textViewDidChange");
    _document.documentText = theTextView.text;
    [_document updateChangeCount:UIDocumentChangeDone];

}

4
これをいくつかの質問に分割することをお勧めします。ここにはいくつかの異なる質問が埋め込まれているのがわかりますが、ここにあるテキストの壁からそれらを見つけるのは困難です。この質問を、iCloudが有効になっていない人のために何をすべきかを尋ねるだけに戻し、他の人(サンプルコードの関連部分のみを含む)を別々の質問に分けます。良い質問ですが、分割する必要があると思います。
BradLarson

@BradLarsonコメントありがとうございます。質問が少し混乱している場合は申し訳ありませんが、(私が示したように)主な質問はアプリのサンドボックスとiCloudのサンドボックスの問題だと思います。何が起こっているのかを知るにはコンテキスト全体が不可欠だと思ったので、完全なコード(iCloudコードの最短の例です)を提供しました...しかし、別の質問を開いて、この質問にリンクして全体像を把握します。
n.evermind 2011年

@BradLarson OK、私はここに新しい質問を開い:stackoverflow.com/questions/7798555/...
n.evermind


...これは実際に私はiCloudの上で見てきたことが、より建設的支柱の一つで閉鎖されるべきではない
アンドリュー・スミス

回答:


22

ドキュメントを読み直したところ、一般的なアプローチが間違っているようです。最初にサンドボックスにファイルを作成してから、それをクラウドに移動する必要があります。言い換えれば、Appleは、常に同じファイルの3つのバージョンを用意することを提案しているようです。1つはアプリのディレクトリ、1つはデバイスのiCloudデーモンディレクトリ(オフラインでもアクセス可能)、もう1つはクラウド:

アプリは、ローカルファイルとディレクトリに対して行うのと同じテクノロジーを使用してiCloud内のファイルとディレクトリを管理します。iCloudのファイルとディレクトリはまだファイルとディレクトリにすぎません。それらを開いたり、作成したり、移動したり、コピーしたり、読み取りと書き込みを行ったり、削除したり、その他の操作を行うことができます。ローカルファイルとディレクトリとiCloudファイルとディレクトリの唯一の違いは、それらにアクセスするために使用するURLです。URLがアプリのサンドボックスに相対的である代わりに、iCloudファイルとディレクトリのURLは対応するiCloudコンテナディレクトリに相対的です。

ファイルまたはディレクトリをiCloudに移動するには:

アプリのサンドボックスにファイルまたはディレクトリをローカルに作成します。使用中、ファイルまたはディレクトリは、UIDocumentオブジェクトなどのファイルプレゼンターによって管理される必要があります。

URLForUbiquityContainerIdentifier:メソッドを使用して、アイテムを保存するiCloudコンテナディレクトリのURLを取得します。コンテナディレクトリのURLを使用して、iCloudでのアイテムの場所を指定する新しいURLを作成します。NSFileManagerのsetUbiquitous:itemAtURL:destinationURL:error:メソッドを呼び出して、アイテムをiCloudに移動します。アプリのメインスレッドからこのメソッドを呼び出さないでください。これを行うと、メインスレッドが長期間ブロックされたり、アプリ自体のファイルプレゼンターの1つでデッドロックが発生したりする可能性があります。ファイルまたはディレクトリをiCloudに移動すると、システムはそのアイテムをアプリのサンドボックスからプライベートローカルディレクトリにコピーして、iCloudデーモンで監視できるようにします。ファイルがサンドボックスに存在しなくなっても、アプリは引き続きそのファイルに完全にアクセスできます。ファイルのコピーは現在のデバイスに対してローカルのままですが、ファイルは他のデバイスに配布できるようにiCloudにも送信されます。iCloudデーモンは、ローカルコピーが同じであることを確認するすべての作業を処理します。したがって、アプリの観点からは、ファイルはiCloudにあります。

iCloudのファイルまたはディレクトリに加えるすべての変更は、ファイルコーディネーターオブジェクトを使用して行う必要があります。これらの変更には、アイテムの移動、削除、コピー、または名前の変更が含まれます。ファイルコーディネーターは、iCloudデーモンがファイルまたはディレクトリを同時に変更しないようにし、他の関係者に変更が通知されるようにします。

ただし、setUbiquitousに関するドキュメントをもう少し深く掘り下げると、次のことがわかります。

この方法を使用して、ファイルを現在の場所からiCloudに移動します。アプリケーションのサンドボックスにあるファイルの場合、これには、サンドボックスディレクトリからファイルを物理的に削除することが含まれます。(システムは、アプリケーションのサンドボックス権限を拡張して、iCloudに移動するファイルへのアクセスを許可します。)この方法を使用して、ファイルをiCloudからローカルディレクトリに移動することもできます。

したがって、これは、ファイル/ディレクトリがローカルサンドボックスから削除されてクラウドに移動されることを意味しているように見えます。


1
URLリンクが壊れている...
NGB

5

私はあなたの例を使ってきましたが、iCloudの基本を理解するのに役立つのが好きです。今、私が知る限り、iCloudを使用しているかどうかにかかわらず、ローカルに保存されたコンテンツでアプリの既存のユーザーをサポートする必要がある自分のアプリについてのあなたの質問に取り組んでいます:

ケース:

  1. 新しいユーザー
    • icloudがあります-icloudでドキュメントを作成します
    • icloudなし-ローカルでドキュメントを作成
  2. 既存のユーザー
    • icloudがあります
      • 追加されました-ローカルドキュメントをicloudに移行します
      • 追加されただけではありません-ドキュメントをicloudに開く/保存する
    • iCloudはありません
      • 削除されたばかり-以前のicloudドキュメントをローカルに移行する
      • 削除されただけではありません-ドキュメントを開いてローカルに保存します

誰かがiCloudを削除した場合、ユビキタスURLへの呼び出しはnilを返しませんか?その場合、ドキュメントをローカルストレージに移行するにはどうすればよいですか?今のところユーザー設定を作成しますが、少し回避策のようです。

ここで明らかな何かが欠けているような気がするので、誰かがそれを見ることができるなら、チャイムを鳴らしてください。


これらのケースを処理するクラスがあるかどうか疑問に思っているので、それを使用するだけで、どこに保存するかを心配する必要はありません。
Earnshavian 2011年

developer.apple.com/library/ios/#documentation/DataManagement/…を見てください。これは、ローカルサンドボックスまたはクラウドのどちらに何かを配置する必要があるかを判断するためのサンプルコードを提供します。
n.evermind 2011年

それをありがとう。私はそのドキュメントを見たことがありますが、iCloudミッションの初期に、それが提供するコードを忘れていました。ローカルとリモートをサポートするようにサンプルを適応させようとします。ユビキタスURLが失われたため、iCloudを無効にしたユーザーをどのように処理するかはまだわかりませんが、クラックが発生して更新を共有します。
Earnshavian 2011年

1
したがって、ある意味で、クラウドのURLとローカルサンドボックスのPATHを使用する必要があるのは少しばかげています。iCloudが私たちのためにすべてを処理できればいいのですが...しかし、このように、私たちは基本的に、開くファイルごとに2つの異なるメソッドをコーディングする必要があります。
n.evermind 2011年

あなたの投稿を読み直しました。NSUserDefaultsにユーザーの設定(つまり、ユーザーがiCloudを使用したい/使用したくない)を保存しています。これはAppleも提案していることです。私はいつもiCloudにアクセスできるかどうかをチェックしています。アクセスできない場合は、ユーザーにオンにするように指示しますが、ユーザーがアプリに使用したくないことを明示的に通知していない場合に限ります。そうでなければ、iCloudを使いたくない人にとっては迷惑になります。iCloudが有効になっているかどうかを判断したら、ユビキタスURLルートをたどってUIDocumentを使用するか、古き良き時代のようにサンドボックスからファイルを開くだけです。
n.evermind 2011年

4

iOS 5.0より前のデバイス間でユーザーがテキストを共有できるようにしたい場合は、iCloudの前に全員がしなければならなかったことを実行し、情報を自分のサーバーに移動する必要があります。

本当に必要なのは、アプリがテキストファイルを保存し、それらをユーザーアカウントに関連付けることができるサーバーです。

アカウントを作成するにはユーザーが必要であり、1つのデバイス上の新しい情報を独自の「クラウド」に移動するプロセスを自分で管理する必要があります。

ユーザーは他のデバイスで同じアカウントに登録するため、別のデバイスが自分のクラウドにデータを移動したことを検出し、現在のデバイスを新しい情報で更新する必要があります。

もちろん、iOS 5.0デバイスの場合は、自分のクラウドでiOS 5.0より前のデバイスの変更されたファイルを検出し、iCloudと通信できるようにする必要があります。


ありがとう。つまり、iOS 5より前のデバイスをサポートしたくない場合は、UIDocumentを使用して、アプリのサンドボックスにあるdocディレクトリの内容を忘れてしまいます。
n.evermind 2011年

私の知る限りでは、UIDocumentがiCloudを仲介するのに役立つドキュメントがサンドボックスに残っていますが、いつアクセスできるかが通知されます...まだ取得中です自分でこのようなものをつかむために!
Jonathan Watmough 2011年

3

iOS5 / notIOS5の問題ほどiCloud / notICloudの問題に苦しんでいるようには見えません。

デプロイメントターゲットがiOS5の場合は、常にUIDocument構造を使用してください。ユビキタスな場合、NSMetaDataQueryはクラウドでそれを見つけます。そうでない場合は、デバイス上で検出されます。

一方、アプリへの5.0より前のアクセスを提供する場合は、実行中のiOSが5.0以降であるかどうかを条件付きで確認する必要があります。その場合はUIDocumentを使用します。そうでない場合は、古い方法でデータを読み書きします。

私のアプローチは、iOS5をチェックする条件付きsaveDataメソッドを作成することでした。存在する場合は、変更カウントを更新します(または元に戻すマネージャーを使用します)。あなたの場合、textViewDidChangeはこのメソッドを呼び出します。そうでない場合は、古い方法でディスクに保存されます。ロードすると、逆のことが起こります。


1

「アプリのサンドボックス内の他のすべてのファイルを処理するのと同じ方法でiCloud内のファイルを処理する」に戸惑います。これは、KeynoteやNumbersのように大量のファイルを保持しているものにも当てはまり、iCloudを使用している場合は、魔法のように同期を開始します。

ただし、iCloudのような機能に依存するものを構築しています。あなたのアプリはiCloudが意図したとおりに機能するために存在することに依存しているので、その声明を保持することはできません。アプリを閉じて「これが機能するようにiCloudをセットアップしてください」と言うか、iCloudのような機能(自分または他の人の機能)を複製して、いつでも使用できるようにする必要があります。


ありがとう。だから、私はiCloud専用アプリを使うのか、iCloud機能を利用する人のためにある種のハイブリッドを使うのかを選ばなければならないと思う。iCloudはとても複雑なので、私はiCloud専用アプリを選ぶ傾向があります。ありがとう。
n.evermind 2011年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.