Jako początkujący zmagam się z iCloud. Jest kilka próbek, ale zwykle są one dość szczegółowe (na forum programistów jest jeden dla iCloud i CoreData, który jest ogromny). Dokumenty Apple są w porządku, ale nadal nie widzę pełnego obrazu. Więc proszę o wyrozumiałość, niektóre z tych pytań są dość fundamentalne, ale prawdopodobnie łatwe do udzielenia odpowiedzi.
Kontekst: Mam uruchomioną bardzo prostą aplikację iCloud (pełny przykładowy kod poniżej). Jest tylko jeden UITextView wyświetlany użytkownikowi, a jego / jej dane wejściowe są zapisywane w pliku o nazwie text.txt.
Plik txt jest przesyłany do chmury i udostępniany wszystkim urządzeniom. Działa idealnie, ale:
Główny problem: A co z użytkownikami, którzy nie używają iCloud?
Kiedy uruchamiam moją aplikację (patrz kod poniżej), sprawdzam, czy użytkownik ma włączoną usługę iCloud. Jeśli iCloud jest włączony, wszystko jest w porządku. Aplikacja działa dalej i szuka text.txt w chmurze. Jeśli zostanie znaleziony, załaduje go i wyświetli użytkownikowi. Jeśli text.txt nie zostanie znaleziony w chmurze, po prostu utworzy nowy text.txt i wyświetli go użytkownikowi.
Jeśli użytkownik nie ma włączonej usługi iCloud, nic się nie stanie. Jak sprawię, że użytkownicy spoza iCloud będą mogli nadal pracować z moją aplikacją tekstową? A może po prostu je ignoruję? Czy musiałbym pisać osobne funkcje dla użytkowników spoza iCloud? Czyli funkcje, w których po prostu ładuję plik text.txt z folderu dokumentów?
Traktuj pliki w iCloud tak samo, jak wszystkie inne pliki w piaskownicy aplikacji.
Jednak w moim przypadku nie ma już „normalnej” piaskownicy aplikacji. Jest w chmurze. A może zawsze najpierw ładuję plik text.txt z dysku, a następnie sprawdzam w iCloud, czy jest coś bardziej aktualnego?
Powiązany problem: Struktura plików - piaskownica a chmura
Być może moim głównym problemem jest fundamentalne niezrozumienie tego, jak powinien działać iCloud. Kiedy tworzę nowe wystąpienie UIDocument, będę musiał nadpisać dwie metody. Najpierw - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError
pobrać pliki z chmury, a następnie -(id)contentsForType:(NSString *)typeName error:(NSError **)outError
przenieść je do chmury.
Czy muszę włączać oddzielne funkcje, które będą również zapisywać lokalną kopię pliku text.txt w mojej piaskownicy? Czy to zadziała dla użytkowników spoza iCloud? Jak rozumiem, iCloud automatycznie zapisze lokalną kopię pliku text.txt. Więc nie powinno być potrzeby, abym zapisywał cokolwiek w „starej” piaskownicy mojej aplikacji (czyli tak jak to było w dawnych czasach sprzed iCloud). W tej chwili moja piaskownica jest całkowicie pusta, ale nie wiem, czy to prawda. Czy powinienem przechowywać tam inną kopię pliku text.txt? Wydaje mi się, że zaśmiecają moją strukturę danych ... ponieważ jeden plik text.txt znajduje się w chmurze, jeden w piaskownicy iCloud na moim urządzeniu (który będzie działać, nawet jeśli jestem offline), a trzeci w starej, dobrej piaskownicy moja aplikacja ...
MÓJ KOD: Prosty przykładowy kod iCloud
Jest to luźno oparte na przykładzie, który znalazłem na forum programistów i na wideo z sesji WWDC. Ogarnąłem go do absolutnego minimum. Nie jestem pewien, czy moja struktura MVC jest dobra. Model znajduje się w AppDelegate, co nie jest idealne. Wszelkie sugestie, jak to poprawić, są mile widziane.
EDYCJA: Próbowałem wyodrębnić główne pytanie i zamieściłem je [tutaj]. 4
PRZEGLĄD:
Najważniejszy bit, który ładuje plik text.txt z chmury:
// 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
//
// 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];
}