1. ホーム
  2. iphone

[解決済み] iCloudの基礎とコードサンプル【終了しました

2023-06-05 16:16:52

質問

初心者のため、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をもう一枚入れておいた方がいいのだろうか?クラウドに text.txt が 1 つ、デバイスの iCloud サンドボックスに 1 つ (オフラインでも機能します)、そして 3 つ目が私のアプリの古き良きサンドボックスにあるので、これは私のデータ構造を乱雑にしているように感じます............。


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

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


EDIT: 主な質問を抽出してみて、[ここに]投稿しました。 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];

}

どのように解決するのですか?

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

アプリは iCloud でファイルやディレクトリを管理するために、ローカルのファイルやディレクトリと同じテクノロジーを使用します。iCloud のファイルおよび は単なるファイルとディレクトリです。あなたができることは 開く、作成する、移動する、コピーする、読み書きをする、削除するなどの操作が可能です。 それらを開く、作成する、移動する、コピーする、読み書きをする、削除する、またはあなたがしたいかもしれない他のすべての操作を行うことができます。 できます。ローカルのファイルとディレクトリと異なるのは iCloudのファイルとディレクトリの唯一の違いは、それらにアクセスするために使用するURLです。 アプリのサンドボックスに対する相対的なURLではなく、iCloudのファイルとディレクトリのURLは、アプリのサンドボックスに対する相対的なURLです。 ファイルおよびディレクトリのURLは、アプリのサンドボックスに対する相対的なURLではなく、対応するiCloud コンテナ・ディレクトリに相対的です。

ファイルやディレクトリをiCloudに移動すること。

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

UIDocument オブジェクトのようなファイルプレゼンターによって管理されなければなりません。

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

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

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

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

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