Monday, December 29, 2008

UITableView Part II

Let's keep the ball rolling on that UITableView.

Before I get started... I'm going to assume you understand all the points from the first part, or you are using the same code to follow along this time.  If you need to, post a comment and I'll be glad to help you with any specific areas in the first part of the UITableView stuff we will be covering.

This one will cover the editing modes of our UITableView.

Luckily for us, Apple has once again made this very easy on us.  You will notice near the top of our RootViewController commented out we have a viewDidLoad class.  We can get rid of that comment because we want to use it now.  Also, remove the comment before the last line of it so we have our editing button.  We can also add a title here.  A title should always be used in our UITableView so people know what they're looking at.  It's very easy and quick to add, so we hardly have any excuses.  We can simply add this:

self.title = @"Table Testing!";
So what we have for that section is:

- (void)viewDidLoad {

    [super viewDidLoad];

   self.title = @"Table Testing!";


    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.

    self.navigationItem.rightBarButtonItem = self.editButtonItem;

}

 Already we should be seemingly close to our desired effect.  Visually it would seem so if you choose to run it now.  I ran it in the simulator and it looks like this:

























Even though visually it would seem as though we are very close to our goal, that wouldn't happen to be the case just yet as we have almost all of our programming yet to do.  For now, we'll hop back to the app delegate so we can create a couple support methods.  We'll need to be able to remove items as well as move items to fully show the UITableView editing experience.  So that will basically require two methods.  I'll call them removeFromList and moveFromOriginal: toNew:.  So in the TableViewPraticeAppDelegate.h they will be:


-(void) removeFromList:(NSInteger)index;

-(void) moveFromOriginal:(NSInteger)indexOriginal toNew:(NSInteger)indexNew;


And these can be added before or after the -(void) createArray;  Order doesn't matter in these cases.


Here is my implementation for the removal from the list:

-(void) removeFromList:(NSInteger)index { 

    NSMutableArray *tempArray = [[NSMutableArray alloc] initWithArray:theArray];

     [tempArray removeObjectAtIndex:index];

     theArray = tempArray;

}


This can be done a great number of ways, but I decided to go with this route.  Another fairly simple option would be making theArray an NSMutableArray for ease of use, but I like using an NSArray when possible.  What I am doing is creating a new array with the same objects and then removing the object we no longer need from that list and resetting our "theArray" as the correct array.


My implementation for the moving items on the list is very similar:

-(void) moveFromOriginal:(NSInteger)indexOriginal toNew:(NSInteger)indexNew {

     NSMutableArray *tempArray = [[NSMutableArray alloc] initWithArray:theArray];

     id tempObject = [tempArray objectAtIndex:indexOriginal];

     [tempArray removeObjectAtIndex:indexOriginal];

    [tempArray insertObject:tempObject atIndex:indexNew];

    theArray = tempArray;

}


This will grab the temporary object (i.e. the object which is moving) so we can remove it from the list and then add it back in where we now want it.


That will handle that end of things, so now we just need to edit two methods that already exist in the RootViewController.  Towards the bottom you will see commented out:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath


Get rid of those comments, and we can use some pre-existing code along with simply calling our methods we just created.  Our end result will be this:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {

    TableViewPracticeAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];

    if (editingStyle == UITableViewCellEditingStyleDelete) {

        // Delete the row from the data source

[appDelegate removeFromList:indexPath.row];

        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];

    }   

    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

    }   

}


As you can see I have once again called the appDelegate and will be using that to call the removeFromList we just created.  Make sure you add the .row after indexPath, otherwise your program will crash and burn like a Zeppelin filled with a highly flammable gas.


We can use a similar approach for moving, once again, here is my class once the comments were removed for that as well:

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {

TableViewPracticeAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];

[appDelegate moveFromOriginal:fromIndexPath.row toNew:toIndexPath.row];

}


So this one will once again use the indexPath.row idea to pass along the actual row number we are in.  With both of these methods created the only thing left to do is make sure to remove the comments surrounding:

- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath


We want all areas to be movable, so we will leave this at straight forward return YES;


Since it's boring to be playing with two rows, I went back and added a bit more to my NSArray I was creating.  I made it originally have the objects "Hello", "World", "How", "You?", "Are" and then moved it around to create the sentence I wanted and then removed world to create a simple "Hello How Are You?" sentence.  This will give you a much better feeling of playing around with an editable UITableView.


Your editable UITableView should function just fine, no matter what you throw at it.  But what you may notice is if you close the application and then launch it again later, the list will be as it was originally.  You may not want this to happen, so as I introduced the App Delegate and its purposes and a possible use in the last section, I will introduce NSUserDefaults in this one.


NSUserDefaults can store information for your application beyond just when it is open.  So while the app delegate was very useful when dealing with information what was currently running, we can use NSUserDefaults to keep that information safe while we're not running the application.


Note: There are many other methods in storing information, this is only one and you should not live and die by only one method of storing information.


We can do all of these changes within the delegate's .m file.  First off, we need to store the information when the application is closing.  Find:


- (void)applicationWillTerminate:(UIApplication *)application


Inside of this we can put things we want to happen if the user closes the application.  So naturally this would be the easiest way to store the information each time we're done editing our list.  We do that by first calling the NSUserDefaults, which is again very easy:


NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];


This will get the NSUserDefaults for us and what's important to know is that this works similar to an NSDictionary, not an NSArray.  So we can set things as values for keys, rather than putting in an object at an index.  So we can store the array as "lastArray" by putting in:

[defaults setObject:theArray forKey:@"lastArray"];


Seems simple enough, now we just need to check for it when our application is launched.  So let's replace this snippet from the applicationDidFinishLaunching:

// Create the array

[self createArray];


With this new section:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

if([[defaults objectForKey:@"lastArray"] count] > 0) {

// We already have an array, let's use it

theArray = [defaults objectForKey:@"lastArray"];

} else {

// Create the array

[self createArray];

}


 This seems to be an elegant way to check, that way, if the user has either launched this for the first time or if they have deleted everything, it will create the array, otherwise it will use the latest array before the application was closed.


Here's the source code once again:  Download UITableView Part II Source Code


Enjoy!

7 comments:

  1. Thanks for your tutorial.
    I just have a related question.
    How could I make the first row not to be edited or moved?

    I know that with
    - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
    the first row could be set not to move but if another cell is dropped above the first one this will we moved down so is moving in the end.

    I wonder is you could answer this question.
    Thanks for the great tutorial again.

    ReplyDelete
  2. I think your method of removing would cause a leak. Unless the Array was autoreleased (when created).

    When you remove one object from the temporary array then directly assign the original to the temp, everything besides the 'object to be removed' would seem to be leaked. (if you used alloc).


    my method is like this and unless i'm misunderstanding memory management in objective-c, would look to work more efficiently.


    My method of removal:
    Since I alloc my 'contentArray' on viewDidLoad().

    // create strong reference to removing obj
    [[contentArray objectAtIndex:indexPath.row] retain];

    // remove object from array
    [contentArray removeObjectAtIndex:indexPath.row];

    // Tell our TableView to remove the row (which would seem to imply it will drop its reference later-on when its updated.
    [varTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];

    ReplyDelete
  3. Hi Thanks for the tutorial it has helped greatly, Please could you indicate how it is possible to add an object to the array and reload the view.

    Many thanks

    ReplyDelete
  4. Thanks for your tutorial.
    I just have a related question.
    How could I make the first row not to be edited or moved?

    I know that with
    - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
    the first row could be set not to move but if another cell is dropped above the first one this will we moved down so is moving in the end.

    I wonder is you could answer this question.
    Thanks for the great tutorial again.

    ReplyDelete
  5. Excellent job man. I finally found someone that uses appDelegate in an understandable way. Everything that you are doing is very simple and straightforward. Beautiful job.

    ReplyDelete
  6. Thanks for this simple and easy to follow tutorial. I had a question though. In my app I am trying to open uitableview by tapping a bottom bar button. I m able to open the tableview but not able to print title for the table. I m not sure why. Also after opening the tableview I want to display data from local memory of iPhone. Not sure how to do that. Any suggestion will be great help.
    Thank

    ReplyDelete
  7. Great tutorial.. !!! but what if we have more than 1 sections in the table.. I increased the number of Sections > 1, how can we handle that??

    ReplyDelete