2011年11月30日 星期三

Lala's Program Note 實作記錄: 32. viewWillAppear: method 的相關處理

since: 2011/11/30
update: 2011/12/05

A. 當切回到 Favorite 或 Search Tab 時,  若文章已不存在, 則回到 navigationController
   的上一層, 不然就更新 Favorite 的點選狀態.


   => 修改  ArticleViewController.m 檔案如下:
//@add
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
   
    NSLog(@"call ArticleViewController's viewWillAppear!");
   
    if (![article valueForKey:@"title"]) {
        NSLog(@"article deleted");
        [self.navigationController popViewControllerAnimated:YES];
    }
    else
    {
        [self toggleFavoriteImage:[[article valueForKey:@"favorite"] boolValue]];
    }
}


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


B.
當切回到 Search Tab 時, 清除已不存在的 Search Result.

   => 修改  SearchViewController.m 檔案如下:

//@add
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *MyIdentifier = @"SearchCell";
   
    UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
   
    if (cell == nil)
    {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:MyIdentifier] autorelease];
    }
   
    // customize cell
    NoteArticle *noteArticle = [self.fetchedResultsController objectAtIndexPath:indexPath];
   
    //@add
    if (!noteArticle.title) {
        NSLog(@"SearchViewController's noteArticle removed");
        [self searchBarSearchButtonClicked:self.mySearchBar];
       
        return [[[UITableViewCell alloc] init] autorelease];
    }
    else
    {
        //@update
        NSMutableString *cellTitle = [[[NSMutableString alloc] init] autorelease];
        [cellTitle appendString:noteArticle.title];
        cell.textLabel.text = cellTitle;
        cell.textLabel.textColor = [UIColor brownColor];
       
        //@update
        NSMutableString *cellSubTitle = [[[NSMutableString alloc] init] autorelease];
        [cellSubTitle appendString:noteArticle.subTitle];
        cell.detailTextLabel.text = cellSubTitle;
        cell.detailTextLabel.textColor = [UIColor darkGrayColor];
       
        return cell;
    }
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSManagedObject *article = [self.fetchedResultsController objectAtIndexPath:indexPath];
   
    //@add
    if (![article valueForKey:@"title"]) {
        NSLog(@"==> SearchViewController's noteArticle removed");
    }
    else {          
        ArticleViewController *articleViewController = [[ArticleViewController alloc] init];
        articleViewController.article = article;
       
        //@relationship
        articleViewController.title = [[article valueForKey:@"notebook"] valueForKey:@"name"];
       
        //@add
        articleViewController.managedObjectContext = self.managedObjectContext;
       
        [self.navigationController pushViewController:articleViewController animated:YES]; 
       
        [articleViewController release];
    }
}

//@add
- (void)viewWillAppear:(BOOL)animated
{
    NSLog(@"call SearchViewController's viewWillAppear!");
    [super viewWillAppear:animated];
   
    [myTableView reloadData];
}

Close the app with NSTimer

A. 新增一個專案:
   Xcode > File > New > New Project...
   iOS > Application > Empty Application

   Product Name: CloseApp
   Device Family: iPhone
   checked: Use Automatic Reference Couting

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

B. 修改 AppDelegate.h 如下:
#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>
{
    //@add
    NSNumber *myNumber;
}

//@add
@property (nonatomic, strong) NSNumber *myNumber;

@property (strong, nonatomic) UIWindow *window;

//@add
- (void)setProgress;
- (void)goProgress:(NSTimer *)theTimer;

@end

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

C. 修改 AppDelegate.m 如下:
#import "AppDelegate.h"
@interface AppDelegate()
- (void)terminateWithSuccess;
@end   

@implementation AppDelegate
//@add
@synthesize myNumber = _myNumber;

//@add
-(NSNumber *)myNumber
{
    if (!_myNumber) {
        _myNumber = [[NSNumber alloc] initWithInt:0];
    }
    return _myNumber;
}

//@add
- (void)terminateWithSuccess
{
}

//@add
- (void)setProgress
{
    [NSTimer scheduledTimerWithTimeInterval:1.0f
                                     target:self
                                   selector:@selector(goProgress:)
                                   userInfo:nil
                                    repeats:YES];
}

//@add
- (void)goProgress:(NSTimer *)theTimer {
   
    int myInt = [self.myNumber intValue]+1;
    self.myNumber = [NSNumber numberWithInt:myInt];
    NSLog(@"myNumber = %@", self.myNumber);
   
    if (myInt > 10) {
        [theTimer invalidate];
       
        if ([[UIApplication sharedApplication] respondsToSelector:@selector(terminateWithSuccess)]) {

            NSLog(@"call terminateWithSuccess");

            [[UIApplication sharedApplication] performSelector:@selector(terminateWithSuccess)];

        } else {
            NSLog(@"call exit(0)");
            exit(0);
        }
    }
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
   
    //@add
    [self setProgress];
   
    return YES;
}

- (void)applicationWillTerminate:(UIApplication *)application
{
    /*
     Called when the application is about to terminate.
     Save data if appropriate.
     See also applicationDidEnterBackground:.
     */
    //@add
    NSLog(@"call applicationWillTerminate");
}

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

D. 說明:
   1. terminateWithSuccess 是 Apple 的  non-public API, 提交程式時有可能不會
       通過審核, 不過它會呼叫到 applicationWillTerminate: method.  

   2. 或直接使用 exit(0); 來離開程式, 不過它不會呼叫到 applicationWillTerminate:
        method.
 

2011年11月22日 星期二

Stanford CS193p Fall 2011 Note-05

StoryBoard:

A. create a segue:









B. Five kinds of segues:
   1. Push - It is the kind of segue you use when the two Controllers are inside a
       UINavigationController.
   2. Replace - Replaces the right-hand side of a UISplitViewController (iPad only)
   3. Popover - Puts the view controller on the screen in a popover (iPad only)
   4. Modal - Puts the view controller up in a way that blocks the app until it is
       dismissed
   5. Custom - You can create your own subclasses of UIStoryboardSegue

2011年11月19日 星期六

Stanford CS193p Fall 2011 Note-04

Instance vs. Class Methods

Instance Methods:
1. Starts with a dash:
   - (BOOL)dropBomb:(Bomb *)bomb
                                    at:(CGPoint)position
                               from:(double)altitude;

2. "Normal" Instance Methods

3. Calling syntax:
     [<pointer to instance> method]
     e.g.
     Ship *ship = ...; // instance of a Ship
     destroyed = [ship dropBomb:firecracker
                                                      at:dropPoint
                                               from:300.0];

4. self/super is calling instance
    self means "my implementation"
    super means "my superclass's implementation"

Class Methods:
1. Starts with a plus sign:
   + (id) alloc;
   + (Ship *)motherShip;
   + (NSString *)stringWithFormat:...

2. Creation & Utility Methods

3. Calling syntax:
    [Class method]
    e.g.
    Ship *ship = [Ship motherShip];
    NSString *resultString =
    [NSString stringWithFormat:@"%g", result];
    [[ship class] doSomething];

4. self/super is this class
    self means "this class's class methods"
    super means "this class's superclass's class methods"

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

Coordinates
1. bounds // @property CGRect bounds;
   a. your view's internal drawing space's origin and size
   b. The bounds property is what you use inside your view's own implementation.
   c. It is up to your implementation as to how to interpret the meaning of
       bounds.origin.
       e.g. View B's bounds = ((0,0),(200,250)) // ((x, y), (width, height))  

2. center // @property CGPoint center;
    the center of your view in your superview's coordinate space
     e.g. View B's center = (300,225) // (x, y)

3. frame // @property CGRect frame;
    a rectangle in your superview's coordinate space which entirely contains your
    view's bounds.size
    e.g. View B's frame = ((140,65),(320,320)) // ((x, y), (width, height))

4. Use frame and center to position the view in the hierarchy
    a. These are used by superviews, never inside your UIView subclass's
        implementation.
    b. You might think frame.size is always equal to bounds.size, but you'd be wrong ...
        Because views can be rotated (and scaled and translated too).

2011年11月18日 星期五

Stanford CS193p Fall 2011 Note-03

strong vs weak

1. strong "keep this in the heap until I don't point to it anymore"
    I won't point to it anymore if I set my pointer to it to nil.
    Or if I myself am removed from the heap because no one strongly points to me!

2. weak "keep this as long as someone else points to it strongly"
    If it gets thrown out of the heap, set my pointer to it to nil automatically
    (if user on iOS 5 only).

3. This is not garbage collection!
     It's way better. It's reference counting done automatically for you.

4. Finding out that you are about to leave the heap
    A special method, dealloc, is called on you when your instance's memory is freed
    from the heap. You will rarely ever have to implement this method. It's  "too late"
    to do much useful here.

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

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

nil

1. The value of an object pointer that does not point to anything
    id obj = nil;
    NSString *hello = nil;

2. Like "zero" for a primitive type (int, double, etc.)
    Actually, it's not "like" zero: it is zero.

3. All instance variables start out set to zero
     Thus, instance variables that are pointers to objects start out with the value of nil.

4. Can be implicitly tested in an if statement
   if (obj) { } // curly braces will execute if obj points to an object
   // i.e. if (obj != nil) { }

5. Sending messages to nil is (mostly) okay. No code gets executed.
   If the method returns a value, it will return zero.
   int i = [obj methodWhichReturnsAnInt]; // i will be zero if obj is nil

6. Be careful if the method returns a C struct. Return value is undefined.
    CGPoint p = [obj getLocation]; // p will have an undefined value if obj is nil

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

2011年11月17日 星期四

Stanford CS193p Fall 2011 Note-02

直接從 UI 新增 property 的方式:
=> 按住 "control" , 點選 UI 上的元件(在此為 UILabel), 將其拖拉到
      對應的 Controller.h 程式區塊內放開.




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

直接從 UI 新增 action 的方式:
=> 按住 "control" , 點選 UI 上的元件(在此為 UIButton), 將其拖拉到
      對應的 Controller.m 程式區塊內放開.



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

@synthesize 的對象為物件時, 通常可以藉由更改其 getter 來作初始化的動作.
ex:

@property (nonatomic, strong) NSMutableArray *operandStack;
....
@synthesize operandStack = _operandStack;
....

- (NSMutableArray *) operandStack
{
    if (_operandStack == nil) {
        _operandStack = [[NSMutableArray alloc] init];
    }
   
    return _operandStack;
}

2011年11月16日 星期三

Stanford CS193p Fall 2011 Note-01

參考: iPad and iPhone Application Development (HD) - Download free content from Stanford on iTunes

MVC:


-----------------------------------------------------------------------------
private methods:

-----------------------------------------------------------------------------
nonatomic:
@property nonatomic means its setter and getter are not thread-safe.
That's no problem if this is UI code because all UI code happens
on the main thread of the application.

-----------------------------------------------------------------------------
synthesize:
ex: @synthesize topSpeed = _topSpeed;

1. We almost always use @synthesize to create the implementation of the setter
   and getter for a @property. It both creates the setter and getter methods AND
   creates some storage to hold the value.

2. _topSpeed
  (1). This is the name of the storage location to use.
  (2). _ (underbar) then the name of the property is a common naming convention.
 
3. If we don't use = here, @synthesize uses the name of the property
   (which is bad so always use =).

4. This is what the methods created by @synthesize would look like:
- (void)setTopSpeed:(double)speed
{
    _topSpeed = speed;
}
- (double)topSpeed
{
    return _topSpeed;
}

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

private property:
ex:
// in Spaceship.m
@interface Spaceship()
// declaration of private methods (as needed)
@property (nonatomic, strong) Wormhole *nearestWormhole;
@end

@synthesize nearestWormhole = _nearestWormhole;

1. strong:
   It's a pointer to an object (of class Wormhole). It's strong which means that the
   memory used by this object will stay around for as long as we need it.

2. *nearestWormhole:
    All objects are always allocated on the heap. So we always access them through
    a pointer. Always.

3. @synthesize:
    does NOT create storage for the object this pointer points to.
    It just allocates room for the pointer.

2011年11月12日 星期六

ARC(Automatic Reference Counting) in iOS 5

since: 2011/11/12
update: 2011/11/12

references:
Beginning ARC in iOS 5 Tutorial Part 1 | Ray Wenderlich


   1. 當在專案中使用 ARC 時, 將不再使用 retain, releaseautorelease;
      因為啟用 ARC 功能後, 編譯器將會為你處理這些問題.

   2. "strong" 指標: 可保持讓物件存活; 預設所有的 instance variableslocal variables
       都是 strong pointers.

   3. "weak" 指標: 仍然會指向物件, 但是沒有擁有權.
       a. 當 weak pointer 指向的物件被釋放後, weak pointer 本身變成 nil, 稱為:
           "zeroing" weak pointer; 因而不會產生 "dangling pointers" 或 "zombies" 的問題.

       b. weak pointers 的常用時機: 當二個物件處於 parent-child 關係.
          => parent 物件有一個 strong pointer 指向 child 物件, 因此擁有 child 物件;
              但是為了防止擁有權的無盡循環, child 物件只有一個 weak pointer 指回
              parent 物件.
          ex: delegate pattern
              (1). view controller: may own a UITableView through a strong pointer.
              (2). The table view's data source and delegate pointers point back
                    at the view controller, but are weak.

   4. assigning retained object to weak variable;
      object will be released after assignment.
      ex:
      __weak NSString *str = [[NSString alloc] initWithFormat:...];
      NSLog(@"%@", str); // will output "(null)"

   5. ARC only works on Objective-C objects. If your app uses Core Foundation
      or malloc() and free(), then you're still responsible for doing the memory
      management there.

2011年11月7日 星期一

在 Blogspot 上安裝 Objective-C 的 Syntax Highlighting

since: 2011/11/07
update: 2011/11/07

references:
1. JumpTheBox: Syntax Highlighting Objective C code on your blog
2. How To Add Syntax Highlighter for Blogger/Blogspot

A. 先到 SyntaxHighlighter 網站, 下載最新的版本: 3.0.83
    解壓縮後先 copy 三個檔案: shCore.css, shThemeDefault.css
    與 shCore.js

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

B. 接著, 到 Scott Densmore: Objective C Syntax Highlighter Reboot 下載:
    Objective C Syntax Highlighter , 解壓縮後 copy 檔案: shBrushObjC.js  

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

C. 到 google site , 先建立 "協作平台":

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


D. 接著, 新增頁面: 使用檔案櫃當作範本

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

E. 新增檔案: 上傳剛剛 copy 的 4 個檔案

   說明: 此時我們可以得到 4 個去掉 ? 參數後面的檔案連結: (同時將 https 改成 http)
           http://sites.google.com/site/lanli0210/blogspot/shBrushObjC.js
           http://sites.google.com/site/lanli0210/blogspot/shCore.css
           http://sites.google.com/site/lanli0210/blogspot/shCore.js
           http://sites.google.com/site/lanli0210/blogspot/shThemeDefault.css

---------------------------------------------------------------------------------------------
F. 開始修改 blogspot:
   blogspot > 設計 > 修改 HTML >
   在 <head> 標籤下, 新增以下的內容:
  
<!-- start for Syntax Highlighting -->

   <link href='http://sites.google.com/site/lanli0210/blogspot/shCore.css' rel='stylesheet' type='text/css'/>
   <link href='http://sites.google.com/site/lanli0210/blogspot/shThemeDefault.css' rel='stylesheet' type='text/css'/>
   <script src='http://sites.google.com/site/lanli0210/blogspot/shCore.js' type='text/javascript'/>
   <script src='http://sites.google.com/site/lanli0210/blogspot/shBrushObjC.js' type='text/javascript'/>
  
   <script type='text/javascript'>
   SyntaxHighlighter.config.bloggerMode = true;

   SyntaxHighlighter.all();
   </script>

<!--  end for Syntax Highlighting -->  
 

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

G. 使用方式:(不知道哪裡出問題, 沒有效果)
    在程式碼的外圍加上以下的 <pre> 標籤:   
  
   <pre class="brush: objc; toolbar: false;">

<pre class="brush: objc; toolbar: false;">
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {  
    
    // Override point for customization after application launch.
    
    // Converting RGBA color for use with UIColor
    UIColor *colorFromRgba = [UIColor colorWithRed:23/255.0f green:45/255.0f blue:145/255.0f alpha:1];
    NSLog(@"converted rgba color is: %@", colorFromRgba);
                             
    [self.window makeKeyAndVisible];
    
    return YES;
}</pre>

iCloud in iOS 5 Tutorial Part 2

since: 2011/11/07
update: 2011/11/07

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

A. Setting Up the User Interface
   1. 修改 ViewController.h 如下:
#import <UIKit/UIKit.h>
//@add
#import "Note.h"

//@interface ViewController : UIViewController
//@update: marked the view controller as implementing UITextViewDelegate
// so that we can receive events from the text view
@interface ViewController : UIViewController <UITextViewDelegate>

//@add
@property (strong) Note * doc;
@property (weak) IBOutlet UITextView * noteView;

@end

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

   2. 開啟 ViewController_iPhone.xib 檔案, 修改如下:
      a. 拖拉一個 Text View 讓它蓋滿整個區域.

      b. 將 File's Owner 上的 noteView outlet 與 UI 上的 Text View 做連結.

      c. 點選 UI 上的 Text View, 將其 delegateFile's Owner  做連結. 

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

   3. 同樣地, 開啟 ViewController_iPad.xib 檔案, 修改如下:
      a. 拖拉一個 Text View 讓它蓋滿整個區域.
      b. 將 File's Owner 上的 noteView outlet 與 UI 上的 Text View 做連結.
      c. 點選 UI 上的 Text View, 將其 delegateFile's Owner  做連結. 

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

   4. 修改 ViewController.m 如下:
//@add
@synthesize doc;
@synthesize noteView;

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //@add: register for the notification our code will send when our document changes
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(dataReloaded:)
                                                 name:@"noteModified" object:nil];
}

//@add: implement the method that gets called when the notification is received
- (void)dataReloaded:(NSNotification *)notification {
   
    self.doc = notification.object;
    self.noteView.text = self.doc.noteContent;
}

//@add: notify iCloud when the document changes
- (void)textViewDidChange:(UITextView *)textView {
   
    self.doc.noteContent = textView.text;
    [self.doc updateChangeCount:UIDocumentChangeDone];
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    //@add: modify the app to refresh the data
    self.noteView.text = self.doc.noteContent;
}

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

   5. 修改 Note.m 如下:
- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName
                   error:(NSError **)outError
{
    ....
    //@add: add the code to send the “noteModified” notification we registered for in viewDidLoad.
    [[NSNotificationCenter defaultCenter]
     postNotificationName:@"noteModified"
     object:self];
   
    return YES;   
}



B. 測試:
   在二台 iDevice 安裝此 app 與執行; 在其中一台 iDevice 編輯資料, 約 5 ~ 30 秒
   便會在另一台 iDevice 更新資料.

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