22.0. Introduction(iCloud)
假设一位同事在办公室玩手游,玩到第12关,回到家后用pad玩,却得从第一关开始玩起。这真不好,如果在pad上也能同步在第12关,那该多好啊。iCloud能解决这个问题。
要使用iCloud服务,你需要正确配置证书
22.1. Setting Up Your App for iCloud
工程配置
简述版:
1,创建iCloud enabled的App ID
2,创建关联这个appID的新证书并下载安装
3,Xcode创建新应用,并关联刚安装的新证书
4,在Capabilities页签,打开iCloud开关
详细:
1,创建应用,假设是com.pixolity.ios.cookbook.icloudapp
2,创建App ID,勾选iCloud
3,创建新的development provision profile ,记住要关联刚才的app id
4,创建完后,下载并安装(拖到iTunes安装)
5,Xcode创建新app,bundle ID 设置为上面写的App ID。如果输错,你的应用将用不了那个证书
6,target中选择刚安装的证书
7,Capabilities中打开iCloud开关
编译一下,如果编译器说entitlements file找不到,请先核查下Code Signing Entitlements的路径,有可能你把它改成这样就可以:$(SRCROOT)/$(TARGET_NAME)/
22.2. Storing and Synchronizing Dictionaries in iCloud
保存和同步数据
NSUbiquitousKeyValueStore
其保存的数据会根据证书文件的信息保存到你的iCloud 账户中,换句话说,你只管用NSUbiquitousKeyValueStore来保存数据,不用担心与别的数据冲突。
在上一节中,我们Capabilities中设置勾选iCloud,然而想用NSUbiquitousKeyValueStore类,我们还需勾选Use key-value store.
NSUbiquitousKeyValueStore的用法跟NSUserDefaults很像。可以保存string,Boolean,integer,float and other values.每个值都需要一个键来对应。他们的不同是,一个保存在云端,一个保存在本地的.plist文件中,当删除应用时,这些数据会被删除。
在iCloud,一个应用实例用一个唯一的id来保存数据,其由3部分组成:
Team ID:开发者ID
Reverse domain-style of company identifier: 公司域名反转
App identifier and optional suffix:应用ID和后缀
-(void)testStoreSyn
{
NSUbiquitousKeyValueStore *kvoStore =
[NSUbiquitousKeyValueStore defaultStore];
NSString *stringValue = @"My String";
NSString *stringValueKey = @"MyStringKey";
BOOL boolValue = YES;
NSString *boolValueKey = @"MyBoolKey";
BOOL mustSynchronize = NO;
if ([[kvoStore stringForKey:stringValueKey] length] == 0){
NSLog(@"Could not find the string value in iCloud. Setting...");
[kvoStore setString:stringValue forKey:stringValueKey];
mustSynchronize = YES;
} else {
NSLog(@"Found the string in iCloud, getting...");
stringValue = [kvoStore stringForKey:stringValueKey];
}
if ([kvoStore boolForKey:boolValueKey] == NO){
NSLog(@"Could not find the boolean value in iCloud. Setting...");
[kvoStore setBool:boolValue forKey:boolValueKey];
mustSynchronize = YES;
} else {
NSLog(@"Found the boolean in iCloud, getting...");
boolValue = [kvoStore boolForKey:boolValueKey];
}
if (mustSynchronize){
if ([kvoStore synchronize]){
NSLog(@"Successfully synchronized with iCloud.");
} else {
NSLog(@"Failed to synchronize with iCloud.");
}
}
}
第一次运行:
2014-08-12 15:09:42.305 ggcloud[855:907] Could not find the string value in iCloud. Setting...
2014-08-12 15:09:42.309 ggcloud[855:907] Could not find the boolean value in iCloud. Setting...
2014-08-12 15:09:42.347 ggcloud[855:907] Successfully synchronized with iCloud.
同步之后运行:
2014-08-12 15:10:11.438 ggcloud[868:907] Found the string in iCloud, getting...
2014-08-12 15:10:11.442 ggcloud[868:907] Found the boolean in iCloud, getting...
22.3. Creating and Managing Folders for Apps in iCloud
保存文件到iCloud
-(void)testCreatAndManageFolder
{
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSString *teamID = <# Put your team ID here #>;
NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier];
NSString *rootFolderIdentifier = [NSString stringWithFormat:
@"%@.%@",
teamID, bundleId];
NSURL *containerURL =
[fileManager URLForUbiquityContainerIdentifier:rootFolderIdentifier];
NSString *documentsDirectory =
[[containerURL path]
stringByAppendingPathComponent:@"Documents"];
BOOL isDirectory = NO;
BOOL mustCreateDocumentsDirectory = NO;
if ([fileManager fileExistsAtPath:documentsDirectory isDirectory:&isDirectory]){
if (isDirectory == NO){
mustCreateDocumentsDirectory = YES;
}
} else {
mustCreateDocumentsDirectory = YES;
}
if (mustCreateDocumentsDirectory){
NSLog(@"Must create the directory.");
NSError *directoryCreationError = nil;
if ([fileManager createDirectoryAtPath:documentsDirectory withIntermediateDirectories:YES
attributes:nil
error:&directoryCreationError]){
NSLog(@"Successfully created the folder.");
} else {
NSLog(@"Failed to create the folder with error = %@",
directoryCreationError);
}
} else {
NSLog(@"This folder already exists.");
}
}
打印:
2014-08-12 15:29:55.300 ggcloud[945:907] Must create the directory.
2014-08-12 15:29:55.304 ggcloud[945:907] Successfully created the folder.
再运行一次:
2014-08-12 15:30:26.426 ggcloud[961:907] This folder already exists.
或:
-(void)testCreateAndManageFloder2
{
NSString *teamID = <# Put your team ID here #>;
NSString *containerID = [[NSBundle mainBundle] bundleIdentifier];
NSString *documentsDirectory = nil;
if ([self createIcloudDirectory:@"Documents3" recursiveCreation:YES
teamID:teamID
iCloudContainer:containerID
finalPath:&documentsDirectory]){
NSLog(@"Successfully created the directory in %@", documentsDirectory);
} else {
NSLog(@"Failed to create the directory.");
}
}
- (BOOL) createIcloudDirectory:(NSString *)paramDirectory
recursiveCreation:(BOOL)paramRecursiveCreation
teamID:(NSString *)paramTeamID
iCloudContainer:(NSString *)paramContainer
finalPath:(NSString **)paramFinalPath{
BOOL result = NO;
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSString *rootFolderIdentifier = [NSString stringWithFormat:
@"%@.%@", paramTeamID, paramContainer];
NSURL *containerURL =[fileManager URLForUbiquityContainerIdentifier:rootFolderIdentifier];
NSString *documentsDirectory =
[[containerURL path]
stringByAppendingPathComponent:paramDirectory];
if (paramFinalPath != nil){
*paramFinalPath = documentsDirectory;
}
BOOL isDirectory = NO;
BOOL mustCreateDocumentsDirectory = NO;
if ([fileManager fileExistsAtPath:documentsDirectory isDirectory:&isDirectory]){
if (isDirectory == NO){
mustCreateDocumentsDirectory = YES;
}
} else {
mustCreateDocumentsDirectory = YES;
}
if (mustCreateDocumentsDirectory){
NSLog(@"Must create the directory.");
NSError *directoryCreationError = nil;
if ([fileManager createDirectoryAtPath:documentsDirectory
withIntermediateDirectories:paramRecursiveCreation
attributes:nil
error:&directoryCreationError]){
result = YES;
NSLog(@"Successfully created the folder.");
} else {
NSLog(@"Failed to create the folder with error = %@",directoryCreationError);
}
} else {
NSLog(@"This folder already exists.");
result = YES;
}
return result;
}
打印:
2014-08-12 15:35:20.908 ggcloud[1018:907] Must create the directory.
2014-08-12 15:35:20.912 ggcloud[1018:907] Successfully created the folder.
2014-08-12 15:35:20.914 ggcloud[1018:907] Successfully created the directory in /private/var/mobile/Library/Mobile Documents/<# Put your team ID here #>~com~gaozgao~ggcloud/Documents3
运行之后,可以在手机->设置->iCloud->Storage&Backup->Documents&DATA 找到你的应用,在这里可以看到你之前创建的文件。(在这里还看不到,到下一节就能看到)
22.4. Searching for Files and Folders in iCloud
Use the NSMetadataQuery class.
-(void)testSearch
{
/* Listen for a notification that gets fired when the metadata query
has finished finding the items we were looking for */
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(handleMetadataQueryFinished:)
name:NSMetadataQueryDidFinishGatheringNotification
object:nil];
/* Create our query now */
self.metadataQuery = [[NSMetadataQuery alloc] init];
NSArray *searchScopes = [[NSArray alloc] initWithObjects:
NSMetadataQueryUbiquitousDocumentsScope, nil];
[self.metadataQuery setSearchScopes:searchScopes];
NSPredicate *predicate = [NSPredicate predicateWithFormat:
@"%K like %@",
NSMetadataItemFSNameKey,
@"*"];
[self.metadataQuery setPredicate:predicate];
if ([self.metadataQuery startQuery]){
NSLog(@"Successfully started the query.");
} else {
NSLog(@"Failed to start the query.");
}
}
- (NSURL *) urlForDocumentsFolderIniCloud{
NSURL *result = nil;
NSString *teamID = kMyTeamID;
NSString *containerID = [[NSBundle mainBundle] bundleIdentifier];
NSString *teamIDAndContainerID = [[NSString alloc] initWithFormat:@"%@.%@",
teamID, containerID];
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSURL *appiCloudContainerURL =
[fileManager URLForUbiquityContainerIdentifier:teamIDAndContainerID];
result = [appiCloudContainerURL URLByAppendingPathComponent:@"Documents"
isDirectory:YES];
if ([fileManager fileExistsAtPath:[result path]] == NO){
/* The Documents directory does NOT exist in our app's iCloud
container; attempt to create it now */
NSError *creationError = nil;
BOOL created = [fileManager createDirectoryAtURL:result
withIntermediateDirectories:YES
attributes:nil
error:&creationError];
if (created){
NSLog(@"Successfully created the Documents folder in iCloud.");
} else {
NSLog(@"Failed to create the Documents folder in \
iCloud. Error = %@", creationError);
result = nil;
}
} else {
/* the Documents directory already exists in our app's
iCloud container; we don't have to do anything */
}
return result;
}
- (NSURL *) urlForRandomFileInDocumentsFolderInIcloud{
NSURL *result = nil;
NSUInteger randomNumber = arc4random() % NSUIntegerMax;
NSString *randomFileName = [[NSString alloc] initWithFormat:@"%lu.txt", (unsigned long)randomNumber];
/* Check in the metadata query if this file already exists */
__block BOOL fileExistsAlready = NO;
[self.metadataQuery.results enumerateObjectsUsingBlock:
^(NSMetadataItem *item, NSUInteger idx, BOOL *stop) {
NSString *itemFileName = [item valueForAttribute:NSMetadataItemFSNameKey];
if ([itemFileName isEqualToString:randomFileName]){
NSLog(@"This file already exists. Aborting...");
fileExistsAlready = YES;
*stop = YES;
}
}];
if (fileExistsAlready){
return nil;
}
result = [[self urlForDocumentsFolderIniCloud]
URLByAppendingPathComponent:randomFileName];
return result;
}
- (NSURL *) urlForRandomFileInDocumentsFolderForFileWithName :(NSString *)paramFileName{
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSURL *documentsUrl = [fileManager URLForDirectory:NSDocumentDirectory
inDomain:NSUserDomainMask
appropriateForURL:Nil
create:YES
error:nil];
return [documentsUrl URLByAppendingPathComponent:paramFileName];
}
- (void) enumerateMetadataResults:(NSArray *)paramResults{
[paramResults enumerateObjectsUsingBlock:
^(NSMetadataItem *item, NSUInteger index, BOOL *stop) {
NSString *itemName = [item valueForAttribute:NSMetadataItemFSNameKey];
NSURL *itemURL = [item valueForAttribute:NSMetadataItemURLKey];
NSNumber *itemSize = [item valueForAttribute:NSMetadataItemFSSizeKey];
NSLog(@"Item name = %@", itemName);
NSLog(@"Item URL = %@", itemURL);
NSLog(@"Item Size = %llu",
(unsigned long long)[itemSize unsignedLongLongValue]);
}];
}
- (void) handleMetadataQueryFinished:(id)paramSender{
NSLog(@"Search finished");
if ([[paramSender object] isEqual:self.metadataQuery] == NO){
NSLog(@"An unknown object called this method. Not safe to proceed.");
return;
}
/* Stop listening for notifications as we are not expecting
anything more */
[[NSNotificationCenter defaultCenter] removeObserver:self];
/* We are done with the query, let's stop the process now */
[self.metadataQuery disableUpdates];
[self.metadataQuery stopQuery];
[self enumerateMetadataResults:self.metadataQuery.results];
if ([self.metadataQuery.results count] == 0){
NSLog(@"No files were found.");
}
NSURL *urlForFileIniCloud =
[self urlForRandomFileInDocumentsFolderInIcloud];
if (urlForFileIniCloud == nil){
NSLog(@"Cannot create a file with this URL. URL is empty.");
return;
}
NSString *fileName = [[[urlForFileIniCloud path]
componentsSeparatedByString:@"/"] lastObject];
NSURL *urlForFileInAppSandbox =
[self urlForRandomFileInDocumentsFolderForFileWithName:fileName];
NSString *fileContent =
[[NSString alloc] initWithFormat:@"Content of %@",
[[self urlForRandomFileInDocumentsFolderInIcloud] path]];
/* Save the file temporarily in the app bundle and then move
it to the cloud */
NSError *writingError = nil;
BOOL couldWriteToAppSandbox =
[fileContent writeToFile:[urlForFileInAppSandbox path]
atomically:YES
encoding:NSUTF8StringEncoding
error:&writingError];
/* If cannot save the file, just return from method because it
won't make any sense to continue as we, ideally, should have
stored the file in iCloud from the app sandbox but here, if an
error has occurred, we cannot continue */
if (couldWriteToAppSandbox == NO){
NSLog(@"Failed to save the file to app sandbox. Error = %@",
writingError);
return;
}
NSFileManager *fileManager = [[NSFileManager alloc] init];
/* Now move the file to the cloud */
NSError *ubiquitousError = nil;
BOOL setUbiquitousSucceeded = [fileManager setUbiquitous:YES
itemAtURL:urlForFileInAppSandbox
destinationURL:urlForFileIniCloud
error:&ubiquitousError];
if (setUbiquitousSucceeded){
NSLog(@"Successfully moved the file to iCloud.");
/* The file has been moved from App Sandbox to iCloud */
} else {
NSLog(@"Failed to move the file to iCloud with error = %@",
ubiquitousError);
}
}
运行了3次之后:
2014-08-12 16:19:30.479 ggcloud[1154:907] Successfully started the query.
2014-08-12 16:19:30.644 ggcloud[1154:907] Search finished
2014-08-12 16:19:30.646 ggcloud[1154:907] Item name = 3555629546.txt
2014-08-12 16:19:30.647 ggcloud[1154:907] Item URL = file://localhost/private/var/mobile/Library/Mobile%20Documents/<# Put your team ID here #>~com~gaozgao~ggcloud/Documents/3555629546.txt
2014-08-12 16:19:30.648 ggcloud[1154:907] Item Size = 111
2014-08-12 16:19:30.650 ggcloud[1154:907] Item name = 4290254931.txt
2014-08-12 16:19:30.651 ggcloud[1154:907] Item URL = file://localhost/private/var/mobile/Library/Mobile%20Documents/<# Put your team ID here #>~com~gaozgao~ggcloud/Documents/4290254931.txt
2014-08-12 16:19:30.652 ggcloud[1154:907] Item Size = 110
2014-08-12 16:19:30.723 ggcloud[1154:907] Successfully moved the file to iCloud.
22.5. Storing User Documents in iCloud
CloudDocument.h文件
#import <UIKit/UIKit.h>
@class CloudDocument;
@protocol CloudDocumentProtocol<NSObject>
- (void) cloudDocumentChanged:(CloudDocument *)paramSender;
@end
@interface CloudDocument : UIDocument
@property (nonatomic, strong) NSString *documentText;
@property (nonatomic, weak) id<CloudDocumentProtocol> delegate;
/* Designated Initializer */
- (id) initWithFileURL:(NSURL *)paramURL delegate:(id<CloudDocumentProtocol>)paramDelegate;
@end
CloudDocument.m文件
#import "CloudDocument.h"
@implementation CloudDocument
- (id) initWithFileURL:(NSURL *)paramURL
delegate:(id<CloudDocumentProtocol>)paramDelegate{
self = [super initWithFileURL:paramURL];
if (self != nil){
if (paramDelegate == nil){
NSLog(@"Warning: no delegate is given.");
}
_delegate = paramDelegate;
}
return self;
}
- (id) initWithFileURL:(NSURL *)paramURL{
return [self initWithFileURL:paramURL
delegate:nil];
}
- (id) contentsForType:(NSString *)typeName
error:(NSError *__autoreleasing *)outError{
if ([self.documentText length] == 0){
self.documentText = @"New Document";
}
return [self.documentText dataUsingEncoding:NSUTF8StringEncoding];
}
- (BOOL) loadFromContents:(id)contents
ofType:(NSString *)typeName
error:(NSError *__autoreleasing *)outError{
NSData *data = (NSData *)contents;
if ([data length] == 0){
self.documentText = @"New Document";
} else {
self.documentText = [[NSString alloc]
initWithData:data
encoding:NSUTF8StringEncoding];
}
if ([_delegate respondsToSelector:@selector(cloudDocumentChanged:)]){
[_delegate cloudDocumentChanged:self];
}
return YES;
}
@end
ViewController.h文件
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
ViewController.m文件
#import "ViewController.h"
#import "CloudDocument.h"
#define kMyTeamID @"你的teamID"
@interface ViewController ()<CloudDocumentProtocol, UITextViewDelegate>
@property (nonatomic, strong) CloudDocument *cloudDocument;
@property (nonatomic, strong) UITextView *textViewCloudDocumentText;
@property (nonatomic, strong) NSMetadataQuery *metadataQuery;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self listenForKeyboardNotifications];
self.view.backgroundColor = [UIColor brownColor];
[self setupTextView];
[self startSearchingForDocumentIniCloud];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSURL *) urlForDocumentsDirectoryInIcloud{
NSURL *result = nil;
NSString *teamID = kMyTeamID;
NSString *containerID = [[NSBundle mainBundle] bundleIdentifier];
NSString *teamIdAndContainerId = [NSString stringWithFormat:@"%@.%@",
teamID,
containerID];
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSURL *iCloudURL = [fileManager
URLForUbiquityContainerIdentifier:teamIdAndContainerId];
NSURL *documentsFolderURLIniCloud =
[iCloudURL URLByAppendingPathComponent:@"Documents"
isDirectory:YES];
/* If it doesn't exist, create it */
if ([fileManager
fileExistsAtPath:[documentsFolderURLIniCloud path]] == NO){
NSLog(@"The documents folder does NOT exist in iCloud. Creating...");
NSError *folderCreationError = nil;
BOOL created = [fileManager createDirectoryAtURL:documentsFolderURLIniCloud
withIntermediateDirectories:YES
attributes:nil
error:&folderCreationError];
if (created){
NSLog(@"Successfully created the Documents folder in iCloud.");
result = documentsFolderURLIniCloud;
} else {
NSLog(@"Failed to create the Documents folder in iCloud. \
Error = %@", folderCreationError);
}
} else {
NSLog(@"The Documents folder already exists in iCloud.");
result = documentsFolderURLIniCloud;
}
return result;
}
- (NSURL *) urlForFileInDocumentsDirectoryIniCloud{
return [[self urlForDocumentsDirectoryInIcloud] URLByAppendingPathComponent:@"UserDocument.txt"];
}
- (void) setupTextView{
/* Create the text view */
CGRect textViewRect = CGRectMake(20.0f,
20.0f,
self.view.bounds.size.width - 40.0f,
self.view.bounds.size.height - 40.0f);
self.textViewCloudDocumentText = [[UITextView alloc] initWithFrame:
textViewRect];
self.textViewCloudDocumentText.delegate = self;
self.textViewCloudDocumentText.font = [UIFont systemFontOfSize:20.0f];
[self.view addSubview:self.textViewCloudDocumentText];
}
- (void) listenForKeyboardNotifications{
/* As we have a text view, when the keyboard shows on screen, we want to
make sure our textview's content is fully visible so start
listening for keyboard notifications */
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleKeyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleKeyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void) startSearchingForDocumentIniCloud{
/* Start searching for existing text documents */
self.metadataQuery = [[NSMetadataQuery alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K like %@",
NSMetadataItemFSNameKey,
@"*"];
[self.metadataQuery setPredicate:predicate];
NSArray *searchScopes = [[NSArray alloc] initWithObjects:
NSMetadataQueryUbiquitousDocumentsScope,
nil];
[self.metadataQuery setSearchScopes:searchScopes];
NSString *metadataNotification = NSMetadataQueryDidFinishGatheringNotification;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleMetadataQueryFinished:)
name:metadataNotification
object:nil];
[self.metadataQuery startQuery];
}
- (void) handleMetadataQueryFinished:(NSNotification *)paramNotification{
/* Make sure this is the metadata query that we were expecting... */
NSMetadataQuery *senderQuery = (NSMetadataQuery *)[paramNotification object];
if ([senderQuery isEqual:self.metadataQuery] == NO){
NSLog(@"Unknown metadata query sent us a message.");
return;
}
[self.metadataQuery disableUpdates];
/* Now we stop listening for these notifications because we don't really
have to, any more */
NSString *metadataNotification =
NSMetadataQueryDidFinishGatheringNotification;
[[NSNotificationCenter defaultCenter] removeObserver:self
name:metadataNotification
object:nil];
[self.metadataQuery stopQuery];
NSLog(@"Metadata query finished.");
/* Let's find out if we had previously created this document in the user's
cloud space because if yes, then we have to avoid overwriting that
document and just use the existing one */
__block BOOL documentExistsIniCloud = NO;
NSString *FileNameToLookFor = @"UserDocument.txt";
NSArray *results = self.metadataQuery.results;
[results enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSMetadataItem *item = (NSMetadataItem *)obj;
NSURL *itemURL = [item valueForAttribute:NSMetadataItemURLKey];
NSString *lastComponent = [[itemURL pathComponents] lastObject];
if ([lastComponent isEqualToString:FileNameToLookFor]){
if ([itemURL
isEqual:[self urlForFileInDocumentsDirectoryIniCloud]]){
documentExistsIniCloud = YES;
*stop = YES; }
}
}];
NSURL *urlOfDocument = [self urlForFileInDocumentsDirectoryIniCloud];
self.cloudDocument = [[CloudDocument alloc] initWithFileURL:urlOfDocument delegate:self];
__weak ViewController *weakSelf = self;
/* If the document exists, open it */
if (documentExistsIniCloud){
NSLog(@"Document already exists in iCloud. Loading it from there...");
[self.cloudDocument openWithCompletionHandler:^(BOOL success) {
if (success){
ViewController *strongSelf = weakSelf;
NSLog(@"Successfully loaded the document from iCloud.");
strongSelf.textViewCloudDocumentText.text = strongSelf.cloudDocument.documentText;
} else {
NSLog(@"Failed to load the document from iCloud.");
}
}];
} else {
NSLog(@"Document does not exist in iCloud. Creating it...");
/* If the document doesn't exist, ask the CloudDocument class to
save a new file on that address for us */
[self.cloudDocument
saveToURL:[self urlForFileInDocumentsDirectoryIniCloud] forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
if (success){
NSLog(@"Successfully created the new file in iCloud.");
ViewController *strongSelf = weakSelf;
strongSelf.textViewCloudDocumentText.text =
strongSelf.cloudDocument.documentText;
} else {
NSLog(@"Failed to create the file.");
}
}];
}
}
- (void) textViewDidChange:(UITextView *)textView{
self.cloudDocument.documentText = textView.text;
[self.cloudDocument updateChangeCount:UIDocumentChangeDone];
}
- (void) cloudDocumentChanged:(CloudDocument *)paramSender{
self.textViewCloudDocumentText.text = paramSender.documentText;
}
- (void) handleKeyboardWillShow:(NSNotification *)paramNotification{
NSDictionary *userInfo = [paramNotification userInfo];
NSValue *animationDurationObject =
[userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey];
NSValue *keyboardEndRectObject =
[userInfo valueForKey:UIKeyboardFrameEndUserInfoKey];
double animationDuration = 0.0;
CGRect keyboardEndRect = CGRectMake(0.0f, 0.0f, 0.0f, 0.0f);
[animationDurationObject getValue:&animationDuration];
[keyboardEndRectObject getValue:&keyboardEndRect];
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
/* Convert the frame from window's coordinate system to
our view's coordinate system */
keyboardEndRect = [self.view convertRect:keyboardEndRect
fromView:window];
[UIView animateWithDuration:animationDuration animations:^{
CGRect intersectionOfKeyboardRectAndWindowRect =
CGRectIntersection(self.view.frame, keyboardEndRect);
CGFloat bottomInset =
intersectionOfKeyboardRectAndWindowRect.size.height;
self.textViewCloudDocumentText.contentInset =
UIEdgeInsetsMake(0.0f,
0.0f,
bottomInset,
0.0f);
}];
}
- (void) handleKeyboardWillHide:(NSNotification *)paramNotification{
if (UIEdgeInsetsEqualToEdgeInsets (self.textViewCloudDocumentText.contentInset,
UIEdgeInsetsZero)){
/* Our text view's content inset is intact so no need to
reset it */
return;
}
NSDictionary *userInfo = [paramNotification userInfo];
NSValue *animationDurationObject =
[userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey];
double animationDuration = 0.0;
[animationDurationObject getValue:&animationDuration];
[UIView animateWithDuration:animationDuration animations:^{
self.textViewCloudDocumentText.contentInset = UIEdgeInsetsZero;
}];
}
@end
打印:
2014-08-12 17:38:33.435 ggcloud[1244:907] Metadata query finished.
2014-08-12 17:38:33.450 ggcloud[1244:907] The Documents folder already exists in iCloud.
2014-08-12 17:38:33.502 ggcloud[1244:907] The Documents folder already exists in iCloud.
2014-08-12 17:38:33.505 ggcloud[1244:907] Document already exists in iCloud. Loading it from there...
2014-08-12 17:38:33.550 ggcloud[1244:907] Successfully loaded the document from iCloud.
22.6. Managing the State of Documents in iCloud
删除同步时产生的冲突或其他问题
UIDocumentStateChangedNotification:当iCloud文档状态发生改变时会广播这个消息。可以监听这个消息并分析其documentState属性
typedef NS_OPTIONS(NSUInteger, UIDocumentState) {
UIDocumentStateNormal = 0,
UIDocumentStateClosed = 1 << 0, // The document has either not been successfully opened, or has been since closed. Document properties may not be valid.
UIDocumentStateInConflict = 1 << 1, // Conflicts exist for the document's fileURL. They can be accessed through +[NSFileVersion otherVersionsOfItemAtURL:].
UIDocumentStateSavingError = 1 << 2, // An error has occurred that prevents the document from saving.
UIDocumentStateEditingDisabled = 1 << 3 // Set before calling -disableEditing. The document is is busy and it is not currently safe to allow user edits. -enableEditing will be called when it becomes safe to edit again.
};
UIDocumentStateNormal:一切正常,没有冲突
UIDocumentStateClosed:文档没有被正常打开,或打开后又关闭了。这种状态下应不允许用户编辑文档。建议不要用弹窗的方式提醒用户,而是在图形上显示不能编辑的标志。
UIDocumentStateInConflict:冲突。比如有多人同时编辑同一个文档。这时可以在编程上帮助解决冲突,或是让用户选择保留哪个版本。
UIDocumentStateSavingError:保存失败。当然你仍然可以不提醒用户,而只是先把它保存在本地,后续在进行提交
UIDocumentStateEditingDisabled:由于某种错误,文档不能再被编辑了。这时你也不该让用户再继续编辑文档了。
在上一节中修改和增加
- (void)viewDidLoad
{
[super viewDidLoad];
// [self listenForKeyboardNotifications];
// self.view.backgroundColor = [UIColor brownColor];
// [self setupTextView];
// [self startSearchingForDocumentIniCloud];
[self listenForDocumentStateChangesNotification];
[self listenForKeyboardNotifications];
self.view.backgroundColor = [UIColor brownColor];
[self setupTextView];
[self startSearchingForDocumentIniCloud];
}
- (void) listenForDocumentStateChangesNotification{
/* Start listening for the Document State Changes notification */
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(handleDocumentStateChanged:)
name:UIDocumentStateChangedNotification
object:nil];
}
- (void) handleDocumentStateChanged:(NSNotification *)paramNotification{
NSLog(@"Document state has changed");
NSLog(@"Notification Object = %@", [paramNotification object]);
NSLog(@"Notification Object Class = %@",
NSStringFromClass([[paramNotification object] class]));
CloudDocument *senderDocument = (CloudDocument *)paramNotification.object;
NSLog(@"Document State = %d", senderDocument.documentState);
/* Since we don't yet know how to solve conflicts, we will disallow
the user from editing the document if an error of any sort has happened.
Later when we will learn about handling conflicts, we will handle
these issues more gracefully */
if (senderDocument.documentState & UIDocumentStateInConflict){
NSLog(@"Conflict found in the document.");
self.textViewCloudDocumentText.editable = NO;
}
if (senderDocument.documentState & UIDocumentStateClosed){
NSLog(@"Document is closed.");
self.textViewCloudDocumentText.editable = NO;
}
if (senderDocument.documentState & UIDocumentStateEditingDisabled){
NSLog(@"Editing is disabled on this document.");
self.textViewCloudDocumentText.editable = NO;
}
if (senderDocument.documentState & UIDocumentStateNormal){
NSLog(@"Things are normal. We are good to go.");
self.textViewCloudDocumentText.editable = YES;
}
if (senderDocument.documentState & UIDocumentStateSavingError){
NSLog(@"A saving error has happened.");
self.textViewCloudDocumentText.editable = NO;
}
}
打印:
2014-08-13 10:40:08.270 ggcloud[1386:907] Metadata query finished.
2014-08-13 10:40:08.281 ggcloud[1386:907] The Documents folder already exists in iCloud.
2014-08-13 10:40:08.321 ggcloud[1386:907] The Documents folder already exists in iCloud.
2014-08-13 10:40:08.324 ggcloud[1386:907] Document already exists in iCloud. Loading it from there...
2014-08-13 10:40:08.474 ggcloud[1386:907] Document state has changed
2014-08-13 10:40:08.475 ggcloud[1386:907] Notification Object = <CloudDocument: 0x2081bbe0> fileURL: file://localhost/private/var/mobile/Library/Mobile%20Documents/<# Put your team ID here #>~com~gaozgao~ggcloud/Documents/UserDocument.txt documentState: [Normal]
2014-08-13 10:40:08.477 ggcloud[1386:907] Notification Object Class = CloudDocument
2014-08-13 10:40:08.478 ggcloud[1386:907] Document State = 0
2014-08-13 10:40:08.480 ggcloud[1386:907] Successfully loaded the document from iCloud.
编译文本后:
Aug 13 10:41:19 enheng ggcloud[1386] <Error>: CGContextSaveGState: invalid context 0x0
Aug 13 10:41:19 enheng ggcloud[1386] <Error>: CGContextDrawLinearGradient: invalid context 0x0
Aug 13 10:41:19 enheng ggcloud[1386] <Error>: CGContextSetFillColorWithColor: invalid context 0x0
Aug 13 10:41:19 enheng ggcloud[1386] <Error>: CGContextFillRects: invalid context 0x0
Aug 13 10:41:19 enheng ggcloud[1386] <Error>: CGContextFillRects: invalid context 0x0
Aug 13 10:41:19 enheng ggcloud[1386] <Error>: CGContextSetFillColorWithColor: invalid context 0x0
Aug 13 10:41:19 enheng ggcloud[1386] <Error>: CGContextFillRects: invalid context 0x0
Aug 13 10:41:19 enheng ggcloud[1386] <Error>: CGContextSaveGState: invalid context 0x0
Aug 13 10:41:19 enheng ggcloud[1386] <Error>: CGContextDrawLinearGradient: invalid context 0x0
Aug 13 10:41:19 enheng ggcloud[1386] <Error>: CGContextSetFillColorWithColor: invalid context 0x0
Aug 13 10:41:19 enheng ggcloud[1386] <Error>: CGContextFillRects: invalid context 0x0
Aug 13 10:41:19 enheng ggcloud[1386] <Error>: CGContextFillRects: invalid context 0x0
Aug 13 10:41:19 enheng ggcloud[1386] <Error>: CGContextSetFillColorWithColor: invalid context 0x0
Aug 13 10:41:19 enheng ggcloud[1386] <Error>: CGContextFillRects: invalid context 0x0