web-dev-qa-db-ja.com

iCloudの基本とコードサンプル

初心者として、私はiCloudに苦労しています。いくつかのサンプルがありますが、通常は非常に詳細です(開発者フォーラムには、iCloudとCoreDataの1つがあります)。 Apple docs は問題ありませんが、全体像はまだ見えません。したがって、これらの質問のいくつかは非常に基本的ですが、答えは簡単かもしれません。

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

enter image description here

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の別のコピーをそこに保存する必要がありますか?これは、データ構造を混乱させているように感じます...クラウドにtext.txtが1つ、デバイスのiCloudサンドボックス(オフラインでも動作します)に1つ、そして古き良きサンドボックスに3つ目があります私のアプリ...


マイコード:シンプルなiCloudサンプルコード

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


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


概要:

Overview

クラウドから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];

}
83
n.evermind

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

アプリは、ローカルファイルとディレクトリに対して行うのと同じテクノロジーを使用して、iCloudのファイルとディレクトリを管理します。iCloudのファイルとディレクトリは、まだファイルとディレクトリです。それらを開き、作成し、移動し、コピーできますローカルファイルとディレクトリとiCloudファイルとディレクトリの唯一の違いは、それらにアクセスするために使用する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からローカルディレクトリに移動することもできます。

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

21
n.evermind

私はあなたの例を使用してきましたが、iCloudの基本を理解するのに役立っています。私の知る限り、ローカルに保存されたコンテンツを持つアプリの既存のユーザーがiCloudを使用しているかどうかにかかわらず、これらのケースを作成する可能性があります。

事例:

  1. 新しいユーザー
    • icloudを使用-icloudでドキュメントを作成
    • no icloud-ドキュメントをローカルで作成
  2. 既存のユーザー
    • icloud を持っています
      • 追加した-ローカルドキュメントをicloudに移行する
      • 追加するだけでなく、ドキュメントをicloudで開く/保存する
    • icloudなし
      • 削除されました-以前のicloudドキュメントをローカルに移行します
      • 削除するだけでなく、ドキュメントをローカルに開いたり保存したりします

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

ここで明らかな何かを見逃しているような気がするので、誰でもそれを見ることができたら、チャイムをお願いします。

5
earnshavian

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

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

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

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

明らかに、iOS 5.0デバイスの場合は、おそらく自分のクラウドでiOS 5.0より前のデバイスの変更されたファイルを検出し、iCloudと通信することもできます。

4

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

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

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

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

3
Michael

「アプリケーションサンドボックス内の他のすべてのファイルを処理するのと同じ方法でiCloudのファイルを処理する」ことに困惑します。これは、多数のファイルを保持するKeynoteやNumbersなどの場合に当てはまり、iCloudがあれば、それらは魔法のように同期を開始します。

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

1
Jesper