iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) (39 page)

Read iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) Online

Authors: Aaron Hillegass,Joe Conway

Tags: #COM051370, #Big Nerd Ranch Guides, #iPhone / iPad Programming

BOOK: iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides)
13.05Mb size Format: txt, pdf, ePub
 

Build and run the application to see the interface. Ignore the incomplete implementation warnings; you’ll implement
toggleEditingMode:
and
addNewItem:
shortly. Until you do, tapping a button will generate an exception because the action methods of the buttons are not implemented.

 

While XIB files are typically used to create the view for a view controller (for example,
TimeViewController.xib
), you’ve now seen that a XIB file can be used any time you wish to archive view objects. In addition, any object can load a XIB file manually by sending the message
loadNibNamed:owner:options:
to the application bundle.

 

UIViewController
’s default XIB loading behavior uses the same code. The only difference is that it connects its
view
outlet to the view object in the XIB file. Imagine what the default implementation of
loadView
for
UIViewController
probably looks like:

 
- (void)loadView
{
    // If a nibName was passed to initWithNibName:bundle:
    if ([self nibName]) {
        // Load that nib file, with ourselves as the file's owner, thus connecting
        // the view outlet to the view in the nib
        [[NSBundle mainBundle] loadNibNamed:[self nibName] owner:self options:nil];
    }
    else {
        // What is the name of this class?
        NSString *className = NSStringFromClass([self class]);
        // What's the full path of the nib file?
        NSString *nibPath = [[NSBundle mainBundle] pathForResource:className
                                                            ofType:@"nib"];
        // If there really is a nib file at that path, load it
        if ([[NSFileManager defaultManager] fileExistsAtPath:nibPath]) {
            [[NSBundle mainBundle] loadNibNamed:className owner:self options:nil];
        }
        else {
            // If there is no nib, just create a blank UIView
            UIView *view = [[UIView alloc] initWithFrame:CGRectZero];
            [self setView:view];
        }
    }
}
 

Now let’s implement the
toggleEditingMode:
method. We could toggle the
editing
property of
UITableView
directly. However,
UITableViewController
also has an
editing
property. A
UITableViewController
instance automatically sets the
editing
property of its table view to match its own
editing
property. Which one should we set? Follow the Model-View-Controller pattern: talk to the controller and let the controller talk to the view.

 

To set the
editing
property for a view controller, you send it the message
setEditing:animated:
. In
ItemsViewController.m
, implement
toggleEditingMode:
.

 
- (IBAction)toggleEditingMode:(id)sender
{
    // If we are currently in editing mode...
    if ([self isEditing]) {
        // Change text of button to inform user of state
        [sender setTitle:@"Edit" forState:UIControlStateNormal];
        // Turn off editing mode
        [self setEditing:NO animated:YES];
    } else {
        // Change text of button to inform user of state
        [sender setTitle:@"Done" forState:UIControlStateNormal];
        // Enter editing mode
        [self setEditing:YES animated:YES];
    }
}
 

Build and run your application, tap the
Edit
button, and the
UITableView
will enter editing mode (
Figure 10.7
).

 

Figure 10.7  UITableView in editing mode

 
Adding Rows

There are a number of ways to add rows to a table view at runtime. The built-in behavior for adding a row is to display a new row with a green plus sign icon. However, this technique has fallen out of favor because it’s cumbersome to enter editing mode and then find the row with the plus sign icon – especially in large tables.

 

So we’re going to use the
New
button in the header view instead. When this button is tapped, a new row will be added to the
UITableView
. In
ItemsViewController.m
, implement
addNewItem:
.

 
- (IBAction)addNewItem:(id)sender
{
    // Make a new index path for the 0th section, last row
    int lastRow = [[self tableView] numberOfRowsInSection:0];
    NSIndexPath *ip = [NSIndexPath indexPathForRow:lastRow inSection:0];
    // Insert this new row into the table.
    [[self tableView] insertRowsAtIndexPaths:[NSArray arrayWithObject:ip]
                            withRowAnimation:UITableViewRowAnimationTop];
}
 

Build and run the application. Tap the
New
button and... the application crashes. The console tells us that the table view has an internal inconsistency exception.

 

Remember that, ultimately, it is the
dataSource
of the
UITableView
that determines the number of rows the table view should display. After inserting a new row, the table view has six rows (the original five plus the new one). Then, it runs back to its
dataSource
and asks it for the number of rows it should be displaying.
ItemsViewController
consults the store and returns that there should be five rows. The
UITableView
then says,

Hey, that’s not right!

and throws an exception.

 

You must make sure that the
UITableView
and its
dataSource
agree on the number of rows. Thus, you must also add a new
BNRItem
to the
BNRItemStore
before you insert the new row. Update
addNewItem:
in
ItemsViewController.m
.

 
- (IBAction)addNewItem:(id)sender
{
    
int lastRow = [[self tableView] numberOfRowsInSection:0];
    
// Create a new BNRItem and add it to the store
    BNRItem *newItem = [[BNRItemStore sharedStore] createItem];
    // Figure out where that item is in the array
    int lastRow = [[[BNRItemStore sharedStore] allItems] indexOfObject:newItem];
    NSIndexPath *ip = [NSIndexPath indexPathForRow:lastRow inSection:0];
    // Insert this new row into the table.
    [[self tableView] insertRowsAtIndexPaths:[NSArray arrayWithObject:ip]
                            withRowAnimation:UITableViewRowAnimationTop];
}

Build and run the application. Tap the
New
button and watch the new row slide into the bottom position of the table. Remember that the role of a view object is to communicate model objects to the user; updating views without updating the model objects isn’t very useful.

 

Also, notice that you are sending the message
tableView
to the
ItemsViewController
to get at the table view. This method is inherited from
UITableViewController
, and it returns the controller’s table view. While you can send the message
view
to an instance of
UITableViewController
and get a pointer to the same object, using
tableView
tells the compiler that the object returned will be an instance of class
UITableView
. Thus, sending a message that is specific to
UITableView
, like
insertRowsAtIndexPaths:withRowAnimation:
, won’t generate a warning.

 

Now that you have the ability to add rows and items, remove the code in the
init
method in
ItemsViewController.m
that puts 5 random items into the store.

 
- (id)init
{
    // Call the superclass's designated initializer
    self = [super initWithStyle:UITableViewStyleGrouped];
    if (self) {
        
for (int i = 0; i < 5; i++) {
            
[[BNRItemStore sharedStore] createItem];
        
}
    }
    return self;
}

Build and run the application. There won’t be any rows when you first fire up the application, but you can add some by tapping the
New
button.

 
Deleting Rows

In editing mode, the red circles with the dash (shown in
Figure 10.7
) are deletion controls, and touching one should delete that row. However, at this point, touching a deletion control doesn’t do anything. (Try it and see.) Before the table view will delete a row, it sends its data source a message about the proposed deletion and waits for a confirmation message before pulling the trigger.

 

When deleting a cell, you must do two things: remove the row from the
UITableView
and remove the
BNRItem
associated with it from the
BNRItemStore
. To pull this off, the
BNRItemStore
must know how to remove objects from itself. In
BNRItemStore.h
, declare a new method.

 
@interface BNRItemStore : NSObject
{
    NSMutableArray *allItems;
}
+ (BNRItemStore *)sharedStore;
- (void)removeItem:(BNRItem *)p;
 

In
BNRItemStore.m
, implement
removeItem:
.

 
- (void)removeItem:(BNRItem *)p
{
    [allItems removeObjectIdenticalTo:p];
}

You could use
NSMutableArray
’s
removeObject:
method here instead of
removeObjectIdenticalTo:
, but consider the difference:
removeObject:
goes to each object in the array and sends it the message
isEqual:
. A class can implement this method to return
YES
or
NO
based on its own determination. For example, two
BNRItem
s could be considered equal if they had the same
valueInDollars
.

 

The method
removeObjectIdenticalTo:
, on the other hand, removes an object if and only if it is the exact same object as the one passed in this message. While
BNRItem
does not currently override
isEqual:
to do special checking, it could in the future. Therefore, you should use
removeObjectIdenticalTo:
when you are specifying a particular instance.

 

Now you will implement
tableView:commitEditingStyle:forRowAtIndexPath:
, a method from the
UITableViewDataSource
protocol. (This message is sent to the
ItemsViewController
. Keep in mind that while the
BNRItemStore
is the where the data is kept, the
ItemsViewController
is the table view’s

data source.

)

 

When
tableView:commitEditingStyle:forRowAtIndexPath:
is sent to the data source, two extra arguments are passed along with it. The first is the
UITableViewCellEditingStyle
, which, in this case, is
UITableViewCellEditingStyleDelete
. The other argument is the
NSIndexPath
of the row in the table.

 

In
ItemsViewController.m
, implement this method to have the
BNRItemStore
remove the right object and to confirm the row deletion by sending the message
deleteRowsAtIndexPaths:withRowAnimation:
back to the table view.

 
- (void)tableView:(UITableView *)tableView
    commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
     forRowAtIndexPath:(NSIndexPath *)indexPath
{
    // If the table view is asking to commit a delete command...
    if (editingStyle == UITableViewCellEditingStyleDelete)
    {
        BNRItemStore *ps = [BNRItemStore sharedStore];
        NSArray *items = [ps allItems];
        BNRItem *p = [items objectAtIndex:[indexPath row]];
        [ps removeItem:p];
        // We also remove that row from the table view with an animation
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                         withRowAnimation:UITableViewRowAnimationFade];
    }
}
 

Build and run your application, create some rows, and then delete a row. It will disappear. Try out some of the different row animations!

 

Other books

Footprints in the Butter by Denise Dietz
Fright Wave by Franklin W. Dixon
The Changing Wind by Don Coldsmith
Lust - 1 by Robin Wasserman
His Want by Ana Fawkes