86

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

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

ここに画像の説明を入力

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

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

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

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

アップルは次のように書いています

アプリのサンドボックス内の他のすべてのファイルを扱うのと同じように、iCloud 内のファイルを扱います。

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

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

おそらく私の主な問題は、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があり、デバイスのiCloudサンドボックスに1つ(オフラインでも機能します)、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

UI ドキュメント

//  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

5 に答える 5

22

ドキュメントを読み直したところ、私の一般的なアプローチが間違っているようです。最初にサンドボックスでファイルを作成してから、クラウドに移動する必要があります。言い換えれば、Apple は、常に同じファイルの 3 つのバージョンを保持する必要があることを示唆しているようです。クラウド:

アプリは、ローカルのファイルやディレクトリに対して行うのと同じテクノロジを使用して、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 から移動し、ローカル ディレクトリに戻すこともできます。

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

于 2011-10-24T08:46:17.360 に答える
5

I've been using your example and I like it for helping me grasp the basics of iCloud. Now I'm wrangling with your question for my own app which has to support existing users of the app with locally stored content who may or may not be using iCloud creating these cases as far as I can tell:

Cases:

  1. New user
    • has icloud - create documents in icloud
    • no icloud - create documents locally
  2. Existing user
    • has icloud
      • just added - migrate local docs to icloud
      • not just added - open/save docs to icloud
    • no icloud
      • just removed - migrate former icloud docs to local
      • not just removed - open/save docs to local

If someone removes iCloud - wouldn't the calls to ubiquitous URL return nil? If that's the case how do I migrate the docs back to local storage? I'll create a user pref for now but seems a bit of a workaround.

I feel like I'm missing something obvious here so if anyone can see it, please chime in.

于 2011-10-25T18:08:09.663 に答える
4

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

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

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

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

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

于 2011-10-19T21:47:17.023 に答える
3

iOS5/notIOS5 の問題ほど iCloud/notICloud の問題に悩まされているようには見えません。

展開ターゲットが iOS5 の場合は、常に UIDocument 構造を使用してください。それがどこにでもある場合、NSMetaDataQuery はクラウドでそれを見つけます。そうでない場合は、デバイス上で見つかります。

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

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

于 2011-10-20T15:53:07.830 に答える
1

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

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

于 2011-10-24T09:09:58.893 に答える