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. 結果如下:




沒有留言:

張貼留言

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