An info button is an easy and straightforward way to provide access to a modal view in your application.

It’s especially useful when you want to display something like an about screen but don’t have a dedicated area for settings.

To add one, simply create a button in the viewDidLoad method of your view controller and add it to the title bar:

UIButton *infoButton = [UIButton buttonWithType:
    UIButtonTypeInfoLight];
[infoButton addTarget:self action:@selector(showInfo:)
    forControlEvents:UIControlEventTouchUpInside];

UIBarButtonItem *rightBarButtonItem = [[UIBarButtonItem alloc]
    initWithCustomView:infoButton];
self.navigationItem.rightBarButtonItem = rightBarButtonItem;
[rightBarButtonItem release];


In the example above I created an info button with a light background and set it as the right bar button item in the title bar of the view controller.

To finish, you just need to define the showInfo method and display a modal view when users tap the button:

- (IBAction)showInfo:(id)sender
{
    if (! self.infoViewController)
    {
        self.infoViewController = [[InfoViewController alloc]             initWithNibName:NSStringFromClass
            ([InfoViewController class]) bundle:nil];
    }

    UINavigationController *infoViewNavController =
        [[UINavigationController alloc]
        initWithRootViewController:self.infoViewController];

    [self.navigationController presentModalViewController:
        infoViewNavController animated:YES];
    [infoViewNavController release];
}


I declared an instance of my modal view (InfoViewController) in the view controller’s header file since the view never changes and doesn’t need to be recreated each time.

I was going to write a post about how casting integers as strings is easy in Flex and hard in Objective-C, but who am I kidding? It’s easy in both.

In general, you’ll probably be dealing mostly with ints (primitives) and NSNumber objects in your code:

NSNumber *myNumber;
int myInt;


So how do you cast them as strings?

NSString *myNumberString = [myNumber stringValue];
NSString *myIntString = [[NSNumber numberWithInt:myInt] stringValue];


Easy, right? The only real trick is you have to convert an int to an NSNumber object before you cast it as a string.

Once you’ve mastered using localized strings in Xcode 4, you can move on to performing substitutions in those strings.

The benefit of this approach is two-fold. Strings values easily calculated at runtime don’t need to be individually defined, and those same strings can still be localized.

For example, let’s say you have a UITableView, and for each cell you want to display its index in the table:

Instead of five individually defined strings, you’d have a single string in your Localizable.strings file with a placeholder for the table index:

/*
  Localizable.strings
  How to Perform String Substitutions in Xcode 4

  Created by Miscellanea on 11/24/11.
  Copyright 2011 __MyCompanyName__. All rights reserved.
*/

"CellTitle" = "Item #%s";


Then in your UITableViewController implementation you’d reference the string and perform a substitution for the table index:

- (UITableViewCell *)tableView:(UITableView *)tableView
    cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView
        dequeueReusableCellWithIdentifier:CellIdentifier];

    if (cell == nil)
    {
        cell = [[[UITableViewCell alloc]
            initWithStyle:UITableViewCellStyleSubtitle
            reuseIdentifier:CellIdentifier] autorelease];
    }

    NSString *itemNumber;

    // FIXME: we'll improve this in a later post!
    switch (indexPath.row)
    {
        case (0):
            itemNumber = @"1";
        break;

        case (1):
            itemNumber = @"2";
        break;

        case (2):
            itemNumber = @"3";
        break;

        case (3):
            itemNumber = @"4";
        break;

        case (4):
            itemNumber = @"5";
        break;

        default:
        break;
    }

    [cell.textLabel setText:[NSLocalizedString(@"CellTitle", @"")
        stringByReplacingOccurrencesOfString:@"%s"
        withString:itemNumber]];

    return cell;
}


This isn’t limited to numeric substitutions; it’s especially useful for titles and labels with prefixes, or anywhere a string needs to reflect a dynamic state or view.

In a future post I’ll explain a much better way to display numbers as strings.

Similar to resizing a UIView when the orientation changes, it can be useful to have a UILabel automatically resize itself based on its text.

For example, let’s say you have a UILabel with a UITextView below it, and you want the UITextView to stay anchored below the UILabel.

Here’s how you’d do it:

- (void)refreshTextLayout
{
    CGRect myLabelFrame = [self.myLabel frame];
    CGSize myLabelSize = [self.myLabel.text         sizeWithFont:self.myLabel.font
        constrainedToSize:CGSizeMake(myLabelFrame.size.width, 9999)
        lineBreakMode:UILineBreakModeWordWrap];

    CGFloat delta = myLabelSize.height – myLabelFrame.size.height;

    myLabelFrame.size.height = myLabelSize.height;
    [self.myLabel setFrame:myLabelFrame];

    CGRect myTextViewFrame = [self.myTextView frame];
    myTextViewFrame.origin.y += delta;
    myTextViewFrame.size.height -= delta;
    [self.myTextView setFrame:myTextViewFrame];
}


This code measures the size of the text in the UILabel (given its current width) and adjusts the UILabel’s frame to fit the text. It then updates the UITextView’s frame to reposition it below the UILabel and adjusts its height to fill the remainder of the view.

So how does this happen automagically?

You simply need to invoke refreshTextLayout in the viewWillAppear and didRotateFromInterfaceOrientation methods in the parent view controller:

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

    [self refreshTextLayout];
}

- (void)didRotateFromInterfaceOrientation:
        (UIInterfaceOrientation)fromInterfaceOrientation
{
    [self refreshTextLayout];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:
        (UIInterfaceOrientation)interfaceOrientation
{
    return YES;
}


If you don’t need to support multiple device orientations then you can omit the last two methods above.

It’s easy to add a property to a UIViewController, but how do you access it from Interface Builder when you’re done?

Let’s say you have a UILabel and a UITextView. To connect them to objects in IB, you need to add an IBOutlet attribute to each of their @property declarations:

//
//  CustomViewController.h
//  App Name
//
//  Created by Miscellanea on 11/10/11.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface CustomViewController : UIViewController
{
    UILabel *myLabel;
    UITextView *myTextView;
}

@property (nonatomic, retain) IBOutlet UILabel *myLabel;
@property (nonatomic, retain) IBOutlet UITextView *myTextView;

@end


Now you can switch to Interface Builder and create the same objects in the corresponding user interface (.xib) file.

To connect them, select each object in IB and open the Connections Inspector. (Command-Option-6 or View > Utilities > Connections Inspector) Under Referencing Outlets, click on the circle to the right of “New Referencing Outlet” and drag it to the File’s Owner. It should draw a blue line as you do this. Then select the appropriate property for each object in the menu that appears.

Afterwards you should see a new connection in Referencing Outlets. That’s it! Now you can configure the objects in your view controller’s implementation file.

Follow

Get every new post delivered to your Inbox.