Als Anfänger habe ich Probleme mit iCloud. Es gibt einige Beispiele, aber sie sind normalerweise ziemlich detailliert (im Entwicklerforum gibt es eines für iCloud und CoreData, das riesig ist). Die Apple-Dokumente sind in Ordnung, aber ich kann das große Ganze immer noch nicht sehen. Bitte nehmen Sie mit, einige dieser Fragen sind recht grundlegend, aber möglicherweise leicht zu beantworten.
Kontext: Ich habe eine sehr einfache iCloud-App (vollständiger Beispielcode unten). Dem Benutzer wird nur eine UITextView angezeigt, und seine Eingabe wird in einer Datei namens text.txt gespeichert.
Die txt-Datei wird in die Cloud verschoben und allen Geräten zur Verfügung gestellt. Funktioniert perfekt, aber:
Hauptproblem: Was ist mit Benutzern, die iCloud nicht verwenden?
Wenn ich meine App starte (siehe Code unten), überprüfe ich, ob der Benutzer iCloud aktiviert hat. Wenn iCloud aktiviert ist, ist alles in Ordnung. Die App geht voran und sucht in der Cloud nach text.txt. Wenn es gefunden wird, wird es geladen und dem Benutzer angezeigt. Wenn text.txt nicht in der Cloud gefunden wird, wird einfach eine neue text.txt erstellt und dem Benutzer angezeigt.
Wenn der Benutzer iCloud nicht aktiviert hat, geschieht nichts. Wie mache ich es möglich, dass Nicht-iCloud-Benutzer weiterhin mit meiner Text-App arbeiten können? Oder ignoriere ich sie einfach? Muss ich separate Funktionen für Nicht-iCloud-Benutzer schreiben? Dh Funktionen, bei denen ich einfach eine text.txt aus dem Dokumentenordner lade?
Behandeln Sie Dateien in iCloud genauso wie alle anderen Dateien in Ihrer App-Sandbox.
In meinem Fall gibt es jedoch keine "normale" App-Sandbox mehr. Es ist in der Cloud. Oder lade ich meine text.txt immer zuerst von der Festplatte und überprüfe dann mit iCloud, ob etwas aktueller ist?
Zugehöriges Problem: Dateistruktur - Sandbox vs. Cloud
Vielleicht ist mein Hauptproblem ein grundlegendes Missverständnis darüber, wie iCloud funktionieren soll. Wenn ich eine neue Instanz eines UIDocument erstelle, muss ich zwei Methoden überschreiben. Zuerst - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError
um Dateien aus der Cloud und dann -(id)contentsForType:(NSString *)typeName error:(NSError **)outError
um Dateien in die Cloud zu bekommen.
Muss ich separate Funktionen integrieren, die auch eine lokale Kopie von text.txt in meiner Sandbox speichern? Funktioniert dies für Nicht-iCloud-Benutzer? Soweit ich weiß, wird iCloud automatisch eine lokale Kopie von text.txt speichern. Ich sollte also nichts in der "alten" Sandbox meiner App speichern müssen (dh wie früher in der Zeit vor iCloud). Im Moment ist mein Sandkasten völlig leer, aber ich weiß nicht, ob dies korrekt ist. Soll ich dort eine weitere Kopie von text.txt aufbewahren? Das fühlt sich an, als würde meine Datenstruktur überladen ... da sich eine text.txt in der Cloud befindet, eine in der iCloud-Sandbox auf meinem Gerät (die auch dann funktioniert, wenn ich offline bin) und eine dritte in der guten alten Sandbox von meine App...
MEIN CODE: Ein einfacher iCloud-Beispielcode
Dies basiert lose auf einem Beispiel, das ich im Entwicklerforum und im WWDC-Sitzungsvideo gefunden habe. Ich habe es auf das Nötigste reduziert. Ich bin mir nicht sicher, ob meine MVC-Struktur gut ist. Das Modell befindet sich im AppDelegate, was nicht ideal ist. Vorschläge zur Verbesserung sind willkommen.
EDIT: Ich habe versucht, die Hauptfrage zu extrahieren und sie [hier] gepostet. 4
ÜBERBLICK:
Das wichtigste Bit, das die text.txt aus der Cloud lädt:
// 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
Das 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
DER VIEWCONTROLLER
//
// 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];
}