Как новичок борюсь с iCloud. Есть несколько примеров, но они обычно довольно подробны (на форуме разработчиков есть один для iCloud и CoreData, который является огромным). Документы Apple в порядке, но я все еще не вижу общей картины. Так что терпите меня, некоторые из этих вопросов весьма фундаментальны, но, возможно, на них легко ответить.
Контекст: у меня работает очень простое приложение iCloud (полный пример кода ниже). Пользователю отображается только один UITextView, и его / ее ввод сохраняется в файле с именем text.txt.
Файл txt отправляется в облако и становится доступным для всех устройств. Работает отлично, но:
Основная проблема: как быть с пользователями, которые не используют iCloud?
Когда я запускаю свое приложение (см. Код ниже), я проверяю, включен ли у пользователя iCloud. Если iCloud включен, все нормально. Приложение продолжает поиск text.txt в облаке. Если он найден, он загрузит его и отобразит пользователю. Если text.txt не найден в облаке, он просто создаст новый text.txt и отобразит его пользователю.
Если у пользователя не включен iCloud, ничего не произойдет. Как сделать так, чтобы пользователи, не использующие iCloud, по-прежнему могли работать с моим текстовым приложением? Или я их просто игнорирую? Нужно ли мне писать отдельные функции для пользователей, не использующих iCloud? Т.е. функции, в которых я просто загружаю text.txt из папки документов?
Обращайтесь с файлами в iCloud так же, как со всеми другими файлами в изолированной программной среде приложения.
Однако в моем случае больше нет «нормальной» песочницы для приложений. Это в облаке. Или я всегда сначала загружаю свой text.txt с диска, а затем проверяю с помощью iCloud, есть ли что-то более актуальное?
Связанная проблема: Файловая структура - песочница против облака
Возможно, моя главная проблема - это фундаментальное непонимание того, как должен работать iCloud. Когда я создаю новый экземпляр UIDocument, мне придется перезаписать два метода. Сначала - (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 в облаке, один в песочнице iCloud на моем устройстве (который будет работать, даже если я не в сети), а третий в старой доброй песочнице мое приложение ...
МОЙ КОД: простой пример кода iCloud
Это примерно основано на примере, который я нашел на форуме разработчиков и на видео сессии WWDC. Я сократил его до минимума. Я не уверен, что моя структура MVC хороша. Модель находится в AppDelegate, что не идеально. Любые предложения по его улучшению приветствуются.
РЕДАКТИРОВАТЬ: Я попытался извлечь главный вопрос и разместил его [здесь]. 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
Документ UID
// 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
КОНТРОЛЛЕР VIEW
//
// 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, кажется, предлагает мне всегда иметь три версии одного и того же файла: одну в каталоге моего приложения, одну в каталоге демона iCloud на моем устройстве (которое также доступно в автономном режиме) и одну в облако:
Однако если вы немного углубитесь в документацию, касающуюся setUbiquitous, вы обнаружите:
Таким образом, это означает, что файл / каталог удаляется из локальной песочницы и перемещается в облако.
источник
Я использовал ваш пример, и мне нравится, что он помогает мне понять основы iCloud. Теперь я спорю с вашим вопросом о моем собственном приложении, которое должно поддерживать существующих пользователей приложения с локально сохраненным контентом, которые могут или не могут использовать iCloud, создавая эти кейсы, насколько я могу судить:
Кейсы:
Если кто-то удалит iCloud - разве вызовы повсеместного URL не вернут ноль? Если это так, как мне перенести документы обратно в локальное хранилище? На данный момент я создам пользовательские настройки, но, похоже, это обходной путь.
Я чувствую, что здесь не хватает чего-то очевидного, поэтому, если кто-то это видит, пожалуйста, присоединяйтесь.
источник
Если вы хотите, чтобы пользователи могли обмениваться текстом между устройствами с предшествующей iOS 5.0, вам придется сделать то, что все должны были делать до iCloud, и переместить информацию на свой собственный сервер.
Все, что вам действительно нужно, это где-то сервер, который позволяет вашему приложению сохранять свои текстовые файлы и связывать их с учетной записью пользователя.
Вам понадобятся пользователи для создания учетной записи, и вам нужно будет самостоятельно управлять процессом перемещения новой информации с одного устройства в свое собственное «облако».
Пользователи будут регистрироваться с той же учетной записью на других устройствах, и вам нужно будет определить, когда другое устройство переместило данные в ваше собственное облако, и обновить текущее устройство новой информацией.
Очевидно, что для устройств iOS 5.0 вы, вероятно, захотите обнаруживать измененные файлы для устройств до iOS 5.0 в своем собственном облаке, а также иметь возможность общаться с iCloud.
источник
Не похоже, что вы боретесь с проблемой iCloud / notICloud так сильно, как с проблемой iOS5 / notIOS5.
Если вашей целью развертывания является iOS5, просто всегда используйте структуру UIDocument. Если он повсеместен, то ваш NSMetaDataQuery найдет его в облаке; в противном случае он найдет его на устройстве.
Если, с другой стороны, вы хотите предоставить доступ к вашему приложению до версии 5.0, вам нужно будет условно проверить, установлена ли iOS 5.0 или выше. Если это так, используйте UIDocument; если нет, то читать / писать данные старым способом.
Мой подход заключался в написании условного метода saveData, который проверяет iOS5. Если он существует, я обновляю счетчик изменений (или использую диспетчер отмены). В вашем случае textViewDidChange вызовет этот метод. Если нет, то сохраняет на диск старым способом. При загрузке происходит обратное.
источник
Вас сбивает с толку фраза «Обращайтесь с файлами в iCloud так же, как со всеми другими файлами в песочнице вашего приложения». Это верно для чего-то вроде Keynote и Numbers, где вы храните кучу файлов, и если у вас есть iCloud, они начинают волшебным образом синхронизироваться.
Однако вы создаете что-то, что зависит от функциональности, подобной iCloud. Вы не можете придерживаться этого утверждения, потому что ваше приложение зависит от присутствия iCloud, чтобы все работало так, как должно. Вам нужно будет либо закрыть приложение и просто сказать «пожалуйста, настройте iCloud, чтобы это работало», либо продублируйте функциональность, подобную iCloud (свою или чужую), которую вы всегда можете использовать, независимо от того.
источник