2011年11月1日 星期二

iCloud in iOS 5 Tutorial Part 1

since: 2011/11/03
update: 2011/11/03

reference: Beginning iCloud in iOS 5 Tutorial Part 1 | Ray Wenderlich

註: iOS simulator 目前沒有 support iCloud, 因此需要二台安裝 iOS 5
      的 iOS devices 來做測試, 並且記得啓用 iCloud 功能.

A. Enabling iCloud in your App

   1. Create an iCloud-enabled App ID
      a. 到 iOS Developer Center > Member Center (登入) > iOS Provisioning Portal
          > App IDs > New App ID
         填入:
                Description
: My iCloud Dox
                Bundle Identifier (App ID Suffix): com.blogspot.MyiCloudDox
         > submit

      b. 手動啟用 iCloud:
         (1). 按下: Configure

         (2). 勾選 "Enable for iCloud" > OK > Done
           

   ---------------------------------------------------------------------------------------------
   2. Create a provisioning profile for that App ID
      a. iOS Provisioning Portal > Provisioning > New Profile

      b. 並填入與設定以下的項目後, 按下 submit.
         Profile Name: My iCloud Dox Profile
         Certificates: Lanli Chen
         App ID: My iCloud Dox
         Devices: Lanli's iPad2, Lanli's iPod 3rd


      c. refresh 網頁, 便會看到 Status 已更新成 Active, 並可下載.

      d. 下載(檔名: My_iCloud_Dox_Profile.mobileprovision)後並 double click,
          便會安裝到 Xcode 裡.

   ---------------------------------------------------------------------------------------------
         
   3. Configure your Xcode project for iCloud
      a. 建立新專案:
         Xcode > File > New > New Project
                    > iOS > Application > Single View Application > Next
                    Product Name: MyiCloudDox
                    Company Identifier: com.blogspot
                    Device Family: Universal
                    只勾選: Use Automatic Reference Counting
                    > Next > 選擇儲存的位置 > Create

      b. 選擇專案 > target(專案名稱) > Summary > 往下 scroll 到 Entitlements 的地方.

      c. 勾選: Enable Entitlements

         說明: 在此可以不用更改預設的資料, 欄位說明如下:
                 (1). Entitlements File: 指向一個屬性清單的檔案, 該檔案包含了應用程式
                       的權利明細.
                 (2). iCloud Key-Value Store: 代表一個唯一的 ID 指向 iCloud key-value
                       儲存處.
                 (3). iCloud Containers: 代表你的應用程式可以在 Cloud 裡讀寫文件的目錄.
                       說明: 當多個應用程式由同一個 Developer Center 登入帳號所建立
                                (the same team), 它們可以共同管理 user 的 iCloud Container .
                 (4). Keychain Access Groups: 應用程式分享 keychain data 時,
                       所需要的 keys.


B. Checking for iCloud Availability
   1. 開啟 AppDelegate.m 檔案, 修改如下: (before the return YES)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    ......
    //@add: check if iCloud is available
    NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];

    if (ubiq) {
        NSLog(@"iCloud access at %@", ubiq);
        // TODO: Load document...
    } else {
        NSLog(@"No iCloud access");
    }
   
    return YES;
}

   2. 說明:
      a. ubiquity[juˋbɪkwətɪ] : 到處存在, 無所不在, 普遍存在
      b. URLForUbiquityContainerIdentifier:
         (1). 這個 method 允許傳入一個 container identifier, 並會回傳一個可在
                iCloud storage 存取檔案的 URL.
         (2). 對於每個要讓應用程式有權限存取其 URL 的 container, 都必需在一開始時
               呼叫此 method.
         (3). 如果傳入 nil (就像目前的狀況), 會自動回傳專案第一個設置的
               iCloud Container
      c. 在 iDevice 編譯並執行, 可以在 console 看到類似以下的訊息:
      d. 承上, 回傳的 URL 實際上是 iDevice 上的 local URL, 這是因為
         iCloud daemon 從 central servers 轉換檔案到 iDevice 的 local 目錄.
         因此應用程式就能夠從這個目錄取得檔案, 或發送更新的版本;
         iCloud daemon會去同步資料.



C. Subclassing UIDocument
   1. Create a new file:
      iOS > Cocoa Touch > Objective-C class > Next >
      Class: Note
      Subclass of: UIDocument
      Next > Create

   ---------------------------------------------------------------------------------------------

   2. 修改 Note.h 如下:
#import <UIKit/UIKit.h>

@interface Note : UIDocument

//@add
@property (strong) NSString * noteContent;

@end

   ---------------------------------------------------------------------------------------------

   3. 修改 Note.m 如下: (two override method)

#import "Note.h"

@implementation Note

//@add
@synthesize noteContent;

//@add
// Called whenever the application reads data from the file system
- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName
                   error:(NSError **)outError
{
   
    if ([contents length] > 0) {
        self.noteContent = [[NSString alloc]
                            initWithBytes:[contents bytes]
                            length:[contents length]
                            encoding:NSUTF8StringEncoding];       
    } else {
        // When the note is first created, assign some default content
        self.noteContent = @"Empty";
    }
   
    return YES;   
}

//@add
// Called whenever the application (auto)saves the content of a note
- (id)contentsForType:(NSString *)typeName error:(NSError **)outError
{
   
    if ([self.noteContent length] == 0) {
        self.noteContent = @"Empty";
    }
   
    return [NSData dataWithBytes:[self.noteContent UTF8String]
                          length:[self.noteContent length]];
   
}

@end

說明:
a. When we load a file we need a procedure to "transform" the NSData contents
    returned by the background queue into a string.
b. When we save we have to encode our string into an NSData object.


D. Opening an iCloud File
   1. 修改 AppDelegate.h 如下:
//@add
#import "Note.h"

//@add
@property (strong) Note *doc;
@property (strong) NSMetadataQuery *query;

//@add
- (void)loadDocument;

   ---------------------------------------------------------------------------------------------

   2. 修改 AppDelegate.m 如下:
//@add
#define kFILENAME @"mydocument.dox"
....
//@add
@synthesize doc = _doc;
@synthesize query = _query;
....

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    ....
    //@add: check if iCloud is available
    NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
    if (ubiq) {
        NSLog(@"iCloud access at %@", ubiq);
        // TODO: Load document...
        [self loadDocument];
    } else {
        NSLog(@"No iCloud access");
    }
   ....

}

//@add
- (void)loadDocument {
   
    // step01: NSMetadataQuery: represent results of a query related to the properties of an object
    NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
    _query = query;
   
    // step02: specify parameters and scope
    [query setSearchScopes:[NSArray arrayWithObject: NSMetadataQueryUbiquitousDocumentsScope]];

    //
step03: build a predicate and set it as parameter of a query/search
    // p.s.: 1. FS: file system
    //         2. %K: for keypaths used, to avoid wrapping it in quotes.
    NSPredicate *pred = [NSPredicate predicateWithFormat: @"%K == %@", NSMetadataItemFSNameKey, kFILENAME]; [query setPredicate:pred];
  
    // step04: it is an asynchronous process we need to set up an observer
    // to catch a notification when it completes.
   
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query];  

    [query startQuery];

}

   ---------------------------------------------------------------------------------------------

//@add
// add this above queryDidFinishGathering
- (void)loadData:(NSMetadataQuery *)query {
   
    // a NSMetadataQuery wraps an array of NSMetadataItems which contain the results.
    if ([query resultCount] == 1) {
        NSMetadataItem *item = [query resultAtIndex:0];
       
        // NSMetadataItemURLKey, which points to the URL that we need to build our Note instance.
        NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
       
        // When you create a UIDocument (or a subclass of UIDocument like Note),
        // you always have to use the initWithFileURL initializer and give it
        // the URL of the document to open.
        Note *doc = [[Note alloc] initWithFileURL:url];
       
        // store it away in an instance variable.
        self.doc = doc;
       
        // open a document
        [self.doc openWithCompletionHandler:^(BOOL success) {
            if (success) {               
                NSLog(@"iCloud document opened");                   
            } else {               
                NSLog(@"failed opening document from iCloud");               
            }
        }];
    }
    // When the query returns zero results
    else {
       
        // Retrieve the local iCloud directory
        NSURL *ubiq = [[NSFileManager defaultManager]
                       URLForUbiquityContainerIdentifier:nil];
       
        // Initialize an instance of document in that directory
        NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:
                                     @"Documents"] URLByAppendingPathComponent:kFILENAME];
       
        Note *doc = [[Note alloc] initWithFileURL:ubiquitousPackage];
        self.doc = doc;
       
        // Call the saveToURL method
        [doc saveToURL:[doc fileURL]
      forSaveOperation:UIDocumentSaveForCreating
     completionHandler:^(BOOL success) {   
        
         if (success) {
             [doc openWithCompletionHandler:^(BOOL success) {               
                 NSLog(@"new document opened from iCloud");               
             }];               
         }
     }];
    } // end of else

}

   ---------------------------------------------------------------------------------------------

//@add
- (void)queryDidFinishGathering:(NSNotification *)notification {
   
    // once you run a query, if you don’t stop it
    // it runs forever or until you quit the application.
    NSMetadataQuery *query = [notification object];
   
    // prevents live updates
    [query disableUpdates];
   
    // allows you to stop a process without deleting already collected results.
    [query stopQuery];
   
    // remove ourselves as an observer to ignore further notifications
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:NSMetadataQueryDidFinishGatheringNotification
                                                  object:query];
   
    _query = nil;
   
    // finally call a method to load the document, passing the NSMetadataQuery as a parameter.
    [self loadData:query];
}


E. 測試
   1. 在第一台 iDevice 執行第一次, 於 Debug area 會出現:
      2011-11-03 10:11:21.736 MyiCloudDox[335:707] new document opened from iCloud

   2. 在第一台 iDevice 執行第二次, 於 Debug area 會出現:
      2011-11-03 10:13:41.214 MyiCloudDox[361:707] iCloud document opened
     
   ---------------------------------------------------------------------------------------------

   3. 測試第二台 iDevice:
      a. 先將 AppDelegate.mloadData: method 裡以下部分 mark 起來, 再編譯執行.
- (void)loadData:(NSMetadataQuery *)query {
....
    // When the query returns zero results
    /*
    else {
    }
    */
....
}

      b. 在第二台 iDevice 編譯執行, 於 Debug area 會出現:
         2011-11-03 11:45:41.029 MyiCloudDox[271:707] iCloud document opened

沒有留言:

張貼留言

注意:只有此網誌的成員可以留言。