Monday, December 24, 2012

A Table View based app for iPhone (Part 3)

A Table View based app for iPhone (Part 3)

In part 2 [link to Friends2 blog post] of this blog, we looked at using a custom table view cell in a table view. If you have been following along, this part of the blog starts with the application as we left it in part 2. You can download the part 2 app [link to Friends2 code .zip file] here, and the full source for this third part of the blog is available [link to Friends3 code .zip] here.

Load the source code for the second part of the blog by double clicking the Friends.xcodeproj file. We’ll start with deletion – deleting rows from a table view is extremely simple, but we must also remember to delete the entry from the underlying data source as well.

To enable deletion of a row, we simply enable editing of the table view by uncommenting a couple of items in the UITableViewController class definition. Open FriendsViewController.m. First, uncomment the line shown here in viewDidLoad:

- ( void )viewDidLoad
{
    [super viewDidLoad ];

    // Uncomment the following line to preserve selection between presentations.
    // self.clearsSelectionOnViewWillAppear = NO;
 
    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    self.navigationItem.rightBarButtonItem = self.editButtonItem;
}

This will give us an edit button in the title bar of the table view. Next, uncomment the tableView: commitEditingStyle: forRowAtIndexPath: method as shown:

// Override to support editing the table view.
- ( void )tableView : (UITableView * )tableView commitEditingStyle : (UITableViewCellEditingStyle )editingStyle forRowAtIndexPath : ( NSIndexPath * )indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete ) {
        // Delete the row from the data source
        [tableView deleteRowsAtIndexPaths :@ [indexPath ] withRowAnimation :UITableViewRowAnimationFade ];
    }  
    else if (editingStyle == UITableViewCellEditingStyleInsert ) {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
    }  
}

Important: if you run the app now, you will be able to delete the row, but since we’ve not added any code to delete the entry in the NSDictionary, the app will get “out of synch.” and kill itself. This isn’t the desired behavior!

Since we’re managing the underlying data in the Friends class, let’s add a method to delete an entry from the dictionary to Friends.h:

#import <Foundation/Foundation.h>

@interface Friends : NSObject

@property (nonatomic, strong) NSDictionary *friendsDictionary;

- (void) saveFriendsToPlist:(NSString *) filename;
- (void) loadFriendsFromPlist:(NSString *) filename;
- (void) deleteEntryWithKey:(NSString *)key;

@end

and define it in Friends.m:

- ( void ) deleteEntryWithKey : ( NSString * )key
{
    NSMutableDictionary *tempFriends = [self.friendsDictionary mutableCopy ];
    [tempFriends removeObjectForKey :key ];
    self.friendsDictionary = tempFriends;
    tempFriends = nil;
}

We make a mutable copy of the self.friendsDictionary object in order to delete the entry, then copy that back into the friendsDictionary object and delete the reference to the mutableCopy by setting it to nil.

Now we can use this method in the tableView: commitEditingStyle: forRowAtIndexPath: method in FriendsViewController.m:

- ( void )tableView : (UITableView * )tableView commitEditingStyle : (UITableViewCellEditingStyle )editingStyle forRowAtIndexPath : ( NSIndexPath * )indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete ) {
        // Delete the row from the data source
        UILabel *nameLabel = (UILabel * ) [ [tableView cellForRowAtIndexPath :indexPath ] viewWithTag :1 ];
        NSString *name = nameLabel.text;
        [self.friends deleteEntryWithKey :name ];
        [self.friends saveFriendsToPlist : @ "friends.plist" ];
        [tableView deleteRowsAtIndexPaths :@ [indexPath ] withRowAnimation :UITableViewRowAnimationFade ];
    }  
    else if (editingStyle == UITableViewCellEditingStyleInsert ) {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
    }  
}

We locate the proper label (the one having a tag value of 1) in the custom cell, then get its text in the name string. This string is used as the key for the dictionary object to be deleted: we pass this key to the new method deleteEntryWithKey: in the self.friends object, which removes this entry from the dictionary. Finally, we call the saveFriendsToPlist: method to save out the newly altered dictionary.

If we run the app now, we can delete the entry:

After deletion, if we re-run the application, it will show an empty table view because we’ve saved out the (now empty) dictionary. Now would be a good time to add code allowing us to add a new entry! It’s also a good idea to go into the CustomCell.xib file and scoot the labels a bit to the right, so the editing control doesn’t overwrite the text of the labels:

In order to be able to add entries, we’ll need another view controller with a view allowing us to enter a name, email, and phone number. Create a new UIViewController class and name it AddingViewController. Make it a subclass of UIViewController, and create it with a XIB file. Now open the AddingViewController.xib file and add three labels, three text fields, and a button as shown:

We’re going to push this new view controller from within FriendsViewController, but in order to do so, we’ll need to put a new bar button for adding in the title bar of FriendsViewController. Here’s the code to add in the viewDidLoad method of FriendsViewController.m:

- ( void )viewDidLoad
{
    [super viewDidLoad ];

    // Uncomment the following line to preserve selection between presentations.
    // self.clearsSelectionOnViewWillAppear = NO;
 
    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
   
    UIBarButtonItem *addButtonItem = [[UIBarButtonItem alloc]   initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self                                action:@selector(displayAddingViewController)];
   
    self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:self.editButtonItem, addButtonItem,     nil];
   
}

The array rightBarButtonItems contains all button items we want to display in the title bar. The edit button is a given, we just need to define the addButtonItem. In FriendsViewController.h, we need to import the AddingViewController class and make a property:

#import <UIKit/UIKit.h>
#import "Friends.h"
#import "AddingViewController.h"

@interface FriendsViewController : UITableViewController

@property (nonatomic, strong) Friends *friends;
@property (nonatomic, weak) IBOutlet UITableViewCell *customCell;
@property (nonatomic, strong) AddingViewController *addingViewController;

@end

Returning to FriendsViewController.m, we can lazily instantiate the addingViewController object (after synthesizing it) and write the definition of displayAddingViewController:


@implementation FriendsViewController

@synthesize friends;
@synthesize customCell;
@synthesize addingViewController;

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
        [self.friends loadFriendsFromPlist:@"friends.plist"];
    }
    return self;
}

#pragma mark – Lazy Instantiation:

- (Friends *)friends
{
    if (!friends) {
        friends = [[Friends alloc] init];
    }
    return friends;
}

- (AddingViewController *)addingViewController
{
    if (!addingViewController) {
        addingViewController = [[AddingViewController alloc] initWithNibName:nil bundle:nil];
    }
    return addingViewController;
}

#pragma mark – Adding View Controller push:

- (void) displayAddingViewController
{
    self.addingViewController.title = @"Add a Friend";
    [self.navigationController pushViewController:self.addingViewController animated:YES];
}

Running the app now allows us to test pushing the new view controller:

So far, so good, but if we try to add an entry, nothing happens. We’ve not done anything to AddingViewController at all!

Open AddingViewController.h and add a protocol, four properties, and two method headers as shown:

#import <UIKit/UIKit.h>

@protocol AddingDelegate <NSObject>

- (void) userDidAddFriend:(NSString *)name email:(NSString *)email phone:(NSString *)phone;

@end

@interface AddingViewController : UIViewController

@property (nonatomic, strong) NSString *name, *email, *phone;
@property (nonatomic, assign) id <AddingDelegate> delegate;

- (IBAction)addButtonPressed:(UIButton *)sender;
- (IBAction)textEntered:(UITextField *)sender;

@end

In AddingViewController.xib, change the tag values of the three text fields (from top to bottom) to 1, 2, and 3. Wire up all three of the text field’s Did End On Exit events to the textEntered: method, and wire the button’s Touch Up Inside event to the addButtonPressed: method:

Now open AddingViewController.m and make these changes:

#import "AddingViewController.h"

@interface AddingViewController ()

@end

@implementation AddingViewController

@synthesize name, email, phone;
@synthesize delegate;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (IBAction)addButtonPressed:(UIButton *)sender
{
    if (![self.name isEqualToString:@""]) {
        [self.delegate userDidAddFriend:self.name email:self.email phone:self.phone];
        [self.navigationController popToRootViewControllerAnimated:YES];
    }
}

- (IBAction)textEntered:(UITextField *)sender
{
    switch (sender.tag) {
        case 1:
            self.name = sender.text;
            break;
        case 2:
            self.email = sender.text;
            break;
        case 3:
            self.phone = sender.text;
            break;
           
        default:
            break;
    }
    [sender resignFirstResponder];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

We’ll need to adopt the AddingDelegate protocol in FriendsViewController, and implement the userDidAddFriend: email: phone: method there as well. Here’s the change to FriendsViewController.h:

@interface FriendsViewController : UITableViewController
<AddingDelegate>

That’s right, all we have to do is adopt the protocol… Now, make these changes to FriendsViewController.m. First, in initWithStyle, set the addingViewController’s delegate to self:

- ( id )initWithStyle : (UITableViewStyle )style
{
    self = [super initWithStyle :style ];
    if (self ) {
        // Custom initialization
        [self.friends loadFriendsFromPlist : @ "friends.plist" ];
        self.addingViewController.delegate = self;
    }
    return self;
}

Then, define the userDidAddFriend: email: phone: method:

- ( void ) userDidAddFriend : ( NSString * )name email : ( NSString * )email phone : ( NSString * )phone
{
    NSArray *valueArray = [ NSArray arrayWithObjects :email, phone, nil ];
    [self.friends addEntryWithKey :name andValue :valueArray ];
    [self.friends saveFriendsToPlist : @ "friends.plist" ];
    [self.tableView reloadData ];
}

Of course, this will generate compiler errors: the Friends class has no addEntryWithKey: andValue: method. We’d better add that now. Here’s the method as it should appear in Friends.m (add the header for the method to Friends.h, too):

- ( void ) addEntryWithKey : ( NSString * )key andValue : ( NSArray * )value
{
    NSMutableDictionary *tempFriends = [self.friendsDictionary mutableCopy ];
    [tempFriends setObject :value forKey :key ];
    self.friendsDictionary = tempFriends;
    tempFriends = nil;
}

Save all your work and run the app. Now we can both add and delete our friends!

We promised to show how to group the table view, but this blog is getting a bit long. So we’ll extend the series to four parts. In the next part, we’ll show the technique of sorting the entries alphabetically and grouping them into sections. Stay Tuned!


Source : blancer[dot]com

0 comments:

Post a Comment