顯示具有 Lala's Program Note 標籤的文章。 顯示所有文章
顯示具有 Lala's Program Note 標籤的文章。 顯示所有文章

2011年12月5日 星期一

Lala's Program Note 實作記錄: 34. App 上架

since: 2011/12/05
update: 2011/12/10


App status
Waiting For Review: 2011/12/05
In Review: 2011/12/09
Processing for App Store: 2011/12/09
Ready for Sale: 2011/12/09


A. 首先登入 iTunes Connect 網站, 可以看到六大功能:
   1. Sales and Trends: 銷售趨勢報告.

   2. Contracts, Tax, and Banking: 管理銀行帳戶資訊.

   3. Payments and Financial Reports: 每個月的財政報告與支付的款項. 

   4. Manage Users: 為帳號設定存取權限.

   5. Manage Your Applications: 管理在 iTunes Store 上的 App.

   6. Contact Us: 問題查詢與提問.

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

B. 新增 App:
   1. 點選: Manage Your Applications > Add New App

   2. 輸入在 App Store 顯示的主要語言公司名稱.
       說明: 確定後即會與此帳號綁定, 以後無法修改.
       Primary Language: English
       Company Name: Lanli's Studio

   3. 設定 App 的資訊:
      App Name: Lala's Notes // App Store 上的顯示名稱, 不可重覆
      SKU Number: 001         // 用來識別你自己的不同 App, 不可更改
      Bundle ID: com.blogspot.LalaProgramNote // 須先建立好, 不可更改

   4. 設定銷售資訊:

   5. 設定 App 的詳細介紹:
      Version Number: 1.0.0 // App 的版本

      Description: A notebook with cute cat background. // App 的描述

      Primary Category: Book // App 的主要分類

      Secondary Category (optional): Productivity // App 的次要分類

      Keywords: note, notebook // App 的關鍵字

      Copyright: 2012 Lanli's Studio Inc // 所有權宣告

      Contact Email Address: lanli0210@gmail.com // Apple 聯絡我們的信箱

      Support URL: http://lala-s-notes.blogspot.com/ // 客戶支援的網站

      App URL (optional): // 關於此 App 的網站

      Review Notes (optional): // 給 Apple 評審人員的備註說明.

      Rating: // 設定 App 的分級

      上傳圖片:
      (1). App Icon: 512 x 512 pixel

      (2). iPhone and iPod touch Screenshots

      (3). iPad Screenshots


   6. 資料送出後:

--------------------------------------------------------------------------------
C. 修改 App 狀態:

   1. 回到  iTunes Connect > Manage Your Applications
      > 點選剛剛產生的 App 

     2. 目前 App 的狀態為: Prepare for Upload
        > 再點選: View Details

   3. 點選右上方的: Ready to Upload Binary

   4. 是否於 App 裡使用加密相關的功能: NO

   5. 接著出現使用 Application Loader 的說明:
If you have downloaded Xcode 3.2.5 or later, you should already have Application Loader stored here: /Developer/Applications/Utilities/Application Loader.app (or in your equivalent custom install location). If you do not find it, download and install the latest version of Xcode, which includes iOS SDK and Application Loader.

   6. App 的狀態已更新為 Waiting For Upload

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

D. 利用 Xcode 上傳 App
   1. 將 Xcode 的 Scheme 設為: iOS Devide

   2. 執行: Menu > Product > Archive
   > 允許

   3. 按下 Submit 開始上傳.

   4. 成功上傳後, iTunes Connect 上的 App 狀態即更新為: Waiting For Review.
      說明: 等了很久都還沒上傳完畢, 因此改用以下 5. 之後的方式.

   5. Archive > Share...

   > 確認是 for AppStore

   > 輸出成 .ipa 檔

   6. 接著開啟: Application Loader
      /Developer/Applications/Utilities/Application Loader.app

   7. > Deliver Your App

   8. > Choose an application

   9. > 選取剛剛產生的 .ipa

   > Send
 
   > 完成

  10. 最後, 便可在 iTunes Connect 上, 看到 App 狀態更新為: Waiting For Review.

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

E. 其它
   1. 約 3 天後, 收到 Apple 的 email 通知 App 目前為 Review 狀態.

   2. 接著, 當天不久即更新成 Ready for Sale 的狀態.

   2. 將 Availability Date 設定成今天 2011/12/09, 看看多久會生效.

   3. 狀態更新成 Pending Contract (懸而未決的契約).

   4. 上架了, 最快的是在 latvian(拉脫維亞) 國家.

5. Promo(推銷) Code 的使用
   說明:
   (1). Promotional Codes
       > You are given 50 codes for each version of an application.
         They are for non-commercial use and will expire four weeks after they
         are requested.

   (2). 方法一. 直接開啓 iPhone 上的 App Store > Featured > New
        > 點選最下面的 Redeem.

   (3). 方法二. 開啓 iTunes, 滑鼠移到到右上角顯示登入帳號右方的向下三角形符號.
        > 點選  Redeem(買回) > 接著輸入 Promo Code 即可下載 App.



2011年12月1日 星期四

Lala's Program Note 實作記錄: 33. 幫 NoteBook 的 TableView 設定背景圖片


since: 2011/12/01
update: 2011/12/02

A. 修改 NoteBookViewController.h 如下:

//@add
- (void)setBackgroundImage:(UIImage *)image;

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

B. 修改 NoteBookViewController.m 如下:


//@add
- (void)setBackgroundImage:(UIImage *)image {
    //UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,320,480)];

    UIImageView *imageView = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
   
    imageView.backgroundColor = [UIColor colorWithPatternImage:image]; 
   
    //設定圖片的透明度 值為 0.0 ~ 1.0(圖片隱藏~完全不隱藏)
    [imageView setAlpha:0.32];
   
    [self.view addSubview:imageView];
    [self.view sendSubviewToBack:imageView];
    //[self.view insertSubview:imageView atIndex:0];
   
    [self.view setOpaque:NO];
    [self.view setBackgroundColor:[UIColor clearColor]];
    //set contentMode to scale aspect to fit
    imageView.contentMode = UIViewContentModeScaleAspectFit;

    [imageView release];
}


- (void)viewDidLoad
{
    [super viewDidLoad];
   
    //@add
    SEL segmentAction = @selector(segmentAction:);
   
    self.navigationItem.rightBarButtonItem = [self segmentBarItemMakeWithSelector:segmentAction btn1Name:@"Trash" btn2Name:@"Add"];

    //@add setBackgroundImage
    [self setBackgroundImage:[UIImage imageNamed:@"pipi_NoteBook.png"]];
    [self.view setNeedsDisplay];
}

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];
}

2011年9月25日 星期日

Lala's Program Note 實作記錄: 31. 匯入預設的 Core Data 資料到專案裡

since: 2011/09/25
update: 2011/09/26


A. 準備好已儲存資料的 Core Data sqlite 檔案.
   方法 1:
   直接在模擬器的 UI 操作介面中先建好資料, 然後到 iPhone Simulator Directory 裡,
   將 app Documents 目錄下的 xxx.sqlite 檔 copy 出來.
   說明: 可先建立 iPhone Simulator Directory 的 symbolic link, 方便使用.

   方法 2:
   在實機測試時, 輸入想要預設儲存的資料, 然後配合:
   利用 iTunes 備份與回復 Core Data 資料, 將 xxx.sqlite 檔 copy 出來.

   方法 3: (未試過)
   藉由 方法 1方法 2, copy 出不含資料的空白資料庫, 然後利用:
   SQLite database browser 工具, 匯入 CSV 格式的資料到 xxx.sqlite 檔.

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

B. 將準備好的 xxx.sql 檔案加到專案裡:
   先新增一個 Resources 的 Group, 然後再將 xxx.sqlite 檔加進來.

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

C. 開啟 Lala_s_Program_NoteAppDelegate.m 檔, 修改如下:

/**
 Returns the persistent store coordinator for the application.
 If the coordinator doesn't already exist, it is created and the application's store added to it.
 */
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (__persistentStoreCoordinator != nil)
    {
        return __persistentStoreCoordinator;
    }
   
    /*
    //@add: import prepopulated sqlite data to a coredata db
     */
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    NSString *documentsDirectory = [paths objectAtIndex:0];
   
    // a. 要存放預設資料的位置, 即 app 在執行時會讀取 .sql 的地方
    NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:@"Lala_s_Program_Note.sqlite"];
    //NSLog(@"writableDBPath %@", writableDBPath);
    /*
     writableDBPath
    
     Simulator: /Users/lanli/Library/Application Support/iPhone Simulator/4.3.2/Applications/8F85FBE9-9C82-4DCD-84D5-C68173657E4C/Documents/Lala_s_Program_Note.sqlite
    
     iDevice: /var/mobile/Applications/C3F2AB7F-A296-4E9D-96C9-554E9499E1D4/Documents/Lala_s_Program_Note.sqlite
     */
   
    // b. 一開始加到專案裡的 .sql 位置, 直接就在 app 的目錄下
    NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:@"Lala_s_Program_Note" ofType:@"sqlite"];
    //NSLog(@"defalultStorePath %@", defaultStorePath);
    /*
     defalultStorePath
    
     Simulator: /Users/lanli/Library/Application Support/iPhone Simulator/4.3.2/Applications/8F85FBE9-9C82-4DCD-84D5-C68173657E4C/Lala's Program Note.app/Lala_s_Program_Note.sqlite
    
     iDevice: /var/mobile/Applications/C3F2AB7F-A296-4E9D-96C9-554E9499E1D4/Lala's Program Note.app/Lala_s_Program_Note.sqlite
     */   
   
    // c. app 在執行時讀取 .sql 的 URL地方
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Lala_s_Program_Note.sqlite"];
    //NSLog(@"store URL %@", storeURL);
    /*
     store URL
    
     Simulator: file://localhost/Users/lanli/Library/Application%20Support/iPhone%20Simulator/4.3.2/Applications/8F85FBE9-9C82-4DCD-84D5-C68173657E4C/Documents/Lala_s_Program_Note.sqlite
    
     iDevice: file://localhost/var/mobile/Applications/C3F2AB7F-A296-4E9D-96C9-554E9499E1D4/Documents/Lala_s_Program_Note.sqlite
     */
   
    // Put down default db if it doesn't already exist
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:writableDBPath]) {
        if (defaultStorePath) {
            [fileManager copyItemAtPath:defaultStorePath toPath:writableDBPath error:NULL];
        }
    }
   
    //@original:
    //NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Lala_s_Program_Note.sqlite"];
    NSError *error = nil;
    __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
    {
        . . . .
        //NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        //abort();
        //@update
        [Common showCoreDataError];
    }   
   
    return __persistentStoreCoordinator;
}

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

D. 結果圖:

2011年9月24日 星期六

Lala's Program Note 實作記錄: 30. Error Handing: Validation Errors

since: 2011/09/24
update: 2011/12/05


A. 先在 data model 裡, 限制 NoteBook 的 name 屬性與 NoteArticle 的 title 屬性
    之字串長度限制. (說明: 最小皆設為 1; 最大皆設為 25)


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

B. 實作 Validation Error Handing
   1. 開啟 Common.h 檔案, 修改如下:
      //@add
      + (NSString *)validationErrorText:(NSError *)error;


   2. 開啟 Common.m 檔案, 修改如下:
//@add
#pragma mark -
#pragma mark Validation Error Handling
+ (NSString *)validationErrorText:(NSError *)error {
    // Create a string to hold all the error messages
    NSMutableString *errorText = [NSMutableString stringWithCapacity:100];
   
    // Determine whether we're dealing with a single error or multiples, and put them all in an array
    NSArray *errors = ([error code] == NSValidationMultipleErrorsError?
    [[error userInfo] objectForKey:NSDetailedErrorsKey]:[NSArray arrayWithObject:error]);
   
    // Iterate through the errors
    for (NSError *err in errors) {
        // Get the property that had a validation error
        NSString *propName = [[err userInfo] objectForKey:@"NSValidationErrorKey"];
        NSString *message = nil;
       
        // From an appropriate error message
        switch ([err code]) {
            // Nonoptional property with a nil value   
            //case NSValidationMissingMandatoryPropertyError:
            //    message = [NSString stringWithFormat:@"%@ required", propName];
            //    break;
               
            // Some string value is too short
            case NSValidationStringTooShortError:
                // name, title: must be at least 1 character
                if ([propName isEqualToString:@"name"] || [propName isEqualToString:@"title"]) {

                    message = [NSString stringWithFormat:@"%@ must be at least %d character", propName, 1];
                }
                break;
               
            // Some string value is too long   
            case NSValidationStringTooLongError:
                // name, title: can't be longer than 25 characters
                if ([propName isEqualToString:@"name"] || [propName isEqualToString:@"title"]) {

                    message = [NSString stringWithFormat:@"%@ can't be longer than %d characters", propName, 25];
                }
                break;   
               
            default:
                message = @"Unknown error. Press Home button to halt.";
                break;
        }
       
        // Separate the error message with line feeds
        if ([errorText length] > 0) {
            [errorText appendString:@"\n"];
        }
       
        [errorText appendString:message];
    }
   
    return errorText;
}     

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

C. 新增 / 編輯 NoteBook 的 Validation 錯誤處理
   1. 開啟  NoteBookViewController.h 檔案, 修改如下:
//- (void)saveContext;
//@update: it will return error message
- (NSString *)saveContext;


//- (void)insertNoteBookWithName:(NSString *)name subName:(NSString *)subName;
//@update: When we added an object, if save failure, we can delete it by keeping a pointer to it
- (NSManagedObject *)insertNoteBookWithName:(NSString *)name subName:(NSString *)subName;


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

   2. 開啟  NoteBookViewController.m 檔案, 修改如下:
//- (void)saveContext {

//@update: it will return error message
- (NSString *)saveContext {

    NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
    NSError *error = nil;
    //@add
    NSString *errorText = nil;
   
    if (![context save:&error]) {
        //NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        //abort();
        //@add
        errorText = [Common validationErrorText:error];
    }
   
    //@add
    return errorText;
}

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

//- (void)insertNoteBookWithName:(NSString *)name subName:(NSString *)subName {
//@update: When we added an object, if save failure, we can delete it by keeping a pointer to it
- (NSManagedObject *)insertNoteBookWithName:(NSString *)name subName:(NSString *)subName {
   
    NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
    NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity];
    NSManagedObject *newNoteBook = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
   
    [newNoteBook setValue:name forKey:@"name"];
    [newNoteBook setValue:subName forKey:@"subName"];
   
     //@update
     return newNoteBook;

    //@update: handled in NoteBook.m
    //[newNoteBook setValue:[NSDate date] forKey:@"dateCreated"];
    //[newNoteBook setValue:[NSDate date] forKey:@"dateUpdate"];

    // Save the context
    //[self saveContext];
}

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

   3. 開啟  ManageNBViewController.m 檔案, 修改如下:
//@add

- (IBAction)save:(id)sender {
    //@add: trim NSString
    nameField.text = [nameField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];

    subNameField.text = [subNameField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
   
    //@add
    NSString *errorText = nil;
   
    //@add: Create variables to store pre-change state, so we can back out of if validation errors occur
    NSManagedObject *tempNoteBook = nil;
    NSString *tempName = nil;
    NSString *tempSubName = nil;
    NSDate *tempUpdateDate = nil;
   
   
    if (parenetController != nil) {
   
        /* editing */
        if (noteBook != nil) {
            //@add: User is editing an existing noteBook. Store its current values
            tempName = [NSString stringWithString:[noteBook valueForKey:@"name"]];
            tempSubName = [NSString stringWithString:[noteBook valueForKey:@"subName"]];
            tempUpdateDate = [noteBook valueForKey:@"dateUpdate"];
           
            // Update with the new values
            [noteBook setValue:nameField.text forKey:@"name"];
            [noteBook setValue:subNameField.text forKey:@"subName"];
            //now date
            [noteBook setValue:[NSDate date] forKey:@"dateUpdate"];
           
            // Save the context
            //[parenetController saveContext];
        }
        /* creating a new noteBook */
        else {
            //[parenetController insertNoteBookWithName:nameField.text subName:subNameField.text];
            //@update: User is adding a new noteBook. Create the new managed object but keep a pointer to it
            tempNoteBook = [parenetController insertNoteBookWithName:nameField.text subName:subNameField.text];
        }
       
        //@update: Save the context and gather any validation errors
        errorText = [parenetController saveContext];
    }
   
    //@add
    if (errorText != nil) {
        //@add: Validation error occurred. Show an alert
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error!" message:errorText delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
       
        [alert show];
        [alert release];
       
        // Because we had errors and the context didn't save, undo any changes this method made
        if (tempNoteBook != nil) {
            //@add: We added an object, so delete it
            [[parenetController.fetchedResultsController managedObjectContext] deleteObject:tempNoteBook];
        }
        else {
            //@add: We edited an object, so restore it to how it was
            [noteBook setValue:tempName forKey:@"name"];
            [noteBook setValue:tempSubName forKey:@"subName"];
            [noteBook setValue:tempUpdateDate forKey:@"dateUpdate"];
        }
    }
    else {
        //@add: Successful save! Dismiss the modal only on success
        [self dismissModalViewControllerAnimated:YES];
    }
   
    //[self dismissModalViewControllerAnimated:YES];
}

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

D. 新增 / 編輯 Article 的 Validation 錯誤處理
   1. 開啟  ArticleListViewController.h 檔案, 修改如下:
//- (void)saveContext;

//@update: it will return error message
- (NSString *)saveContext;

//- (void)insertArticleWithTitle:(NSString *)title subTitle:(NSString *)subTitle content:(NSString *)content;
//@update: When we added an object, if save failure, we can delete it by keeping a pointer to it
- (NSManagedObject *)insertArticleWithTitle:(NSString *)title subTitle:(NSString *)subTitle content:(NSString *)content;

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

   2. 開啟  ArticleListViewController.m 檔案, 修改如下:
//- (void)saveContext {

//@update: it will return error message
- (NSString *)saveContext {
    NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
   
    NSError *error = nil;
    //@add
    NSString *errorText = nil;
   
    if (![context save:&error]) {
        //NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        //abort();
        //@add
        errorText = [Common validationErrorText:error];
    }
   
    //@add
    return errorText;
}

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

//- (void)insertArticleWithTitle:(NSString *)title subTitle:(NSString *)subTitle content:(NSString *)content { 
//@update: When we added an object, if save failure, we can delete it by keeping a pointer to it
- (NSManagedObject *)insertArticleWithTitle:(NSString *)title subTitle:(NSString *)subTitle content:(NSString *)content { 
   
    NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
   
    NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity];
   
    NSManagedObject *newArticle = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
   
    [newArticle setValue:title forKey:@"title"];
    [newArticle setValue:subTitle forKey:@"subTitle"];
    [newArticle setValue:content forKey:@"content"];
   
    //@update: use NSSet to handle to-many relationship
    NSMutableSet *articles = [self.noteBook mutableSetValueForKey:@"articles"];
    [articles addObject:newArticle]; 
   
    //@add
    return newArticle;
   
    //@update: handled in NoteArticle.m
    //Date
    //[newArticle setValue:[NSDate date] forKey:@"dateCreated"];
    //[newArticle setValue:[NSDate date] forKey:@"dateUpdate"];
   
    //[self.noteBook setValue:newArticle forKey:@"articles"];
    // 'NSInvalidArgumentException', reason: 'Unacceptable type of value for to-many relationship: property = "articles"; desired type = NSSet; given type = NSManagedObject;
   
    //@use NSSet to handle to-many relationship
    /*
    NSMutableSet *articles = [self.noteBook mutableSetValueForKey:@"articles"];
    [articles addObject:newArticle];   
   
    // Save the context
    [self saveContext];
     */
}

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

   3. 開啟  ManageArticleViewController.m 檔案, 修改如下:
//@add

- (IBAction)save:(id)sender {
    //@add: trim NSString
    titleField.text = [titleField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
   
    subTitleField.text = [subTitleField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
   
    contentText.text = [contentText.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
   
    //@update
    NSString *errorText = nil;   
   
    //@add: Create variables to store pre-change state, so we can back out of if validation errors occur
    NSManagedObject *tempArticle = nil;
    NSString *tempTitle = nil;
    NSString *tempSubTitle = nil;
    NSString *tempContent = nil;
    NSDate *tempUpdateDate = nil;
   

    if (parenetController != nil) {

        /* editing */
        if (article != nil) {
            //@add: User is editing an existing article. Store its current values
            tempTitle = [NSString stringWithString:[article valueForKey:@"title"]];
            tempSubTitle = [NSString stringWithString:[article valueForKey:@"subTitle"]];
            tempContent = [NSString stringWithString:[article valueForKey:@"content"]];
            tempUpdateDate = [article valueForKey:@"dateUpdate"];
           
            // Update with the new values
            [article setValue:titleField.text forKey:@"title"];
            [article setValue:subTitleField.text forKey:@"subTitle"];
            [article setValue:contentText.text forKey:@"content"];
           
            //now date
            [article setValue:[NSDate date] forKey:@"dateUpdate"];
           
            // Save the context
            //[parenetController saveContext];
        }
        /* creating a new article */
        else {
            //[parenetController insertArticleWithTitle:titleField.text subTitle:subTitleField.text content:contentText.text];
            //@add: User is adding a new article. Create the new managed object but keep a pointer to it
            tempArticle = [parenetController insertArticleWithTitle:titleField.text subTitle:subTitleField.text content:contentText.text];
        }
       
        //@update: Save the context and gather any validation errors
        errorText = [parenetController saveContext];
    }
    //@add
    if (errorText != nil) {
        //@add: Validation error occurred. Show an alert
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error!" message:errorText delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
       
        [alert show];
        [alert release];
       
        //@add: Because we had errors and the context didn't save, undo any changes this method made
        if (tempArticle != nil) {
            //@add: We added an object, so delete it
            [[parenetController.fetchedResultsController managedObjectContext] deleteObject:tempArticle];
        }
        else {
            //@add: We edited an object, so restore it to how it was
            [article setValue:tempTitle forKey:@"title"];
            [article setValue:tempSubTitle forKey:@"subTitle"];
            [article setValue:tempContent forKey:@"content"];
            [article setValue:tempUpdateDate forKey:@"dateUpdate"];
        }
       
    }
    else {
        //@add: Successful save! Dismiss the modal only on success
        [self dismissModalViewControllerAnimated:YES];
    }
   
    //[self dismissModalViewControllerAnimated:YES];
}

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

E. 結果如下: