Core Data on iOS 5 Tutorial: How To Work with Relations and Predicates

This is a tutorial where you’ll learn how to work with predicates and relationships in Core Data. It is written by iOS Tutorial Team member Cesare Rocchi, a UX designer and developer specializing in web and mobile applications. Good news – by popular request, we now have a 4th part to our Core Data tutorial […] By Cesare Rocchi.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 6 of this article. Click here to view the first page.

Editing Banks

At this point, new banks are added using “static” content. Now you’ll incorporate the ability to edit the details of a bank. To handle this, you’ll build a new view controller.

Right-click on the project root and select New File/Cocoa Touch/Objective-C Class. Name the controller SMBankDetailViewController and make it a subclass of UIViewController. Also make sure that a XIB is generated.

Now open SMBankDetailViewController.xib to add a few components. The view will be pushed by a view controller, so you might want to visualize the space taken by a navigation bar. With the view selected, tap the fourth icon in the inspector (the right sidebar) and set the top bar to “Navigation Bar.”

Then drag four text fields and two labels to the view, and lay them out as in the following screenshot:

You might want to edit the placeholder text of the input fields so it’s clear what’s what – you can use the above screenshot as a reference. Later on in this tutorial, you’ll set the tags on the labels as well.

While you’re at it, add a date picker component. This will be displayed when necessary via code to edit dates.

For the moment, place the picker outside of the visible area of the view. The Y of the picker should be set to 420. With the picker selected, switch to the Size Inspector tab on the right sidebar and set its position as follows:

Now it’s time to write some code to display the new view. In SMBankDetailViewController.h, add the following import statements below the existing #import line:

#import "FailedBankInfo.h"
#import "FailedBankDetails.h"

Then add the following properties and methods before the final @end:

@property (nonatomic, strong) FailedBankInfo *bankInfo;
@property (nonatomic, weak) IBOutlet UITextField *nameField;
@property (nonatomic, weak) IBOutlet UITextField *cityField;
@property (nonatomic, weak) IBOutlet UITextField *zipField;
@property (nonatomic, weak) IBOutlet UITextField *stateField;
@property (nonatomic, weak) IBOutlet UILabel *tagsLabel;
@property (nonatomic, weak) IBOutlet UILabel *dateLabel;
@property (nonatomic, weak) IBOutlet UIDatePicker *datePicker;

-(id)initWithBankInfo:(FailedBankInfo *) info;

Next, in SMBankDetailViewController.xib, hook up each outlet you defined with its corresponding component. You can do this by selecting the File’s Owner (which is the SMBankDetailViewController class), switching to the Connections Inspector in the right sidebar, and dragging from each outlet to the relevant control on the view.

At the top of SMBankDetailViewController.m, right below the @implementation line, synthesize all the properties as follows:

@synthesize bankInfo = _bankInfo;
@synthesize nameField;
@synthesize cityField;
@synthesize zipField;
@synthesize stateField;
@synthesize tagsLabel;
@synthesize dateLabel;
@synthesize datePicker;

Also add two private methods to the class continuation above the @implementation line, as follows:

@interface SMBankDetailViewController ()
-(void)hidePicker;
-(void)showPicker;
@end

initWithBankInfo: is pretty simple: it assigns an instance of info to the view controller. Add it below the existing initWithNibName:bundle: method implementation:

-(id)initWithBankInfo:(FailedBankInfo *)info {
    if (self = [super init]) {
        _bankInfo = info;
    }
    return self;
}

Then add the following to the end of viewDidLoad to set a few parameters like the title, the right navigation item and a few gesture recognizers:

    self.title = self.bankInfo.name;
    
    // 1 - setting the right button
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave 
        target:self action:@selector(saveBankInfo)];
    
    // 2 - setting interaction on date label
    self.dateLabel.userInteractionEnabled = YES;
    UITapGestureRecognizer *dateTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self 
        action:@selector(dateTapped)];
    [self.dateLabel addGestureRecognizer:dateTapRecognizer];
    // 3 - set date picker handler
    [datePicker addTarget:self action:@selector(dateHasChanged:)
         forControlEvents:UIControlEventValueChanged];

In section 1, the right button on the navigation bar triggers a save action. The operation in this case is easy: set the values of the bank info as the values specified in the components, and save the context. Add the code for it to the end of the file (but before the final @end):

-(void)saveBankInfo {

    self.bankInfo.name = self.nameField.text;
    self.bankInfo.city = self.cityField.text;
    self.bankInfo.details.zip = [NSNumber numberWithInt:[self.zipField.text intValue]];
    self.bankInfo.state = self.stateField.text;
    
    NSError *error;
    if ([self.bankInfo.managedObjectContext hasChanges] && ![self.bankInfo.managedObjectContext save:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    } 
    
    [self.navigationController popViewControllerAnimated:YES];
    
}

The second tap detector (in section 2 above) calls the method showPicker. Add it to the end of the file:

-(void)dateTapped {
    [self showPicker];
}

The third selector (section 3) changes the value of the date label according to the selected value in the date picker. Add the necessary code again to the end of the file:

-(void)dateHasChanged:(id)sender {
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateStyle:NSDateFormatterMediumStyle];
    self.dateLabel.text = [formatter stringFromDate:self.datePicker.date];
    
}

viewWillAppear: sets the values of the text fields and labels according to the instance of bank info associated with the controller. Add the following code below viewDidLoad:

-(void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    // setting values of fields
    self.nameField.text = self.bankInfo.name;
    self.cityField.text = self.bankInfo.city;
    self.zipField.text = [self.bankInfo.details.zip stringValue];
    self.stateField.text = self.bankInfo.state;
    
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateStyle:NSDateFormatterMediumStyle];
    self.dateLabel.text = [formatter stringFromDate:self.bankInfo.details.closeDate];
}

Finally, you’re left with the implementation of the two private methods. Add the code for those to the end of the file:

-(void)showPicker {
    [self.zipField resignFirstResponder];
    [self.nameField resignFirstResponder];
    [self.stateField resignFirstResponder];
    [self.cityField resignFirstResponder];
    
    [UIView beginAnimations:@"SlideInPicker" context:nil];
	[UIView setAnimationDuration:0.5];
	self.datePicker.transform = CGAffineTransformMakeTranslation(0, -216);
	[UIView commitAnimations];
}

-(void)hidePicker {
    [UIView beginAnimations:@"SlideOutPicker" context:nil];
	[UIView setAnimationDuration:0.5];
	self.datePicker.transform = CGAffineTransformMakeTranslation(0, 216);
	[UIView commitAnimations];
}

The above code shows or hides the date picker, as necessary. Before showing the date picker, the first responder for all text fields is resigned, thus effectively dismissing the keyboard if it was visible.

To test your new view, you need to push it onto the navigation stack when a cell is tapped. In FBCDMasterViewController.m, add the following import statement at the top:

#import "SMBankDetailViewController.h"

Then replace the existing placeholder for tableView:didSelectRowAtIndexPath: with the following:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    FailedBankInfo *info = [_fetchedResultsController objectAtIndexPath:indexPath];
    SMBankDetailViewController *detailViewController = [[SMBankDetailViewController alloc] initWithBankInfo:info];
    [self.navigationController pushViewController:detailViewController animated:YES];
}

Now you’re ready to see the first big change to your app!

Run the app and create a few instances of banks. Each bank record is editable, including the close date. To save data, hit the save button; to discard changes, just tap the back button.

Notice that the date picker and the keyboard never obstruct each other. Changes are reflected in the list of banks with no need to refresh the table view. Pretty cool, huh?

Contributors

Over 300 content creators. Join our team.