Wednesday, May 7, 2014

Bringing It All Together: Notifications

There are some routine activities in iOS development that involve spreading related code across multiple methods. In my early days, I just took this for granted as The Way Things Are Done. As my skills and experience grew, some of these aspects started to rub me the wrong way, and eventually I became irritated enough to come up with my own solutions. So over the next couple posts, I'd like to describe those solutions and explain how they came to be.

 

The first topic is notifications, specifically setting up a UIViewController subclass to listen for them. I was initially set along the path of "something needs to change" by this post. Here the author argues against the use of what I call "Nuclear Unregister" in viewDidDisappear:, like so:

 

- (void)viewDidDisappear:(BOOL)animated
{
   [super viewDidDisappear:animated];

   [[NSNotificationCenter defaultCenter] removeObserver:self];
}

 

The author states - and I wholeheartedly agree - that this should only be done in dealloc. He describes how doing this in a UIViewController subclass also winds up unsubscribing from any notifications that UIViewController would be otherwise listening for, such as memory warnings. I think Apple has made some internal changes since this was written 3 years ago, as I was indeed still able to get memory warning notifications after doing this in iOS 7, but the larger point remains. Maybe the superclass is depending on some other notifications that are still in play when the view is off screen. Maybe future subclasses will, too. Going with the nuclear option means you are turning off listeners that you are not responsible for and possibly have no control over, and this happens at a point when the object in question is very much still alive and will likely be used again in the future. At minimum it is lazy, but on the other end of the spectrum you are stepping on toes that shouldn't be stepped on. Sure, your listeners will be reactivated in viewWillAppear, but if sub/superclasses had them defined in -init, they won't be coming back on again.

 

Whether or not you agree that this is an issue - and by this point, I certainly hope that you do - the argument was convincing enough for me to revisit lots of code where I had been doing it wrong. And I then moved forward with a strict policy of only unregistering the specific notifications that I had originally registered.

 

This means that NSNotificationCenter code gets spread out to numerous locations. The vast majority of them will be added in viewWill/DidAppear:

 

- (void)viewDidAppear:(BOOL)animated
{
   [super viewDidAppear:animated];

   NSNotificationCenter *notificationCenter = ...;
   [notificationCenter addObserver: selector: name: object: ];
   [notificationCenter addObserver: selector: name: object: ];
   [notificationCenter addObserver: selector: name: object: ];
   [notificationCenter addObserver: selector: name: object: ];
}

 

And then you have to remember to turn them off in viewWill/DidDisappear:

 

- (void)viewDidDisappear:(BOOL)animated
{
   [super viewDidDisappear:animated];

   NSNotificationCenter *notificationCenter = ...;
   [notificationCenter removeObserver: name: object: ];
   [notificationCenter removeObserver: name: object: ];
   [notificationCenter removeObserver: name: object: ];
   [notificationCenter removeObserver: name: object: ];
}

 

And this is just for the ones that need to be active only when on screen. If you need more that will be active for the life of this object, then you have to handle init. But if this view controller is created in a XIB file, it won't run through init, so you have to cover -awakeFromNib too. And then of course you do the nuclear unregister in dealloc. So that is potentially 5 different methods where some code will be placed. This is spreading things out too much for me.

 

Worse, there is nothing inherently linking any of these locations. Say you change your mind, and one of these viewDidAppear ones needs to go to init instead. Now you have to remember to remove the corresponding line from viewDidDisappear, because if you don't it could potentially be shut off early. Are you going to remember to do that every time? Will the new person on the project even know that they should? It is related code in multiple methods that contains no additional documentation about what the intent should be.

 

Also it's annoying to have to type out the removeObserver: lines again. Even if you copy-paste from the addObserver: lines, you still have to edit each one. The 3 parameters in removeObserver: are exactly the same as 3-out-of-4 of the addObserver: parameters, but you don't get any code-completion help typing out the combination. It's a situation just begging for mistakes.

 

That pretty much covers all of my pain points with notifications, so let's move on to my attempt to address them. I was not able to come up with a single class or technique that solves all of these issues. They are similar-but-different method calls that have to happen. They will need to be called at different times. It's a tough nut to crack, but I came up with a system that I feel does a really good job of making this process as simple as it can be. The classes involved are part of my BTIKit project, so you can just hop over there and look at code if you don't care to read any more.

 

Step 1 is basically to refactor the addObserver/removeObserver methods. If it can be broken down to as simple as "startListening" and "stopListening" that would be great. Naturally these methods do not contain enough information, as we're dealing with 3 or 4 parameters ultimately, so I need an object that can store the appropriate values until they are needed. Then they can be used by these simpler methods.

 

Enter BTINotificationInfo.

 

This class has properties for storing the observer, the selector, the name, and the object. But those are all private as they shouldn't be messed with after this object is created, so let's talk just about the initializer:

 

+ (instancetype)infoWithObserver:(id)observer
   selector:(SEL)selector
   name:(NSString *)name
   object:(id)object
   lifespan:(BTINotificationInfoLifespan)lifespan;

 

One of these objects is created with the exact same parameters that would be involved with addObserver. There is an additional lifespan value that I'll discuss in a moment. Then we do indeed have our simpler methods:

 

- (void)startListening;
- (void)stopListening;

 

The initializer places the parameters into the appropriate properties, and then these methods reference them as needed to do the addObserver/removeObserver stuff. Right off the bat, one potential source of error has been removed: the largely-duplicated parameters in removeObserver. Instead of appearing twice (add + remove), they appear once. The values are defined in one place. Then using this object, you can start/stop listening as much as you want, repeatedly, without having to type those values again.

 

Of course, add/remove observer are called from different methods typically, so you will want to keep this object around somewhere. As I said, I was not able to come up with a single-class solution. But this does serve as the building block for the entire system. And this object can be used standalone. An example would be in Core Data, where you might start listening for a managed object context save, do the save, and then stop listening. Rather than type out the parameters twice, just create one of these objects. startListening before, save, stopListening when finished. Easy.

 

I mentioned the additional lifespan parameter. With view controllers, the vast majority of notification needs fall into 2 categories: 1) Listen only when the view is on screen, or 2) Listen the entire time this view controller is alive. If there have been other situations, they didn't sink into my mind enough to care about. And then I haven't really had too many consistent notification needs outside of view controllers that follow any particular pattern. So the rest of the solution is geared towards view controllers, and this lifespan parameter is how you define which category you fall into. The value is an enum, with pre-defined values of "lifetime", "visible", and "custom". While defining all of the notification listener stuff, you can also define the period during which this listener should be active.

 

This addresses another one of my pain points. Instead of relying on the addObserver code to be in a particular method to explain the intent, now you explicitly define it. And it is easy to change your mind. The case I mentioned previously about moving the addObserver from viewDidAppear to init goes away. Change the lifespan value, done. Nothing else needs to change.

The BTINotificationInfo object doesn't use the lifespan value itself. Actually this is the only property that is allowed to be public (read-only), so that other classes can make use of it. For the vast majority of my needs, "other classes" have been view controllers. And a view controller makes up the other half of this solution: BTIViewController.

 

The first thing that BTIViewController provides is a single place to define ALL (or at least "most", certainly) of your BTINotificationInfo objects:

 

- (void)populateNotificationInfos NS_REQUIRES_SUPER;

 

This is subclass-friendly, so any view controllers subclassing this can define their own notification infos. This method will be automatically called when the view controller is created.

 

BTINotificationInfo has that lifespan property, so you can define all of your "visible" and "lifetime" notifications right along side each other, in the same method. Since everything is defined in one place, that reduces the amount of effort required to investigate all of your notification listener setup.

 

Two simple methods make the magic happen:

 

- (void)startListeningForNotificationsWithLifespan:(BTINotificationInfoLifespan)lifespan;
- (void)stopListeningForNotificationsWithLifespan:(BTINotificationInfoLifespan)lifespan;

 

These can be called manually if desired, but there probably isn't much of a reason to do so. BTIViewController is already set up to register the "lifetime" ones in init/awakeFromNib, and handle the "visible" ones in viewWill(Dis)Appear. Pretty much the only reason to call these methods manually would be to address any "custom" ones, but I will make a guess that it won't make much sense to handle special-case ones along with the others. I could be wrong.

 

BTIViewController has a primary method for adding notification infos:

 

- (void)addNotificationInfo:(BTINotificationInfo *)info;

 

...and then a whole bunch of shorthand helper methods that reduce the amount of code required even further. Let's look at a typical example:

 

[[NSNotificationCenter defaultCenter] addObserver:self
   selector:@selector(keyboardWillShow:)
   name:UIKeyboardWillShowNotification
   object:nil];

 

To do this same thing with the generic methods would be:

 

BTINotificationInfo *notificationInfo = [BTINotificationInfo infoWithObserver:self
   selector:@selector(keyboardWillShow:)
   name:UIKeyboardWillShowNotification
   object:nil
   lifespan:BTINotificationInfoLifespanVisible];
[self addNotificationInfo:notificationInfo];

 

Ok, so it's a bit wordier. Here come the shorthand methods to save the day:

 

[self addVisibleNotificationInfoForName:UIKeyboardWillShowNotification
   selector:@selector(keyboardWillShow:)
   object:nil];

 

Boom, less code. The shorthand methods assume that the view controller - self - will be the observer. And the lifespan is incorporated in the name of the method.

 

One thing I haven't talked about yet is block-based notification handlers. For one thing, I never considered separate methods for notification responders to be a big deal, so they don't really solve a significant problem for me. For another, you have to keep track of the goofy observer object in order to removeObserver. So between solving a problem I didn't think I had and actually creating some work I didn't otherwise need to do, I didn't give them much thought.

 

Then I realized that with BTINotificationInfo I'm already keeping around an object for the purpose of unregistering. Since I'm doing that, that object can keep track of the observer object, too. And suddenly block-based notification handlers got more interesting, so I decided to support them and also included shorthand methods in BTIViewController. Like so:

 

[self addVisibleNotificationInfoForName:UIKeyboardWillShowNotification
   object:nil
   usingBlock:^(NSNotification *notification) {
      // Resize/reposition some views
   }];

 

We've pretty much got the holy grail here. Everything, and I mean everything, defined in one place.

 

So the steps that you need to take in order to experience this awesomeness are:

  • Acquire BTIKit.
  • Make your view controllers inherit from BTIViewController.
  • Override -populateNotificationInfos. Call super first.
  • Define the notifications that you care about.

 

That's it. BTIViewController will do the rest for you. No more typing essentially duplicated notification code in multiple methods. No more scrolling back and forth in your implementation file trying to find which notifications this class cares about. BTIViewController even does the nuclear unregister in dealloc, so you don't have to include it yourself anymore.

 

Enjoy.

Friday, March 29, 2013

Open Source Code: BTIConcepts

I've published some code on GitHub that I'm calling BTIConcepts. There is nothing earth-shattering here; I'm certainly not breaking any new ground in computer science. This is just a variety of small things that are individually useful, and become even more so when combined. If you have ever thought "Boy, I sure wish all of my [instances of class] would do [something]", then you might want to peek through the code. Even if you don't use any of these specific classes, surely there is a weak spot somewhere in your repertoire where you can apply the concepts.

 

But first let's talk about coding style.

 

I have written previously about how I use logging. I've even written a little service to make adding these logs easier. This is caveman logging at its finest, and I am not even a little bit ashamed of it. The number of problems I have solved over the years simply by doing this more than outweighs any loss of "l33t" status I may have as a professional developer. I've added to this recently with BTITrackingLog. I'm not the first to make an NSLog macro, and probably won't be the last. But until I did this, I was only using NSLogs. So it was either ALL messages, or no messages. Now I can conditionally enable the tracking logs, but still see "important" NSLogs. I will add a configuration and scheme called Debug + Tracking that allows me to see everything. I've thoughtfully commented them out from the repository in anticipation of people viewing them as annoying, but what can I say, I like them. I sadly have not yet updated my service to use BTITrackingLogs.

 

For categories, method names should be decorated somehow to avoid clashes with primary methods. Most people use a prefix, so btiDoSomething, for example. I saw a recommendation a while back (sorry person, I'd credit you if I remembered who you are) to use postfixes instead. doSomethingBTI. They key argument offered was that with a prefix, you have to know that you mean to use a category method when you start typing in order to get code completion. So it's not merely "This is an array method", it is also "This is an array method that I or someone else have manually created". With a postfix, just start typing like you normally would, and code completion will take care of the rest. For methods that accept multiple parameters, I place the decoration at the end of the first group of text, like so: doSomethingBTI:andSomethingElse:andThisToo: Another benefit (or curse, depending on point of view) of using a postfix is that if the category method is similar to a primary method, then you can easily change your mind while typing by hitting either method in the pop-up. Can't do that with a prefix.

 

The ways that I have organized my view controllers have become less and less important as Objective-C gains new features. I saw a recommendation a long time ago to put dealloc at the top of the file, so that it is right there next to the properties. This way when you add a property, you're less likely to forget to travel all the way to the bottom to clear out the property in dealloc. And since viewDidUnload was often very similar to dealloc, I wanted it placed right after dealloc for the same reasons. Well, with ARC you don't need dealloc (in most cases) anymore. And you don't need to synthesize properties anymore. And viewDidUnload won't be called anymore. So, yeah. But there are still a few of my organization quirks that remain. I like notification responders grouped, and UI response methods (IBActions, etc) grouped, and so on. Delegate methods always go to the bottom, though if multiple delegate protocols are in use, the specific order doesn't matter. The #pragma mark usage in the .m files here are quite typical of how I do things. I don't normally do it in the .h file quite so much, but wanted things to look nice for public consumption. The specific order of anything in these files doesn't really matter, but for sake of consistency, try to follow some kind of pattern.

 

As I said at the beginning, I often want all of [instances of class] to do something. Naturally this points to categories, and there are categories here, but categories don't cover everything. For example, if I always want view controllers to do something in viewWillAppear:, a category isn't going to accomplish that easily, if at all. For far too long, my solution to this has been to use templates. Every time I wanted a table view (I don't like UITableViewController), I'd fire up my template that contains a table view property, and be on my merry way. I even sorta made this work with XIBs in Xcode 4. I always got what I wanted, my code was consistent, and I was doing less work. Yay! The thing about templates is that you are effectively copy-pasting code. If you change your mind about how something should work, then all of the other hundreds of classes that were created by your template have to be changed. Or, most likely, don't get changed. Another issue with templates is that your non-standard templates don't keep up when Apple changes their templates. This is not to suggest that all of Apple's templates are winners, but if they start adding something or removing something, one should at least inquire if that is a noteworthy change. I would still be trucking along adding now-useless dealloc and viewDidUnload methods if I was continuing to use my templates. I've decided that it is useful to start with Apple's templates, quickly observe any changes, and then copy-paste in my style stuff (pragma marks, etc).

 

If templates are out, and categories don't cover everything, that pretty much leaves subclassing. Instead of always subclassing UIViewController and copy-pasting in what I want over and over again, make a new common view controller. I've made BTIViewController. And then all of my view controllers can inherit from it. Don't need viewDidUnload anymore? One class, delete one method, done. Need to update didReceiveMemoryWarning since viewDidUnload isn't called anymore? One class, modify one method, done (Ignoring customization in subclasses, of course). All subclasses then inherit that change. And although this is less relevant with iOS 6, want EVERY view controller in your project to have the same autorotation behavior? One class, one method, done. (Really wish I'd thought of this years ago...). And then differences are handled like any other subclass. Override any behaviors you want to change.

 

So the "meat" of BTIConcepts is really found in 3 classes: BTIObject, BTIViewController, and BTITableViewCell. They don't have to do much at all- BTIObject in particular does not - but by simply using them, downstream activities become much easier if you need to apply changes across the board. Instead of NSObject, inherit from BTIObject. Instead of UIViewController, inherit from BTIViewController. If you don't like what my classes do, then modify them or make your own. But the key concept here is to insert something as high in the class hierarchy as you can, so that you can easily apply mass changes if the need should arise. I would probably even go so far as to argue that you should do this even if your inserted class is totally blank. Prevents having to go change all of your inheritances later.

 

BTIObject - At the moment all it does is provide a custom description method. It automatically logs all properties. Before I came up with this, I had lengthy and quite manual description methods. This is easier.

 

BTITableViewCell - I hate worrying about the cell identifier, particularly with XIB-based cells. Now, (hat tip to Jeff LaMarche) I no longer have to.

 

BTIViewController - Now we're getting into more functionality. First of all, I hate when other classes call initWithNibName: (or initWithStyle: for UITVC). That's bad OOP, IMO. The 'parent' VC has to know too much about this VC when doing this. Is there a nib? What name does it have? And if this VC is used in multiple places across an app, these decisions have to be made every single time. Sloppy, unnecessary work. So right off the bat I override init to pass through to initWithNibName:. Parents simply alloc/init, done.

I always want notification listeners to unsubscribe in dealloc. Done. The number of reasons to have dealloc in a view controller decreases even further.

 

The bulk of the remaining additions deal with BTINotificationHandlers, which I'll describe in a moment. This view controller will automatically register and unregister notifications at appropriate times. No more forgetting to unregister, or unregistering incorrectly.

 

Pre-iOS 6, if I wanted each view controller to have the same autorotation behavior, I'd define that behavior one time in this class. No more forgetting that VC's default to portait if you don't override the proper method. I didn't include it here since the code is aimed at iOS 6, but it would be easy enough to add.

 

BTITableViewController - This can be viewed as an alternative to UITableViewController, but I'm not really trying to recreate that class since I don't like it. I define basic table view stuff like a property and conform to protocols. Additionally, there are some features that get you started for handling things like selection and searching. Not very much code is implemented, it's more of a "here are some tools that should be useful" kind of thing.

 

At the time of writing, I've really only focused on table views, since that's most of what I do. But this concept could easily be expanded. If you do a lot of web views, you could make a BTIWebViewController that doesn't have to do much more than add a web view property and become the delegate. But you could also implement the delegate methods so that the activity spinner triggers at appropriate times, and then never worry about it again. You could make an image view class, perhaps embedded in a scroll view, so that each time you want to show an image you get zooming and panning. The point of all of this is to identify routine activities, and then put them in one place rather than doing them over and over again. Like I've done here:

 

BTIArrayTableViewController.h BTIManagerTableViewController.h BTICoreDataTableViewController.h - These are common flavors of table view controllers that I make. The first is for really basic stuff, nothing fancy. A list of strings, perhaps. The second utilizes another of the models that I'll mention here soon, BTITableContentsManager. The goal here is to define your table information in one place - contents and behaviors - rather than having a ton of if/else statements in each of your delegate methods. And lastly, one that is powered by a NSFetchedResultsController that gets defined in subclasses. Each of these has sufficiently different needs that I felt it made more sense to split them up rather than make a uber-table view controller.

 

That covers the view controllers. On to categories...

 

BTIErrorCategories - Described here.
NSArray - Isn't it annoying that we get a lastObject method that doesn't crash on an empty array, but we don't have a firstObject method?
NSMutableArray - Isn't it annoying that we get a removeLastObject method but we don't have a removeFirstObject method? And isn't it annoying that moving an item to a different index requires removing and reinserting?
NSNotificationCenter - Post notifications on the main thread.
NSManagedObject - Described here.
UITableView - Came from a book. Books are good.
UIDevice - Wrappers around the userInterfaceIdiom for nicer-looking code.

 

Little stuff. They don't have to be complicated. Sure, making complicated stuff simple is nice, but making repetitive stuff less repetitive is nice too.

 

Last but not least are a few models.

 

BTINotificationHelper - I've written about them here. The short version is that these let you define notification handlers in one spot, as opposed to registering in one place, and then unregistering with similar parameters somewhere else. Define all of the usual stuff, along with an extra parameter describing when this notification should be active. Then register/unregister as needed. This class doesn't do much by itself, but BTIViewController has been rigged to automatically handle a couple of common scenarios.

The next few are for table views.

 

BTITableSectionInfo - I came up with this a long time ago, mostly for table headers. The app I was working on had a crazy if/else scheme to determine when to show headers. Finally decided it made more sense to come up with a model to encapsulate what was necessary, defining things at the time I was building the data list. When I later learned of NSFetchedResultsSectionInfo, I rearranged this class a bit to mimic it. So you can use this class to build up your table data, a section at a time. Define headers/footers, add contents, and off you go. Throw a bunch of these in an array, and multi-section table view life gets easier.

 

BTITableRowInfo - This one isn't quite as useful for model-driven tables (ex: list of people, list of addresses, etc). But for table views with a lot of individual behaviors, such as a settings screen that might drill deeper in any number of directions, this can be quite handy. Before coming up with this, I would build arrays of strings to define the overall structure. Then cellForRow and didSelectRow would each have a massive if/else chain that determined what to show in each row, and then where to go in the event of a row tap. I'm not proud; I did it this way for a long time. Once I got my head wrapped around blocks, I realized there was a better way. Using this class, you can pack up this information into a single object, then extract what you need in the various delegate methods. It may not be practical in 100% of cases, but it sure covers 90+% of what I've needed. Any remaining if/else cases are truly for exceptions, rather than for everything.

 

BTITableContentsManager - This class took me longer to create than it should have. Ever since learning Core Data, I've really enjoyed the objectAtIndexPath: method that NSFRC provides. I've wanted to be able to do that in non-Core Data situations, too. After creating the previous two classes, it finally seemed like the time was right to pursue it. I wish I had done it sooner. So although it isn't capable-enough at the moment, you can think of this as a non-Core Data version of NSFetchedResultsController. Right now, you have to build up the table structure manually. I'd like to get to a point where I just dump a pool of objects in, and then hand it a sort descriptor and maybe a predicate. I'm not there yet. In the meantime, enjoy simplified table delegate methods while using this class.

 

So there you have it: BTIConcepts. I've tried to put nice overviews into each of the .h files. I also try to make code that is fairly self-explanatory. Hopefully this code helps you out, or at least inspires you to simplify your own coding life somehow. Enjoy.

Tuesday, September 4, 2012

A Model (Object) Is A Beautiful Thing

In object-oriented programming, a common pattern is MVC: Model-View-Controller.  For some reason, the Model typically seems to be the slowest concept to grasp for new developers.  Views are easy to understand, probably because they are visual.  Controllers are maybe not 100% understood, but at least there are plenty of examples so people plod through.  Generally speaking, however, the model is often the most important component of an app, and the number of ways to handle the model badly are considerable.  This is odd, because the good ways really are not that hard.

 

Of Thee I String

 

Most developers begin their programming lives with strings.  Strings are easy and readable.  And then for more complicated structures, they move onto arrays.  Arrays are still relatively easy to understand, and aren't too hard to visualize.  They are also easy to teach, which is why books and tutorials tend to begin here.  And that's fine, we all need to start somewhere.  Let's look at a simple list of colors:

 

NSArray *colorList = [NSArray arrayWithObjects:@"Blue", @"Green", @"Red", nil];

 

Straightforward, easy to read, a good starting point.  If we were discussing table views, then it would be simple to take this array and use it to drive a table view.  That's more than sufficient for static display, and helps to establish concepts.  Another example:

 

NSArray *actors = [NSArray arrayWithObjects:@"Brad Pitt", @"William H. Macy", @"Jennifer Aniston", nil];

 

Still just strings, still quite easy to read.  But now we've started to complicate things a bit, and certain downstream tasks get harder.  Let's say I wanted to sort this list by last name.  That's going to be a challenge, since each full name is a single string.  I would need a way to identify the last name within the string.  I could potentially carve each string into pieces, and take the second piece.  But that would give me "H." for Mr. Macy.  Ok, so I need to take the last piece instead.  That should work as long as I haven't inadvertently entered "Pitt, Brad" somewhere. 

 

Ideally we need to split this data up in a known fashion, probably at the time of creation.  Relying on algorithms to extract information can be risky if the algorithm is wrong, so it is better to simply define things from the get go.  In this case, that means keeping the first name separate from the last name.  The question then becomes how to do that.

 

Don't Be A Dictionary

 

Level 2 in the programming learning curve leads to dictionaries.  At first glance, dictionaries are great.  They can contain anything, there is no hard structure, and in basic cases they can save data directly to plists without much trouble.  I'll use a mutable dictionary here since the code is easier to understand, but a sample actor represented in a dictionary might look like this:

 

NSMutableDictionary *actor = [NSMutableDictionary dictionary];
[actor setObject:@"Brad" forKey:@"firstName"];
[actor setObject:@"" forKey:@"middleName"];
[actor setObject:@"Pitt" forKey:@"lastName"];

 

The string has been broken up into pieces, and can now be located via an identifier, the key.  Retrieving a value is relatively simple:

 

NSString *actorFirstName = [actor objectForKey:@"firstName"];

 

Now instead of putting strings into an array, add dictionaries instead:

 

NSArray *actors = [NSArray arrayWithObjects:actor1, actor2, actor3, nil];

 

There are ways of sorting arrays using these keys, so it would seem that we've solved our problem.  For now, at any rate.

 

The problems with using dictionaries take a while to reveal themselves.  Let's say that you set aside this project for a couple of months to work on something else, and then come back to it in order to make changes.  You find yourself looking at this line of code:

 

NSDictionary *actor = [actors objectAtIndex:2];

 

Ok, so you're referencing a single dictionary in an array, and you're calling it an actor.  Great.  Now then, what should "actor" have in it?  Hrm, well, I sort of remember caring about the names, so probably names.  Maybe their birthdays.  Oh, it would be nice if there was a list of their movies too.  I probably thought that was a good idea when I made this, so I'll just assume this data is there.  Wonder what I called it.  Probably "birthday".  Great, let's see what happens:

 

NSString *birthday = [actor objectForKey:@"birthday"];
NSLog(@"birthday is: %@", birthday);

Log output: birthday is: (null)

 

Uh oh.  It's not there.  I must have called it something else.  And come to think of it, would it really be an NSString?  It's a date, and we have NSDate objects.  Might have been one of those.  How can I find out?  Well, I could log the entire dictionary:

 

NSLog(@"actor is: %@", [actor description])

 

Hrm, no birthday.  Is that because I didn't have information for this actor, or is that because none of the actors have birthdays?  Not sure.  And there isn't a handy way to find out, either.  All I can do is figure out where I first created these dictionaries, and see what is there.

 

The point that I'm making here in a roundabout way is that dictionaries do not document themselves.  Every NSDictionary looks the same from the outside.  It is the contents that matter, and short of manual interrogation, there is no easy way to find out what the contents of a given dictionary are or should be.  If you wanted to be diligent, you could put something in the comments somewhere like this:

 

// I hereby define an "actor" dictionary as containing the following data:
// First name shall be an NSString stored with the key "firstName"
// Middle name shall be an NSString stored with the key "middleName"
// Last name shall be an NSString stored with the key "lastName"

 

You could, but no one takes the time to do this.  And because dictionaries are so handy and flexible, you can add and remove keys all day long, and totally forget to update this list, at which point the list just becomes another point of confusion, rather than an aid.  Without this list, you do not have an easy way to figure out what this dictionary should contain.  You will have to waste time researching your own code and doing logging just to figure out what this should be.

 

But wait, there's more.  Let's say a common activity in our actor program is to display the full name of the actor.  That might look like this:

 

NSString *firstName = [actor objectForKey:@"firstName"];
NSString *lastName = [actor objectForKey:@"lastName"];
NSString *fullName = [NSString stringWithFormat:@"%@ %@", firstName, lastName];

 

Blech.  That's a lot of code, copy-pasted all over the place.  And I forgot the middle initial.  And sometimes it outputs "Bob (null)".  So I need to make it more complicated, add some error handling, and then re-copy-paste it all over the place.  Ah ha! I know a little about OOP, I'll just make a helper method somewhere.  The only thing I know how to grab from anywhere is the app delegate, so I'll put it there!

 

- (NSString *)fullNameFromActor:(NSDictionary *)actor;

 

Brilliant!  Well, not so much.  I mean, this will of course work.  But look how far separated it is from your data.  All of these actors are defined way over in some view controller (or better yet, a data controller) somewhere, but this method is here in the app delegate.  Any time that you have one of these dictionaries in your hand, you have to go all the way over to the app delegate to get this method.  More advanced programmers might be tempted to move this code into a Category, and that's certainly an option, but a bit beyond the scope of this article and not a good solution for a case like this anyway.

 

The ideal solution to this problem would include some handy form of documentation that can be easily referenced later, and would also provide a convenient place to store helper methods.  And that ideal solution really isn't that hard to do.

 

Subclassy

 

Creating a custom model subclass is straightforward.  Create a new file, starting with Objective-C class, then choose to subclass NSObject.  By convention, class names should begin with a capital letter, so "Actor", not "actor".  For bonus points in avoiding name clashes, use some kind of prefix.  I use BTI, so I would name it BTIActor.  Do NOT use NS.  Do NOT use UI.  These are Apple's territories.  So NSActor and UIActor are wrong.  Period.  Wrong.  Don't do it.  No exceptions.

 

So we have a new class now:

// BTIActor.h
@interface BTIActor : NSObject
{

}
@end

 

…what do we do with it.  Well, with the dictionary, we were thinking in terms of keys.  Here in a model class, you think in terms of properties instead.  This is no different than defining a label property in a view controller.  Let's begin:

 

// BTIActor.h
@interface BTIActor : NSObject
{
}
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *middleName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, copy) NSDate *birthday; 
@end

 

Immediately we have a much, much better situation than we had with the dictionaries.  First of all, there is a list of each element in this model.  3 strings and a date.  No keys to remember or mistype, it's all right here.  And also notice that we know what everything is.  Ah, the birthday is a date object.  Nothing to remember, nothing to get wrong.  With dictionaries, it is always objectForKey:.  What kind of object?  I don't remember.  Have to go look it up, uh, somewhere.  With this class, all I have to do is click on the .h file anytime I want to know something.  It's easy for me to find today, and it's easy for me to find in 3 months.  It is inherently self-documenting.

 

And I'm not even done yet.  How about that helper method?  Put it here, too:

 

// BTIActor.h
@interface BTIActor : NSObject
{
}
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *middleName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, copy) NSDate *birthday;
- (NSString *)fullName;
@end

 

The data remains broken up so that you can still get to individual elements (last name) if you need to, but you can make as many handy methods as you need all right here on the object that can use them.  Want to do last name, first name?

- (NSString *)reversedFullName;

 

How old is this actor right now?

- (NSInteger)currentAge;

 

On and on and on.  If you have a need, make a method for it here, then you can use it wherever you use this model.  Granted, the implementations of these methods may not necessarily be any simpler this way, but at least you have everything you need right here.

 

The .m file wouldn't be all that different from an average view controller.  Synthesize your properties and implement your methods:

 

// BTIActor.m
#import "BTIActor.h"
@implementation BTIActor
@synthesize firstName = ivFirstName;
@synthesize lastName = ivLastName;
...
- (NSString *)fullName
{
   return [NSString stringWithFormat:@"%@ %@", [self firstName], [self lastName]];

... 
@end

 

There are a variety of other things to do here.  There should be a dealloc method (unless using ARC).  Might want an init method, too.  But chances are, this is all stuff you've done elsewhere before, most likely in a view controller.  Same idea here.

 

Model objects are easy to create, so there isn't much of a reason to hesitate to use them.  Describing a car?

// BTICar.h
@interface BTICar : NSObject
{
}
@property (nonatomic, copy) NSString *make;
@property (nonatomic, copy) NSString *model;
@property (nonatomic, assign) NSInteger *year;
@property (nonatomic, assign) NSInteger *numberOfDoors;
@property (nonatomic, assign) CGFloat engineDisplacement;
@end

 

Describing a shirt?

// BTIShirt.h
@interface BTIShirt : NSObject
{
}
@property (nonatomic, copy) NSString *size; // S, M, L, XL
@property (nonatomic, retain) UIColor *color;
@property (nonatomic, assign) BOOL isLongSleeve;
@property (nonatomic, assign) BOOL hasPoppedCollar;
@end

 

If you can figure out how to put this stuff into a dictionary, then you can do the same thing even better with a model class.  There really is nothing to be afraid of here.  So kick a dictionary to the curb today!

 

Work Your Model

 

How do you use a model class?  Well, the first thing you need to do is add this to the .m file wherever you want to use them:

#import "MySuperAwesomeModel.h"

 

Now you can use it.  Let's revisit the dictionary version:

NSMutableDictionary *actor = [NSMutableDictionary dictionary];
[actor setObject:@"Brad" forKey:@"firstName"];
[actor setObject:@"" forKey:@"middleName"];
[actor setObject:@"Pitt" forKey:@"lastName"];

 

Doing this same thing with the model object:

BTIActor *actor = [[BTIActor alloc] init];
[actor setFirstName:@"Brad"];
[actor setMiddleName:@""];
[actor setLastName:@"Pitt"]; 

 

It's cleaner, easier to read, and as a double-bonus, Xcode will help you type this stuff.  Xcode does not help at all when typing @"strings".  So you are much more likely to make a typo.  If you make a typo with dictionary keys, you won't figure it out until much letter.  Xcode will not warn you.  The key will simply not be what you expect it to be.  That's hard to troubleshoot.

 

Use them in arrays just like you did with dictionaries:

[actors addObject:actor];
...
BTIActor *someActor = [actors objectAtIndex:1];

 

Oh yeah, and don't forget the helper method:

NSString *fullName = [someActor fullName];
[[self nameLabel] setText:fullName]; 

That's more or less all there is to it.

 

There are a couple of other aspects to model objects that I will address with a future post, particularly related to saving and loading these objects.  If you are in a hurry to do so, do some Googling for "NSCoding".

Tuesday, July 10, 2012

UITableView How-To: Part 6 - Inserting, Deleting, and Reordering Cells

Part 1Part 2Part 3Part 4 | Part 5

 

It seems to be time for my annual table view tutorial post.  In this long-overdue episode, we'll take a look at what is involved with making a table view support dynamic content.  If you want to allow the user to add, remove, or rearrange cells, then these are the basics that you need to understand.

 

One of the first changes to make is to use a proper data container.  If you've been using an NSArray up to this point, then you need to switch to NSMutableArray, or and/or switch NSDictionary to NSMutableDictionary.  You can add and remove items to/from the mutable versions, but cannot from non-mutable versions.

 

Create a new project using whatever template you want.  I'm using the Single View one for simplicity sake.  Place a table view on it, and get it wired up with a property.  In addition to that, place 2 buttons on the screen, one called Edit, and the other called Insert.  It doesn't need to be fancy, just something like this:

 

xib_layout.png

 

Declare IBActions for each button, and wire them up.

 

- (IBAction)insertButtonPressed:(UIButton *)button;
- (IBAction)editButtonPressed:(UIButton *)button;

 

Our initial data is just going to be a handful of strings.  Declare a property, synthesize it, then customize a getter:

 

@property (nonatomic, retain) NSMutableArray *contents;
...
@synthesize contents = ivContents; ...
- (NSMutableArray *)contents { if (ivContents == nil) { ivContents = [[NSMutableArray alloc] initWithObjects:@"A", @"B", @"C", @"D", @"E", nil]; } return ivContents; }

 

And let's quickly set up the table view delegate and data source methods.

 

- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
   NSInteger rows = [[self contents] count];
   return rows;
}

 

The number of rows is the count of the array.  Easy enough.

 

- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
   static NSString *cellIdentifier = @"cellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease]; }
NSString *rowContent = [[self contents] objectAtIndex:[indexPath row]];
[[cell textLabel] setText:rowContent];
return cell; }

 

And then to populate the cell, we grab the string out of the array, and give it to the cell's label.  Go ahead and run the app to make sure that the table fills in correctly.  Beware that if you press either button, it will crash right now since we haven't implemented those methods yet.

 

initial_run.png

 

Insert Row

 

Let's start with inserting rows.  The UITableView method for inserting rows is insertRowsAtIndexPaths:withRowAnimation:.  First thing to note is that this has an animation property.  You can slide rows in from above, below, either side, or simply fade in.  The other thing to note is the plural - rowS - meaning that you can insert more than 1 row with this method if you want to.

 

Let's continue by implementing our insert method.  The method above deals with index paths, so the first thing we're going to do is build one.  We're making the executive decision right now that any new rows will appear at the top of the table, and this is row 0.  A handy construction method is indexPathForRow:inSection:.  Note that it has a section parameter.  We are only using a single section at the moment, so that would be section 0.  Let's begin:

 

- (IBAction)insertButtonPressed:(UIButton *)button
{
   NSInteger section = 0;
   NSInteger row = 0;
   NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
NSArray *indexPathsToInsert = [NSArray arrayWithObject:indexPath];
[[self mainTableView] insertRowsAtIndexPaths:indexPathsToInsert withRowAnimation:UITableViewRowAnimationRight]; }

 

We create an index path, then create an array to hold it, and finally pass that array along to the table view via the insert method.  If you run the app now, and hit the Insert button, you will again crash.  But this time there is an actual problem we need to solve.  Look in the console, and you'll find one of the most helpful error messages in all of iOS development:

 

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0.  The number of rows contained in an existing section after the update (5) must be equal to the number of rows contained in that section before the update (5), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'

 

This crash message tells you exactly what went wrong, but it does take a little bit of getting to used in order to understand what it is saying.  Basically, the table is saying "Hey! I knew from before that you had 5 rows, and then you just told me to add 1, so I did that, but after I did that, there were still only 5 rows!"  Well clearly you are stupid, Mr. Table View, because you obviously should have 6 rows now, since I told you to add one.

 

Or should it?  How does the table view know how many rows should be there?  Oh, right, from this:

 

- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
   NSInteger rows = [[self contents] count];
   return rows;
}

 

The array still only has 5 objects.  Even though we inserted a row, the array still has just 5 objects.  The table view will ping this method again as a part of the insert process.  If 5 (old) + 1 (inserted) does not equal 6 (new total), then it will complain.  We only have a total of 5, so that's a problem.

 

This is a very important concept to understand, and it is equally important when we delete and rearrange rows in a few minutes.  Inserting a row is a view operation.  Remember the old, trusty MVC concept, Model-View-Controller?  What we did so far is make a change to V, the view.  So what still needs to happen is to make a change to M, the model.  If you insert a row in a table, you must also add something to the array.  If you remove a row from a table, you must also remove something from the array.

 

So let's revisit the insert method, and add something to the array.  Just to be quick, let's add a string that is simply a count of the number of items in the array at the time.

 

- (IBAction)insertButtonPressed:(UIButton *)button
{
   NSInteger section = 0;
   NSInteger row = 0;
   NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
NSString *extraContent = [NSString stringWithFormat:@"%d", [[self contents] count]]; [[self contents] insertObject:extraContent atIndex:row];
NSArray *indexPathsToInsert = [NSArray arrayWithObject:indexPath];
[[self mainTableView] insertRowsAtIndexPaths:indexPathsToInsert withRowAnimation:UITableViewRowAnimationRight]; }

 

Run the app again, and you should be able to hit the insert button as many times as you want.  Each time, a new row will appear at the top of the list, sliding in from the right.

 

after_insert.png

 

Some additional exercises for you to perform on your own:

  • Use different animation options to see how each one works.
  • Use a different row value to insert at different points in the table.  Beware that you should not use an index larger than the array.  You will need much smarter code than this is to protect against that.

 

Delete Row

 

There are 2 basic UI's for deleting rows.  There is swipe-to-delete, and then there is putting the table view into edit mode, where all of the red circles appear.  If you try to swipe on your rows right now, nothing will happen.  Swiping is not enabled by default, you have to implement a datasource method to get it.  That method is:

 

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

 

The mere presence of this method enables the swipe-to-delete UI.  It doesn't do anything yet, but you can go ahead and run your app, swipe on a row, and you will see the delete button.  Now we need to add the guts to this method.

 

The first item to note is the editingStyle parameter.  A quick trip to the documentation reveals that the value could be none, insert, or delete.  I don't believe I have ever used this parameter for anything other than delete.  Still, better safe than sorry:

 

if (editingStyle == UITableViewCellEditingStyleDelete)

 

For inserting a row, we had to build an index path.  In this case, we are given an index path parameter already.  The table view delete row method is deleteRowsAtIndexPaths:withRowAnimation:.  Basically, the exact opposite of the insert method.  Instead of flying in from the right, the row will fly out to the right, and so on.

 

Remember from inserting a row that we must also make a change to the array.  This is no different.  We will remove a row, therefore we must remove something from the array at the same time.

 

- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath
{
   if (editingStyle == UITableViewCellEditingStyleDelete)
   {
      [[self contents] removeObjectAtIndex:[indexPath row]];
NSArray *indexPathsToRemove = [NSArray arrayWithObject:indexPath];
[tableView deleteRowsAtIndexPaths:indexPathsToRemove withRowAnimation:UITableViewRowAnimationRight];} } }

 

We remove an item from the array, then delete the corresponding row.  Run the app now, and you will be able to swipe-to-delete anything you want.  If you delete everything, just hit the insert button to put more stuff back in and then go delete it again.

 

Swipe-to-delete is nice, but sometimes it is better to show all of the delete circles to let the user see that deleting can be done.  This is done by turning on the edit mode for the table view.  By default, edit mode is off, so we will need to turn it on.  This is where our other IBAction method comes in:

 

- (IBAction)editButtonPressed:(UIButton *)button
{
   [[self mainTableView] setEditing:![[self mainTableView] isEditing] animated:YES];
}

 

We look at whether or not the table view is currently in edit mode, and then set it to the opposite.  If you run the app now, you can hit the Edit button multiple times to turn on and off edit mode.  You can also go ahead and delete rows while you're there.

 

You've probably seen apps where the button says Edit sometimes, and Done sometimes.  Let's make that happen:

 

- (IBAction)editButtonPressed:(UIButton *)button
{
   [[self mainTableView] setEditing:![[self mainTableView] isEditing] animated:YES];
NSString *buttonTitle = ([[self mainTableView] isEditing]) ? @"Done" : @"Edit";
[button setTitle:buttonTitle forState:UIControlStateNormal]; }

 

For additional research that I'm not going to go into here, if you are using a navigation bar or a toolbar, you can use UIViewController's built-in editButtonItem.  As noted in the documentation, you will need to implement setEditing:animated:, and then do basically the same thing I've done here (except for the button title part).

 

Insert And Delete Rows

 

For more complicated tables, a single insert command or a single delete command may not be sufficient.  Perhaps you will need to add AND remove some rows.  Or maybe you will add rows AND add sections (there is a different method for that, see the documentation).  If you will be performing multiple actions, then you need to group those activities inside of a table animation block.

 

Drag another button onto the view, and call it "Silly".  Then implement this method:

 

- (IBAction)sillyButtonPressed:(UIButton *)button
{
   NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
   NSArray *indexPaths = [NSArray arrayWithObject:indexPath];
[[self mainTableView] beginUpdates];
[[self mainTableView] insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationRight]; [[self mainTableView] deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationRight];
[[self mainTableView] endUpdates]; }

 

These are the same commands we did previously, just now we are doing them at the same time.  We begin by telling the table view that we are about to give it multiple commands, via the beginUpdates method.  Then we perform whatever actions we need, and tell the table view we are done via the endUpdates method.  The table view will analyze all of these changes and come up with an animation that looks nice, performing the adds and deletes at the same time.

 

If you run the app right now, each time you hit the Silly button, the first row will slide to the right and then back to the left.

 

The 2 animations do not have to be the same.  Change one to Left, and then see what happens.

 

You may have noticed that we did not change the array.  In this (really silly) case, we didn't need to.  We add 1 row, and remove 1 row.  That means a net change of 0, so the number of items in the array is still correct.  BUT, for more complicated situations, you absolutely would need to change the array.  If you add 5 rows and delete 2 others, then your array needs to become 3 items longer.

 

Reorder Rows

 

Changing the order of items in the table is a common need.  You may have noticed that we have not yet seen the drag widgets on the right side of the table.  Just like the delete buttons, we need to implement a method in order to see them.  Also, they only show up in Edit mode, so our Edit button will be involved.  The necessary method is:

 

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

 

Just like before, the mere presence of this method is enough to show the widgets.  Run the app, hit the Edit button, and you will see them.  If you try to reorder rows, it will LOOK like it works, but it really doesn't.  In order to prove that, I need to implement a different method:

 

- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
   NSString *rowContent = [[self contents] objectAtIndex:[indexPath row]];
   NSLog(@"tapped row is: %@", rowContent);
}

 

Run the app, tap on a row, and then you will see this in the console.  Make sure that when you tap A, you see A in the console.  Then edit the table, drag the last row to the top, and hit the Done button.  Now tap on rows and see what happens.  When you tap on E, you should see A in the console.  Tap on A, you'll see B, and so on.

 

Why is that?  Well, it's the same reason as before.  The table view is doing the visual stuff, but the underlying data - the array - was never changed.  We need to handle that ourselves:

 

- (void)tableView:(UITableView *)tableView
moveRowAtIndexPath:(NSIndexPath *)fromIndexPath
toIndexPath:(NSIndexPath *)toIndexPath
{
   NSString *contentsToMove = [[[self contents] objectAtIndex:[fromIndexPath row]] retain];
[[self contents] removeObjectAtIndex:[fromIndexPath row]]; [[self contents] insertObject:contentsToMove atIndex:[toIndexPath row]];
[contentsToMove release]; }

 

We select the item, and retain it because we don't want it to die when we pull it out of the array.  Then we remove it, and insert it back in at the new location.  Then we release it to balance the retain.

 

Final Thoughts

 

If you read back through this tutorial, you will see that "reloadData" does not appear anywhere here.  reloadData is sort of the nuclear option, and generally speaking it is cheating.  Reloading the table also defeats any animations that might otherwise happen.  In this app right now, if you insert an item, you'll get a nice animation showing the new row.  If you delete an item, you'll get a nice animation removing the row.  The remaining rows are shifted up or down in a nice animated fashion.

 

You could go to the above code, replace each insert/delete line with a reload line, and the end result will still be a table showing the correct contents.  So it does actually "work" to do the reload.  But if you watch carefully, there will be a difference.  The new row just pops into existence.  Deleted rows just instantly disappear.  The animations are gone.  Reloading a table doesn't do any animations.  The user experience is not as good, and frankly using reloadData in these situations is Amateur Hour.  It isn't required, as obviously evidenced by the code above.  So go the extra mile, and don't use table reloading as a replacement for actually understanding how these things work.

 

UITableViewDataSource and UITableViewDelegate define a number of methods to further refine these behaviors.  Have a row that you don't want to be deleted?  There's a method for that.  Have a row that you don't want to be moved?  There's a method for that.  Want to limit where a row can be dragged?  There's a method for that, too.  All I have shown here is the bare minimum necessary to enable these features.  For more complicated behaviors, you will need to employ some or all of these other methods.  See the documentation for a full list of what is available.

 

Here is the sample project for this tutorial:

 

BTITableViewModification.zip

UITableView How-To: Part 5 - More About XIB-Based Cells

Part 1 | Part 2 | Part 3 | Part 4 | Part 6

 

(This is really the second half of Part 4, so we'll just continue with the same project)

 

I've got one more key topic to address, then we'll take a look at some alternate techniques.

 

By the end of Part 4, we created a custom view layout using IB, so we've got the visual part down. That really just leaves one more basic characteristic to talk about: actions. In addition to customizing the look of your cell, you can customize the behavior of your cell, and this is most easily done by adding controls such as buttons, switches, and so on. We're just going to do a quick button, but the concept applies to any of the other controls you may wish to use.

 

So, open up CustomTableCell.xib and drop a button smack in the middle:

 

 

With a typical view controller, we'd connect this button to an IBAction. That's what we want to do here as well, but the question is: where is the IBAction? There are 2 basic options, in the cell class or in the view controller class. Where you should put it will depend on the goal you are trying to accomplish. If you put it in the cell class, then it really isn't any different than wiring it up as you would in a view controller. Depending on your goal, this approach can either simplify or complicate your ultimate outcome. But for our purposes here, we're going to put the IBAction in the view controller. So add this declaration to BasicViewController.h:

 

- (IBAction)buttonPressed:(UIButton *)button;

 

(you are probably accustomed to seeing (id)sender for these things. Go ahead and do that if you prefer. I like the type to be correct, and rarely have a need to mix these methods with multiple types of controls)

 

Now we just need to wire it up. You're going to drag from the button to File's Owner like you normally would. The only difference is that this is happening in the cell class instead of the view controller class.

 


 

All that's left is to make the method do something. You can do a simple stub for now to make sure that this works:

- (IBAction)buttonPressed:(UIButton *)button
{
NSLog(@"It worked!");
}

 

Go ahead and run the app, and you should see this message in the console when you hit any button. And now we reach the fun part. As you can see, this method fires when you hit any button. So, how do you know which button?

 

There are a couple of ways to answer this question and, like so many programming challenges, which way to go will depend on your situation. You could use the tag property of the button. This is a common thing to do when you have multiple buttons and would like to distinguish between them. You would need to declare a property in the cell for the button, and then you could assign the tag value in cellForRow like this:

 

[[cell theButton] setTag:[indexPath row]];

 

Then you extract the tag value in the IBAction method:

 

NSLog(@"Pressed button at row: %d", [button tag]);

 

For many tables, this will be an adequate and simple approach. However, if you recall some behaviors from Part 3 regarding multi-section tables, then your spidey sense should be warning you that something may be wrong here. We are using the row parameter to identify the cell, but what happens if there is more than one section? You could have row 0 in the first section, and row 0 in the second section, and row 0 in the third section... Remember that row indexes reset in each section. This is why NSIndexPath provides TWO parameters - section and row - because only one parameter is insufficient to find/describe the location.

 

UIButton doesn't have an NSIndexPath property. If you really wanted to, you could subclass UIButton to add that property. Then the approach above would be the same, except you would provide/extract the indexPath instead of merely the row. I personally tend to view subclassing as a last resort, so I've never bothered doing this. But I suppose I could see some situations where BSTableButton could come in handy. But that isn't why I've brought you here today.

 

No matter what the approach, you have to do some work. Above we've done a little work in cellForRow, and a little work in the IBAction method. (And if you subclassed, you've done a lot of work in even more places) Also think in terms of performance. You are flicking your way through a table, and you want to get in and out of cellForRow as fast as possible. Assigning the tag (or indexPath) is just one more thing that has to be done that is preventing you from leaving the method ASAP. So instead of doing the work for each cell, just do the work when the user actually hits the button. The question is how.

 

In the IBAction, I receive the button itself as a parameter. From there, I would like to wind up with an indexPath. This will allow me to know that the 5th button was pressed, so I can go do something to the 5th item in the array. A quick glance at the UITableView documentation reveals a handful of methods that return an indexPath. For all visible rows. For the selected row (before you get excited, this is only for the blue highlighted row, not for tapping on items in the row). The only one that seems useful in this situation is indexPathForCell:. If I have a cell, the table can tell me where it is located.

 

Ok, so how do I get to the cell from the button. Ah ha! Remember that these are all views, and that they have been arranged into an hierarchy. The button would be a subview of the cell, so since I'm starting with the button, I would need to walk UP the hierarchy. You do that with the superview method.

 

This part can require some learning and trial-and-error, so let's do it the hard way before we skip to the answer. Add this to the IBAction:

 

NSLog(@"superview is: %@", [[button superview] description]);

 

Run it and you should see something like this:

 

superview is: <UITableViewCellContentView: 0x4d31390; frame = (0 0; 320 43); layer = <CALayer: 0x4d313f0>>

 

Hrm, UITableViewCellContentView. We're looking for CustomTableCell, so that's not a match. If you look at the top of the UITableViewCell documentation, you'll find the contentView property mentioned, and then you can go read its description. Basically, what we added the button to was the contentView, not the cell directly. So we need to go higher in the hierarchy:

 

NSLog(@"superview is: %@", [[button superview] description]);
NSLog(@"superview superview is: %@", [[[button superview] superview] description]);

 

Try again:

 

superview is: <UITableViewCellContentView: 0x4d31390; frame = (0 0; 320 43); layer = <CALayer: 0x4d313f0>>
superview superview is: <CustomTableCell: 0x4b3ad70; baseClass = UITableViewCell; frame = (0 264; 320 44); autoresize = W; layer = <CALayer: 0x4b39e20>>

 

Bingo. So, with this particular hierarchy, to go from the button to the cell, we need to go up 2 levels in the view hierarchy. This will depend on your actual view hierarchy, which is why I showed the long steps first. Just keep walking up the hierarchy until you find the cell. (I'm sure you could come up with an algorithm to keep walking, but it's never been worth the effort for me) Now that we know where we're going, our actual line of code is:

 

CustomTableCell *cell = (CustomTableCell *)[[button superview] superview];

 

We have the cell, and can now get the indexPath:

 

NSIndexPath *indexPath = [[self mainTableView] indexPathForCell:cell];
NSLog(@"indexPath is: %@", [indexPath description]);

 

Now you have the exact same location that you would have in cellForRow. So you can access your array the same way, and go manipulate your data or otherwise make something happen as you see fit.

 

Cell Identifiers Suck

 

I mentioned in Part 4 that I don't like the way Apple put the cell identifier into the cell's XIB. About a year ago, I discovered a blog post by Jeff LaMarche that provides a workaround. I won't bother repeating what he has said there, so let's just apply the code with a little twist.

 

Add this to CustomTableCell.h:

 

+ (NSString *)reuseIdentifier;

 

And then add these to CustomTableCell.h:

 

- (NSString *)reuseIdentifier
{
return [[self class] reuseIdentifier];
}

+ (NSString *)reuseIdentifier
{
return NSStringFromClass([self class]);
}

 

It's the same thing Jeff shows, I just took out the constant declaration as mentioned in the comments. This allows for easy copy-pasting into any cell class.

 

You can now blank out the identifier setting in IB, or forget to include it altogether.

 

And Now For Something New

 

There are a variety of debates regarding XIB-based cells. First there are the usual arguments with the "I only use code, IB is the debbil" people. Then there is another layer for use in cells, with people arguing that XIBs are slower than code because you have to go do disk to load the cell. There must have been some truth to this, because Apple added a new class for OS 4 to address it. It's too bad it took so long, because if anything we needed it more in slower-hardware days of OS 2 than we do today, but they felt it was important enough to create, so we should at least give it a look.

 

Now presenting: UINib.

 

Read the documentation and you'll find that it is basically a caching and instantiation class for loading XIBs. You can use it for any kind of XIB loading, but you can be pretty confident that it was developed for table cells. It doesn't wind up making a tremendous difference in terms of code - if anything it adds some - but I'll take Apple at their word that this is faster in terms of performance. I like using UINib as a property, so add this to BasicViewController.h:

 

@interface BasicViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
{
}

@property (nonatomic, retain) IBOutlet UITableView *mainTableView;
@property (nonatomic, retain) IBOutlet CustomTableCell *customTableCell;
@property (nonatomic, retain) UINib *customTableCellNib;

 

And then I implement it with a custom getter in the .m file:

 

@@synthesize customTableCellNib = ivCustomTableCellNib;
...

// dealloc
[self setCustomTableCellNib:nil];
...

- (UINib *)customTableCellNib
{
if (ivCustomTableCellNib == nil)
{
ivCustomTableCellNib = [[UINib nibWithNibName:NSStringFromClass([CustomTableCell class]) bundle:nil] retain];
}
return ivCustomTableCellNib;
}

 

I customize the getter so that I don't have to care who creates it or when. The first time it is needed, it will get created.

 

So we've added a property, and we've added a method. What's the payoff? Well, not much really. Here is what we had before:

 

if (cell == nil)
{
[[NSBundle mainBundle] loadNibNamed:@"CustomTableCell" owner:self options:nil];
cell = [self customTableCell];
[self setCustomTableCell:nil];
}

 

This now becomes:

 

if (cell == nil)
{
[[self customTableCellNib] instantiateWithOwner:self options:nil];
cell = [self customTableCell];
[self setCustomTableCell:nil];
}

 

Wow. We changed one line of code. Yay. Well, this is more about performance than it is about code reduction.

 

A Couple Of Tweaks

 

Speaking of performance, there is one thing you want to be careful of in cell design, whether it is in code or via XIB: avoid transparency. With our fancy iPhone 4's and iPad 2's, this is becoming less and less of an issue, but the basic premise remains the same: the device likes opacity a whole lot more than it does transparency.

 

What this specifically means as far as the view items go is to turn on the Opaque toggle, and supply a background color. The Opaque toggle is here:

 

 

When you first turn it on, the items will often default to black. Simply apply a different color:

 

 

Naturally this won't work if your background is not consistent, like a gradient or an image. In those cases, you're pretty much stuck with the transparency.

 

The next thing to be aware of isn't performance related, but it is very visual so you'll want to make sure to catch it. For labels (and some others like image views) you will want to specify a highlight color:

 

 

This is the color that the text will be when the user highlights a row. Generally speaking you will want it to be white, but that's a design decision for you. At some point in the last couple releases of Xcode, and I'm not sure when this started, the default automatically became white. So hopefully you shouldn't have to mess with this too much. But if you're on a slightly older version of Xcode, be aware that the default used to be black. Regardless, if you tap a cell, and don't see nice white text, this is where you need to go to fix it.

 

Holy Crap, This Is Long

 

Now you know why I split this into 2 parts. Head back to the cell's XIB, and we're going to make a slight change. Make the cell taller. You can add more stuff if you want, but for immediate purposes, all I want is something like this:

 

 

Depending on how you set up the label's masks, when you run the app now you should get something like this:

 

 

Well that's no good. What happened here? I made the cell taller, why isn't it displaying that way? Ah, because nobody told the table that the row height should change. So we need to do that.

 

The wrong answer is to use this delegate method:

 

tableView:heightForRowAtIndexPath:

 

This method should ONLY be used if your table will feature more than 1 row height. If you have tall rows and short rows and everything in between, then this method is pretty much your only option. But if your rows are all the same height, then you will slam against this method over and over and over again (seriously, add some logs and watch how much it gets called) just to return the same number each time.

 

Fortunately, there is a better answer: UITableView has a rowHeight property. In fact, we've already utilized it, we just didn't realize it. Open up the BasicViewController.xib, select the table view, then hit Cmd-3. Right there at the top of the inspector: row height of 44.0. So we could simply change this number to match whatever our cell height is, and things will be good.

 

Or will they? Hard-coding numbers is generally something to avoid. And really, we could change the cell design again, and change the height again, and then we just forget to come back and change this setting. Wouldn't it be nice if we could establish a link between our cell height and our row height?

 

We can't quite get there directly, but we sure can take a measurement and use that to define the row height.

 

So first we need a cell. viewDidLoad is a good place to do this, and we are literally going to do the same thing we do in cellForRow to get a cell:

 

[[self customTableCellNib] instantiateWithOwner:self options:nil];

 

From what we learned before, we know that the cell property is now occupied. So we can grab that cell, take a measurement, and drive the table:

 

[[self customTableCellNib] instantiateWithOwner:self options:nil];
[[self mainTableView] setRowHeight:[[self customTableCell] frame].size.height];
[self setCustomTableCell:nil];

 

We get the frame of the cell, then get the height, and give that value to the rowHeight property. Then we clear out the cell property since we're done with it.

 

Try again:

 

 

Muuuuch better. Now you can change the cell's height as much as you want, and the table will always display the correct row height.

 

Thus concludes a couple of lengthy posts on this topic. But don't let that volume scare you away from using XIB-based cells. After you've run through the process a few times, it gets less intimidating. And once you're there, you get to take full advantage of Interface Builder's promise to make view layout easier.

 

TableViewTutorial_Part5.zip