Skip to content

Customize the Back Button of UINavigationItem in the Navigation Bar

I’ve recently noticed a pattern of developers having trouble customizing the back button in the navigation bar. If you’re reading this, the problem may sound familiar.

When you use a UINavigationController to create a navigation stack, iOS conveniently and automatically creates a back button having the title that is the title of the previous view controller or the word Back if the previous view controller has no title. Of course, many times you want to override that default title or perhaps even use an image in its place. Let’s use Apple’s Master-Detail Application template as an example.

Let’s assume that instead of the word back or the title of the MasterViewController, you wanted to use the word Previous. You’ve looked at the UINavigationItem documentation and so in the -viewDidLoad method DetailViewController, you create an instance of UIBarButtonItem with the title Previous, and then set the backBarButtomItem of the detail view controllers’s UINavigationItem to this bar buttom item you created.

- (void)viewDidLoad
{
    [super viewDidLoad];
    UIBarButtonItem *backButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Previous" style:UIBarButtonItemStylePlain target:nil action:nil];
    self.navigationItem.backBarButtonItem = backButtonItem;
}

You then run your app and ¡Viola! … a big disappointment as nothing seemed to happen. You’ve double-checked your code, you read the docs again, you move the statements to -viewWillAppear and viewDidLoad and still nothing. 

So what most developers do that have gotten stuck here is create a custom bar button item and set the left button of the navigation item to it. However, then you get a square button instead of the button with the triangular end. So then the graphics designer gets involved and creates a custom image which developers then use to create a custom UIButton to use as a custom view for the hand-spun back button. The developers aren’t happy with it, it seems like a hack, especially given that Apple’s documentation clearly says you can customize the back button!  WTF???

Now I think that taken as a whole, Apple’s documentation of the Cocoa and iOS frameworks are the best of any framework documentation. Easy to read, very description, easy to navigate (although, I wonder why searching web version is so much faster than the local version within Xcode). Occasionally, however, Apple makes you read between the lines when an additional sentence or two would save some developers a few hair-pulling incidents. The documentation for the `backButtonItem` property is one of those times. Let’s look at the description:

When this navigation item is immediately below the top item in the stack, the navigation controller derives the back button for the navigation bar from this navigation item. When this property is nil, the navigation item uses the value in its title property to create an appropriate back button. If you want to specify a custom image or title for the back button, you can assign a custom bar button item (with your custom title or image) to this property instead. When configuring your bar button item, do not assign a custom view to it; the navigation item ignores custom views in the back bar button anyway.

It’s very important to read the first sentence carefully, “When this navigation item is immediately below the top item in the stack… .” What this is saying, and what developers sometimes miss, is that the back button is NOT set in navigation item of the visible view controller, but instead, it is set in the navigation item of the previous view controller. This throws everyone off because the rest of the items in the navigation bar is controlled by the navigation item of the visible view controller. As you can see, the documentation says that the back button in the bar is derived from the back button item in the navigation item immediately below the top item in the stack.

Going back to the MasterDetail example application, this means that the statements in the -viewDidLoad statement belong in the -viewDidLoad method in MasterViewController.

Also, make sure to notice that you cannot create a custom view to use for the back button. Custom views are ignored. You can still set the background image of the back bar button item with the – setBackButtonBackgroundImage:forState:barMetrics:.

 

UIColor colorFromHex?

Don’t get me wrong. I love the Cocoa and iOS frameworks, but seriously, how could Apple still not have a method that returns color based on hex code. Web designers use them a lot, especially in CSS. And so when those designers pass the specs on to us developers, we always have to go through the process of calculating the RGB values.

Well enough of that, here is a category for UIColor which adds a class and instance method that returns a color based on its hex code.

My Gist: https://gist.github.com/1656060 https://gist.github.com/1673195

Load images asynchronously in a UITableView using GCD (Grand Central Dispatch)

Recently, I needed to create a UITableView with UITableViewCells with the image property of the cells’ imageView property set to images retrieved from the web.

First, I didn’t want the images retrieved from the web until the tableView sent the message -tableView:cellForRowAtIndexPath: to the dataSource. Second, once the image was retrieved a first time, should the tableView send the message again for the same row, I did not want to retrieve the image again.

To solve the first requirement, I simply set the image in the dataSource method

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
    if (!cell) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 
                                       reuseIdentifier:@"Cell"] autorelease];
    } NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://www.images.com/images/1"]];
    [[cell imageView] setImage:[UIImage imageWithData:imageData]];
    [[cell textLabel] setText:@"My Beautiful Image"];
    return cell;
}

That was easy enough. Now I know what you’re thinking (or should be thinking), loading images this way is not asynchronous. And you’re absolutely right. While investigating the easiest way to do this, I found several solutions including some that involved using web views. However, what I found the easiest to implement was to use Apple’s Grand Central Dispatch or “GCD.”

First I created a dispatch queue in the init method of the class I am using as my dataSource:

- (id)init
{
    if (self = [super init]) {
        imageQueue_ = dispatch_queue_create("com.company.app.imageQueue", NULL);
    }
    return self;
}

Don’t forget to use proper memory management techniques on the image_queue. I will leave that as an exercise to the readers.


Next, I changed my dataSource method as follows:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
    if (!cell) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"] autorelease];
    }
    NSMutableDictionary *record = [_records objectAtIndex:[indexPath row]];    
    dispatch_async(imageQueue_, ^{
        NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[record imageURLString]];
        dispatch_async(dispatch_get_main_queue(), ^{
            [[cell imageView] setImage:[UIImage imageWithData:imageData]];
            [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
        });
    });
    [[cell textLabel] setText:[record title]]
    return cell;
}

So here, I dispatched the block asynchronously to the image_queue I create and then got the dataWithContentsOfURL. At this point the main thread goes on its merry way and returns the cell to the tableView. When the image is loaded, I then created a block that gets queued up in the main_queue, a default serial queue, which sets the imageView’s image property. Then, I told send a message to the tableView to reload the row at the index path.
That takes care of the asynchronous portion of the tutorial, but don’t go away yet. I still had another requirement—”caching” the images. I put caching in quotes because I cheated and never had to create any real sort of cache. The objects of my array are all instances of NSMutableDictionary. So what I did was to simply add the image to the dictionary as a key value.

Added Thanks to a comment from JackTheVain, I decided to look at an issue I’ve seen occur a couple of times. That is, the image for a row is sometimes the wrong image. This is because inside the GCD block, I am setting the image of the cell. However, cells are reused so the cell that was being used to represent a particular object might be used to represent a different object by the time the message is sent to the cell. Well that line isn’t even needed. We’re telling the tableview to redraw the row after the image is received and now the image is in memory. So I’ll be deleting that line.

Thus my dataSource method finally looked something like this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
    if (!cell) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"] autorelease];
    }
    NSMutableDictionary *record = [_records objectAtIndex:[indexPath row]];
    if ([record valueForKey:@"actualImage"]) {
        [[cell imageView] setImage:[record valueForKey:@"actualImage"]];
    } else {
        dispatch_async(imageQueue_, ^{
            NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[record imageURLString]]];
            dispatch_async(dispatch_get_main_queue(), ^{
                [record setValue:[UIImage imageWithData:imageData] forKey:@"actualImage"];
                [[cell imageView] setImage:[record valueForKey:@"actualImage"]];
                [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
            });
        });
    }
    [[cell textLabel] setText:[app valueForKey:@"name"]];
    return cell;
}

Thanks for reading. Hope someone found it useful.

IBOutlets as Properties and Memory Management in iOS 5 SDK

Private properties

If you look at the code of many iOS developers who developed for Cocoa before iOS, you’re likely to see a common pattern. IBOutlets were simply declared as ivars and then can be referenced in the class and connected in the IB files (which I will call “nibs” because I never got comfortable pronouncing “xib”).

@interface MyViewController : NSObject
{
    IBOutlet NSButton* submitButton;
}
@end

I always felt that the public variables of a class should help define the “state” of the class. In the case of controllers with connections to nibs, I don’t think that many of the objects declared as IBOutlets needed to be declared as public variables. The question I often asked myself is why would some object outside of the controller need to know the state or need to change the state of the objects declared as IBOutlets. When there is no answer (as often is the case), I would declare them after @private or @protected in the header.

Now with Objective-C 2.0 and the introduction of properties, synthesized variables, and automatic reference counting (“ARC”), Apple seems to be eliminating the need to declare ivars. Well what about properties that you want to keep hidden from the public interface. The solution is to use an unnamed category in your implementation file.

@interface MyViewController ()
@property (strong, nonatomic) IBOutlet UIButton* submitButton;
@end
@implementation MyViewController

@synthesize submitButton;

// rest of the implementation

@end

You can now connect this IBOutlet in Interface Builder yet keep the property “private.”

Memory Management of IBOutlets

Developers must manage the allocation of memory of IBOutlets referenced in instances of UIViewController (or its subclasses). The preferred method differs depending on whether ARC is being used or not. I would suggest that if you are developing for iOS 5 or later, use ARC.

IBOutlet Memory Management with ARC

If I am developing for iOS 5, I will use ARC and handle my IBOutlets like so. First, I will decade the property for the IBOutlet as I discussed above. Second, I will synthesize the getters & setters (the ivars are synthesized automatically). Finally, in -viewDidUnload I will set the value of the property to nil.

@interface MyViewController ()
@property (strong, nonatomic) IBOutlet UIButton* submitButton;
@end
@implementation MyViewController

@synthesize submitButton;

- (void)viewDidUnload
{
    [self setSubmitButton:nil];
// You can also use self.submitButton = nil; but I'm old-school

    [super viewDidUnload];
}

@end

IBOutlet Memory Management without ARC

If I am developing for iOS 4 or earlier or for some other reason, can’t use ARC, I will handle my IBOutlets like so. First, I will decade the property for the IBOutlet as I discussed above. Second, I will synthesize the getters & setters and set the ivar manually. Finally, in -dealloc I will release the ivar.

@interface MyViewController ()
@property (retain, nonatomic) IBOutlet UIButton* submitButton;
@end
@implementation MyViewController

@synthesize submitButton = _submitButton;

- (void)dealloc
{
    [_submitButton release];
// Apple advises against using accessors in dealloc methods

    [super dealloc];
}

@end

I like to put the dealloc right after my @synthesize statements so that I can make sure that I released everything I created.

Delegate methods: There is NO magic. @cappuccino #cocoa

I have read many blogs and books about Cocoa and Cappuccino. Almost every one of them absolutely suck at explaining the Delegate design pattern used in Cocoa and Cappuccino. They say things like delegation describes “where an object, instead of performing one of its stated tasks, delegates that task to an associated helper object.” That’s as useful as an umbrella in a hurricane.

Let me put it simply. A delegate method is … wait for it … a method.  That’s it. And what do you do with a delegate method. Frankly, anything you want your class to do when it receives that message. Again that’s it.

Certain Cocoa/Cappuccino classes have a property call delegate. They have statements that check to if delegate is not nil, and if it will respond to a certain message. If the answer to both of those is yes, it will send that particular message when it was programmed to send it.

For example, NSURLConnection/CPURLConnection is programmed to send the following message to its delegate:

[delegate  connection:self didReceiveData:data];

Don’t believe me look at the source code, you will find that statement in there.

And there is nothing magical about it.

On the flip side, if you created a class and included the method;

- (void)connection:(CPURLConnection)connection didReceiveData:(CPString)data

and you set an instance of your class as CPURLConnection’s delegate, then at the right time the CPURLConnection instance will send your class that message.

Pay attention, this is tricky now. What should I put inside a delegate method? The answer: ANYTHING YOU WANT. More specifically anything that you want to execute when your class receives that message. Maybe you want to do something with the data, maybe you want a spinner to disappear, maybe you want to draw a pretty flower on your screen…it doesn’t matter.

Questions?

CPImageView flickering when setting CPImage in @cappuccino

Are you, or is someone you know, suffering from CPImageView flickering? Read now for this no-obligation tip.

When creating a CPImage from a web resource, do not immediately set the image of a CPImageView to that image. You will may result in a flicker.

Instead set a delegate to the CPImage (e.g. [myImage setDelegate:self]) and then set the CPImageView’s image in the delegate method -(void)imageDidLoad:(CPImage)anImage

This should alleviate you and your loved ones from CPImageView flickering.

This was NOT a paid message by the Brotherhood of Post Atari 2600 Computing.

Custom buttons & nib2cib in Cappuccino

nib2cib is an invaluable tool when creating web apps using Cappuccino because it allows you to create your UI using Interface builder.

If you want to use a subclass of CPButton (and I would image this holds true for other CPControl subclasses), you’ll may run into a couple of problems. First, if you place a NSButton on the view then change it’s class to your custom class name, you’ll find that nib2cib won’t display your custom button. Alternative, if you place a NSView on the view, nib2cib will display your button but when you try to create connection the sent perform action to a class that could receive that connection, you’ll find that connection missing because it does not exist for NSView.

One solution is to place on the view an instance of NSControl. On the view in nib2cib it will appear as an instance of NSView so you can size it to the proper dimensions, but because it is NSControl you can also connect the send action to the appropriate receiver.

After you have connected the action, THEN you can change the class name of the object from NSControl to your custom class name. IB will give you a warning, but you’ll find that after you run nib2cib and refresh your browser, the button will work as expected

ADDED

If your custom button code sets images to the button such as setting the bezel-color value to a custom image, you will need to get nib2cib to find those Resource. To do that you must use the -R switch.

For example  (Assuming I’m in the Resources directory):
nib2cib myAwesomeWindow.xib -R .

The “.” at the end instructs nib2cib to use the current directory to check for resources.

 

Follow

Get every new post delivered to your Inbox.

Join 44 other followers