iCloud目前只能在真实设备中测试,不能在Simulator中测试Display Set在iTunesConnect中新建"iCloud Manage Display Sets",iCloud的文档和数据存储在display sets中。多个应用可以引用和存储数据到同一个display set。(iTunesConnect手册上有这一步,但是很多教程,包括iOS App Programming Guide也没有提到,可能不需要了)APP ID 和 开发证书
iOS Provisioning Portal中,创建启用iCloud的App ID,现有的App ID也可以编辑并启用iCloud,App ID必须不包含通配符"*"针对上面的App ID,创建新的Development Provisioning Profile,下载后双击使用Xcode打开完成安装,并设置Code Signing。Xcode entitlements在Xcode中配置应用的iCloud Entitlements,Target->Summary->Entitlements,勾选后Xcode会自动生成iCloud Key-Value Store: com.company.AppiCloud Containers: com.company.AppKeychain Access Groups: com.company.App这里Xcode自动生成的Entitlements使用了通配符,我们需要手工修改PROJECT.Entitlements文件:iCloud Key-Value Store: TeamIdentifier.com.company.AppiCloud Containers: TeamIdentifier.com.company.AppKeychain Access Groups: ApplicationIdentifierPrefix.com.company.App其中TeamIdentifier是申请 IDP 时拿到的标识,可以在MemberCenter -> Your Account -> Account Summary中找到(http://developer.apple.com/membercenter/index.action
)ApplicationIdentifierPrefix 则是注册App ID时使用的前缀。其实简单的办法是用编辑器打开上面下载的Development Provisioning Profile,直接在里面就可以找到这两个字符串。源代码定义Ubiquity Container URL定义一个常量字符串,我们代码里面使用它来构造iCloud storage的URL,实际上和Entitlement中定义的字符串是一样的。#define UBIQUITY_CONTAINER_URL @"ABCDEF12345.com.yourdomain.icloudappUIDocumentiOS 5为iCloud引入的file presenter,创建和管理文档及其内容,极大地方便了开发。对本地文件使用UIDocument也能带来一些好处。如后台队列的异步读写、处理版本冲突、自动文档保存等。继承UIDocumentcontentsForType:error:UIDocument对象将数据写入文件或文档时调用,这个方法负责收集要写入的数据,并返回NSData或NSFileWrapper对象。loadFromContents:ofType:error:UIDocument对象从文件或文档中读取数据时调用,并传递从文件或文档中已经读取到的数据给这个方法。方法负责把这些数据装载到应用的内部数据model文档状态和冲突解决文档状态:UIDocumentStateNormal – 文档已打开并允许用户编辑UIDocumentStateClosed – 文档当前已关闭,读取文档时出错也可能是这个状态UIDocumentStateInConflict – 检测到文档的多个版本冲突UIDocumentStateSavingError – 试图保存文档时出错UIDocumentStateEditingDisabled – 文档繁忙,当前编辑不安全文档状态变化时会发送 UIDocumentStateChangedNotification 通知,注册这个通知就能监测文档状态,及时处理出错、冲突等。解决冲突:- 如果可行,合并冲突版本
- 如果不丢失数据,丢弃冲突版本
- 提示用户,选择要保留的版本
iCloud理想情况下,应该允许用户指定哪些文件存储在iCloud,哪些文件存储在本地之前已经添加到iCloud的文档,不能使用绝对路径来访问!应用应该通过名字在iCloud storage中查找文档。存储到iCloud的文档,应该放在应用的Documents目录使用 URLForUbiquityContainerIdentifier: 方法检测iCloud是否可用,传递已经定义好的Container URL,也可以传递nil,表示Entitlements中定义的第一个URL。方法返回nil表示iCloud不可用,返回URL表示可用。拿到这个URL后,再对它调用 URLByAppendingPathComponent:@"Documents" 来组建一个路径。默认情况下,当前应用的iCloud storage中并没有这个Documents目录,因此我们需要首先创建它。然后再使用 NSMetadataQuery 查找iCloud中是否存在我们要的文档,查找过程会在另一个线程中进行,完成后会通过 NSMetadataQueryDidFinishGatheringNotification 通知告诉你注册的observer本地文件移动到iCloud调用 setUbiquitous: itemAtURL: 方法,传递YES表示本地文件移动到iCloud,传递NO则表示反向移动。设备配置在 Settings 应用中启用设备的iCloud Document and Data Storage,运行程序测试,此时可以在 Settings 应用中看到已经上传到iCloud中的文档完整例子MyDocument.h@interface MyDocument : UIDocument{ NSString *userText;}@property (strong, nonatomic) NSString *userText;MyDocument.m-(id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError{ return [NSData dataWithBytes:[self.userText UTF8String] length:[self.userText length]];}-(BOOL) loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError { if ( [contents length] > 0) { self.userText = [[NSString alloc] initWithBytes:[contents bytes] length:[contents length] encoding:NSUTF8StringEncoding]; } else { self.userText = @""; } return YES;}iCloudStoreViewController.h@interface iCloudStoreViewController : UIViewController{ MyDocument *document; NSURL *documentURL; NSURL *ubiquityURL; UITextView *textView; NSMetadataQuery *metadataQuery;}@property (strong, nonatomic) IBOutlet UITextView *textView;@property (strong, nonatomic) NSURL *documentURL;@property (strong, nonatomic) MyDocument *document;@property (strong, nonatomic) NSURL *ubiquityURL;@property (strong, nonatomic) NSMetadataQuery *metadataQuery;-(IBAction)saveDocument;iCloudStoreViewController.m- (void)saveDocument{ self.document.userText = textView.text; [self.document saveToURL:ubiquityURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) { if (success){ NSLog(@"Saved to cloud for overwriting"); } else { NSLog(@"Not saved to cloud for overwriting"); } }];}- (void)viewDidLoad{ [super viewDidLoad]; NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *docsDir = [dirPaths objectAtIndex:0]; NSString *dataFile = [docsDir stringByAppendingPathComponent: @"document.doc"]; self.documentURL = [NSURL fileURLWithPath:dataFile]; NSFileManager *filemgr = [NSFileManager defaultManager]; ubiquityURL = [[filemgr URLForUbiquityContainerIdentifier:UBIQUITY_CONTAINER_URL] URLByAppendingPathComponent:@"Documents"]; NSLog(@"iCloud path = %@", [ubiquityURL path]); if ([filemgr fileExistsAtPath:[ubiquityURL path]] == NO) { NSLog(@"iCloud Documents directory does not exist"); [filemgr createDirectoryAtURL:ubiquityURL withIntermediateDirectories:YES attributes:nil error:nil]; } else { NSLog(@"iCloud Documents directory exists"); } ubiquityURL = [ubiquityURL URLByAppendingPathComponent:@"document.doc"]; NSLog(@"Full ubiquity path = %@", [ubiquityURL path]); // Search for document in iCloud storage metadataQuery = [[NSMetadataQuery alloc] init]; [metadataQuery setPredicate: [NSPredicate predicateWithFormat: @"%K like 'document.doc'", NSMetadataItemFSNameKey]]; [metadataQuery setSearchScopes:[NSArray arrayWithObjects:NSMetadataQueryUbiquitousDocumentsScope,nil]]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(metadataQueryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:metadataQuery]; NSLog(@"starting query"); [metadataQuery startQuery];}- (void)metadataQueryDidFinishGathering:(NSNotification *)notification{ NSMetadataQuery *query = [notification object]; [query disableUpdates]; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:query]; [query stopQuery]; NSArray *results = [[NSArray alloc] initWithArray:[query results]]; if ([results count] == 1) { NSLog(@"File exists in cloud"); ubiquityURL = [[results objectAtIndex:0] valueForAttribute:NSMetadataItemURLKey]; self.document = [[MyDocument alloc] initWithFileURL:ubiquityURL]; //self.document.userText = @""; [document openWithCompletionHandler: ^(BOOL success) { if (success){ NSLog(@"Opened cloud doc"); textView.text = document.userText; } else { NSLog(@"Not opened cloud doc"); } }]; } else { NSLog(@"File does not exist in cloud"); self.document = [[MyDocument alloc] initWithFileURL:ubiquityURL]; [document saveToURL:ubiquityURL forSaveOperation: UIDocumentSaveForCreating completionHandler:^(BOOL success) { if (success){ NSLog(@"Saved to cloud"); } else { NSLog(@"Failed to save to cloud"); } }]; }}