podstawy iCloud i przykład kodu [zamknięty]
Jako początkujący zmagam się z icloudem. Istnieje kilka próbek, ale zazwyczaj są one dość szczegółowe (na forum deweloperów jest jeden dla iCloud i CoreData, który jest ogromny). dokumenty apple są w porządku, ale nadal nie widzę większego obrazu. Proszę więc o cierpliwość, niektóre z tych pytań są dość fundamentalne, ale być może łatwo na nie odpowiedzieć.
Kontekst: mam uruchomioną bardzo prostą aplikację iCloud (pełny przykładowy kod poniżej). Wyświetlany jest tylko jeden UITextView do użytkownika i jego 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: co z użytkownikami, którzy nie korzystają z iCloud?
Po uruchomieniu aplikacji (patrz kod poniżej) sprawdzam, czy użytkownik ma włączone iCloud. Jeśli iCloud jest włączony, wszystko jest w porządku. Aplikacja idzie do przodu i szuka tekstu.txt w chmurze. Jeśli zostanie znaleziony, załaduje go i wyświetli na użytkownika. Jeśli SMS.txt nie znajduje się w chmurze, po prostu utworzy nowy tekst.txt i wyświetli to użytkownikowi.
Jeśli Użytkownik nie ma włączonego iCloud, nic się nie stanie. Jak Mogę sprawić, że użytkownicy spoza iCloud będą mogli nadal pracować z moją aplikacją tekstową? Czy po prostu je ignoruję? Czy muszę pisać oddzielne funkcje dla użytkowników spoza iCloud? Czyli funkcje, w których po prostu Ładuję tekst.txt z folderu Dokumenty?
Traktuj pliki w iCloud tak samo, jak traktujesz wszystkie inne pliki w piaskownicy aplikacji.
Jednak w moim przypadku nie ma już "normalnej" piaskownicy aplikacji. Jest w chmurze. Czy zawsze Wczytuję swój tekst?najpierw txt z dysku, a następnie sprawdź w iCloud, czy jest coś bardziej aktualnego?
Problem pokrewny: struktura plików-piaskownica kontra Chmura
Być może moim głównym problemem jest fundamentalne niezrozumienie sposobu działania iCloud. Kiedy tworzę nowy instancja UIDocument, będę musiał nadpisać dwie metody. Najpierw - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError
, Aby pobrać pliki z chmury, a następnie -(id)contentsForType:(NSString *)typeName error:(NSError **)outError
, Aby pobrać pliki do chmury.
Czy muszę włączyć osobne funkcje, które będą również zapisywać lokalną kopię tekstu.txt do mojej piaskownicy? Czy będzie to działać dla użytkowników spoza iCloud? Jak rozumiem iCloud, zapisze lokalną kopię tekstu.txt automatycznie. Nie powinno więc być potrzeby zapisywania czegokolwiek w "starym" Sandboxie mojej aplikacji (tj., dni przed iCloud). W tej chwili moja piaskownica jest całkowicie pusta, ale nie wiem, czy to prawda. Powinienem zachować kolejną kopię tekstu.txt tam? To wygląda jak zaśmiecanie mojej struktury danych... jak jest jeden tekst.txt w chmurze, jeden w piaskownicy iCloud na moim urządzeniu (co będzie działać, nawet jeśli jestem offline), a trzeci w starym, dobrym piaskownicy mojej aplikacji...
Mój kod: prosty przykładowy kod iCloud
Jest to luźno oparte na przykładzie, który znalazłem na forum programistów oraz na wideo sesji WWDC. Rozebrałem go do minimum. Nie jestem pewien, czy moja struktura MVC jest dobra. Model jest w AppDelegate, co nie jest idealne. Wszelkie sugestie, aby uczynić go lepszym są mile widziane.
EDIT: próbowałem wyodrębnić główne pytanie i zamieściłem je [tutaj].4
Przegląd:
Najważniejszy bit, który ładuje tekst.txt from the cloud:
// 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
Na 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];
}
5 answers
Po prostu ponownie przeczytałem dokumenty i wygląda na to, że moje ogólne podejście jest złe. Powinienem najpierw utworzyć plik w piaskownicy, a następnie przenieść go do chmury. Innymi słowy, Apple wydaje się sugerować, że powinienem mieć trzy wersje tego samego pliku przez cały czas: jedną w katalogu mojej aplikacji, jedną w katalogu demona iCloud mojego urządzenia (który jest również dostępny w trybie offline) i jedną w chmurze: {]}
Aplikacje używają tych samych technologii do zarządzania plikami i katalogami w iCloud, które robią dla plików lokalnych i katalogów. Pliki i katalogi w iCloud to nadal tylko pliki i katalogi. Możesz otwierać, tworzyć, przenosić, kopiować, czytać i pisać z je, usuń je lub dowolne inne operacje, które możesz chcieć zrób. Jedyne różnice między plikami lokalnymi i katalogami oraz pliki i katalogi iCloud to adres URL, którego używasz, aby uzyskać do nich dostęp. Zamiast adresów URL względem piaskownicy aplikacji, adresy URL dla iCloud pliki i katalogi są względem odpowiedniego iCloud katalog kontenerów.
Aby przenieść plik lub katalog do iCloud:
Utwórz plik lub katalog lokalnie w piaskownicy aplikacji. podczas gdy w użycia, plikiem lub katalogiem musi zarządzać prezenter plików, taki jako obiekt UIDocument.
Użyj metody URLForUbiquityContainerIdentifier:, aby pobrać adres URL dla katalogu kontenera iCloud, w którym chcesz przechowywać pozycji. Użyj adresu URL katalogu kontenera, aby utworzyć nowy adres URL, który określa lokalizację elementu w iCloud. Call the setUbiquitous:itemAtURL: destinationURL: error: metoda NSFileManager aby przenieść element do iCloud. Nigdy nie wywołaj tej metody z aplikacji główny wątek; może to zablokować główny wątek na Rozszerzony okres czasu lub spowodować zablokowanie jednego z własnych plików aplikacji prezenterów. Po przeniesieniu pliku lub katalogu do iCloud system kopiuje ten element z aplikacji piaskownica i do prywatnego lokalu katalog, dzięki czemu może być monitorowany przez demona iCloud. Parzyste chociaż plik nie znajduje się już w piaskownicy, aplikacja nadal ma pełną dostęp do niego. Chociaż kopia pliku pozostaje lokalna do bieżącego urządzenie, plik jest również wysyłany do iCloud, dzięki czemu można go dystrybuować do innych urządzeń. Demon iCloud zajmuje się całą pracą nad tworzeniem upewnij się, że lokalne kopie są takie same. Tak więc z perspektywy Twojej aplikacji, plik po prostu jest w iCloud.
Wszystkie zmiany wprowadzone do pliku lub katalogu w iCloud muszą być wykonane za pomocą obiektu file coordinator. Zmiany te obejmują przenoszenie, usuwanie, kopiowanie lub zmiana nazwy elementu. Koordynator plików zapewnia że Demon iCloud nie zmienia pliku ANI katalogu w w tym samym czasie i zapewnia powiadomienie innych zainteresowanych stron o zmiany, które wprowadzasz.
Jednakże, jeśli zagłębisz się trochę głębiej w dokumenty dotyczące setUbiquitous, będziesz find:
Użyj tej metody, aby przenieść plik z jego bieżącej lokalizacji do iCloud. W przypadku plików znajdujących się w piaskownicy aplikacji, polega to na fizycznym usunięciu pliku z katalogu piaskownicy . (System rozszerza uprawnienia piaskownicy aplikacji, aby zapewnić jej dostęp do plików przenoszonych do iCloud.) Możesz również użyć tej metody, aby przenieść pliki z iCloud i z powrotem do katalogu lokalnego.
Więc wygląda na to, że plik / katalog zostanie usunięty stwórz lokalną piaskownicę i przenieś ją do chmury.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-06 01:58:08
Używałem twojego przykładu i podoba mi się, że pomaga mi zrozumieć podstawy iCloud. Teraz zastanawiam się nad twoim pytaniem o moją własną aplikację, która ma wspierać istniejących użytkowników aplikacji z lokalnie przechowywaną treścią, którzy mogą lub nie mogą korzystać z iCloud tworząc te przypadki, o ile mogę powiedzieć:
Przypadki:
- Nowy użytkownik
- ma icloud-tworzenie dokumentów w icloud
- Brak icloud-tworzenie dokumentów lokalnie
- istniejący użytkownik
- ma icloud
- właśnie dodano-migruj lokalne dokumenty do icloud
- nie tylko dodano-Otwórz / Zapisz dokumenty do icloud
- No icloud
- just removed-migrate former iCloud docs to local
- nie tylko usunięte-Otwórz / Zapisz dokumenty do lokalnego
- ma icloud
Jeśli ktoś usunie iCloud - czy wywołania do wszechobecnego URL nie zwrócą nic? W takim przypadku Jak mogę przenieść dokumenty z powrotem do magazynu lokalnego? Na razie stworzę user pref ale wydaje się trochę obejścia problemu.
Wydaje mi się, że brakuje mi tutaj czegoś oczywistego, więc jeśli ktoś może to zobaczyć, proszę dzwonić.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-25 18:08:09
Jeśli chcesz, aby użytkownicy mogli udostępniać tekst między urządzeniami, które są pre-iOS 5.0, będziesz musiał zrobić to, co wszyscy musieli zrobić przed iCloud i przenieść informacje na własny serwer.
Wszystko, czego naprawdę potrzebujesz, to serwer gdzieś, który pozwala aplikacji zapisać swoje pliki tekstowe i powiązać je z kontem użytkownika.
Będziesz potrzebował użytkowników do utworzenia konta i będziesz musiał samodzielnie zarządzać procesem przenoszenia nowych informacji na jednym urządzeniu do własnego "Chmura".
Użytkownicy będą rejestrować się na tym samym koncie na innych urządzeniach i musisz zadbać o wykrycie, kiedy inne urządzenie przeniosło dane do Twojej własnej chmury, i zaktualizować bieżące urządzenie o nowe informacje.
Oczywiście w przypadku urządzeń z systemem iOS 5.0 prawdopodobnie będziesz chciał wykryć zmienione pliki dla urządzeń przed iOS 5.0 we własnej chmurze, a także móc rozmawiać z iCloud.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-19 21:47:17
Nie wygląda na to, że zmagasz się z problemem iCloud/notiosloud tak samo jak z problemem iOS5/notIOS5.
Jeśli twoim celem wdrożenia jest iOS5, po prostu Zawsze używaj struktury UIDocument. Jeśli jest wszechobecny, Twój NSMetaDataQuery znajdzie go w chmurze; jeśli nie, znajdzie go na urządzeniu.
Jeśli z drugiej strony chcesz zapewnić dostęp do aplikacji przed wersją 5.0, musisz warunkowo sprawdzić, czy system iOS jest w wersji 5.0 lub nowszej. Jeśli jest wtedy use UIDocument; jeśli nie, to Odczyt / Zapis danych w stary sposób.
Moim podejściem było napisanie warunkowej metody saveData, która sprawdza iOS5. Jeśli istnieje, aktualizuję liczbę zmian(lub używam menedżera cofania). W Twoim przypadku textViewDidChange wywoła tę metodę. Jeśli nie, to zapisuje na dysk w stary sposób. Podczas ładowania dzieje się odwrotnie.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-20 15:53:07
Jesteś oszołomiony przez "traktuj pliki w iCloud tak samo, jak traktujesz wszystkie inne pliki w piaskownicy aplikacji."Dotyczy to czegoś takiego jak Keynote i Numbers, w których przechowujesz kilka plików, a jeśli masz iCloud, zaczynają się one magicznie synchronizować.
Jednak budujesz coś, co zależy od funkcjonalności podobnej do iCloud. Nie możesz trzymać się tego Oświadczenia, ponieważ twoja aplikacja zależy od tego, czy iCloud będzie obecny, aby cokolwiek działało tak, jak powinno. Albo będziesz musiał zamknij aplikację i po prostu powiedz "proszę skonfiguruj iCloud, aby to działało" lub zduplikuj funkcje podobne do iCloud (własne lub czyjeś), z których zawsze możesz korzystać, niezależnie od tego.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-24 09:09:58