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];
}
quelle
Antworten:
Ich habe gerade die Dokumente erneut gelesen und es scheint, dass mein allgemeiner Ansatz falsch ist. Ich sollte zuerst die Datei in der Sandbox erstellen und sie dann in die Cloud verschieben. Mit anderen Worten, Apple scheint vorzuschlagen, dass ich immer drei Versionen derselben Datei haben sollte: eine im Verzeichnis meiner App, eine im iCloud-Dämonenverzeichnis meines Geräts (auf das auch offline zugegriffen werden kann) und eine in die Wolke:
Wenn Sie sich jedoch etwas eingehender mit den Dokumenten zu setUbiquitous befassen, werden Sie Folgendes feststellen:
Dies scheint also zu bedeuten, dass eine Datei / ein Verzeichnis aus der lokalen Sandbox gelöscht und in die Cloud verschoben wird.
quelle
Ich habe Ihr Beispiel verwendet und es gefällt mir, um die Grundlagen von iCloud zu verstehen. Jetzt ringe ich mit Ihrer Frage nach meiner eigenen App, die bestehende Benutzer der App mit lokal gespeicherten Inhalten unterstützen muss, die iCloud verwenden oder nicht, um diese Fälle zu erstellen, soweit ich das beurteilen kann:
Fälle:
Wenn jemand iCloud entfernt - würden die Aufrufe der allgegenwärtigen URL nicht Null zurückgeben? Wenn dies der Fall ist, wie migriere ich die Dokumente zurück in den lokalen Speicher? Ich werde vorerst eine Benutzereinstellung erstellen, scheint aber eine Problemumgehung zu sein.
Ich habe das Gefühl, dass mir hier etwas Offensichtliches fehlt. Wenn jemand es sehen kann, melden Sie sich bitte.
quelle
Wenn Sie möchten, dass Benutzer Text zwischen Geräten vor iOS 5.0 austauschen können, müssen Sie das tun, was alle vor iCloud tun mussten, und Informationen auf Ihren eigenen Server verschieben.
Alles, was Sie wirklich brauchen, ist ein Server, auf dem Ihre App ihre Textdateien speichern und einem Benutzerkonto zuordnen kann.
Sie benötigen Benutzer, um ein Konto zu erstellen, und Sie müssen den Prozess selbst verwalten und neue Informationen auf einem Gerät in Ihre eigene "Cloud" verschieben.
Benutzer registrieren sich mit demselben Konto auf anderen Geräten, und Sie müssen sicherstellen, dass erkannt wird, wenn ein anderes Gerät Daten in Ihre eigene Cloud verschoben hat, und das aktuelle Gerät mit den neuen Informationen aktualisieren.
Für iOS 5.0-Geräte möchten Sie wahrscheinlich geänderte Dateien für Geräte vor iOS 5.0 in Ihrer eigenen Cloud erkennen und auch mit iCloud kommunizieren können.
quelle
Es scheint nicht, dass Sie mit einem iCloud / notICloud-Problem zu kämpfen haben, sondern mit einem iOS5 / notIOS5-Problem.
Wenn Ihr Bereitstellungsziel iOS5 ist, verwenden Sie einfach immer die UIDocument-Struktur. Wenn es allgegenwärtig ist, findet es Ihre NSMetaDataQuery in der Cloud. Wenn nicht, wird es auf dem Gerät gefunden.
Wenn Sie andererseits Zugriff auf Ihre App vor 5.0 gewähren möchten, müssen Sie bedingt prüfen, ob das laufende iOS 5.0 oder höher ist. Wenn dies der Fall ist, verwenden Sie UIDocument. Wenn nicht, lesen / schreiben Sie die Daten auf die alte Weise.
Mein Ansatz war es, eine bedingte saveData-Methode zu schreiben, die nach iOS5 sucht. Wenn es existiert, aktualisiere ich die Änderungsanzahl (oder verwende einen Rückgängig-Manager). In Ihrem Fall würde textViewDidChange diese Methode aufrufen. Wenn nicht, wird auf die alte Weise auf der Festplatte gespeichert. Beim Laden passiert das Gegenteil.
quelle
Sie sind verwirrt von "Behandeln Sie Dateien in iCloud genauso wie alle anderen Dateien in Ihrer App-Sandbox." Dies gilt für so etwas wie Keynote und Numbers, bei denen Sie eine Reihe von Dateien aufbewahren. Wenn Sie über iCloud verfügen, werden diese auf magische Weise synchronisiert.
Sie erstellen jedoch etwas, das von der iCloud-ähnlichen Funktionalität abhängt. Sie können an dieser Aussage nicht festhalten, da Ihre App davon abhängt, dass iCloud vorhanden ist, damit alles so funktioniert, wie es soll. Sie müssen entweder Ihre App schließen und einfach "Bitte richten Sie iCloud ein, damit dies funktioniert" sagen oder iCloud-ähnliche Funktionen (Ihre eigenen oder die einer anderen Person) duplizieren, die Sie unabhängig davon immer verwenden können.
quelle