UINavigationController - iOS Programming: The Big Nerd Ranch Guide (2014)

iOS Programming: The Big Nerd Ranch Guide (2014)

10. UINavigationController

In Chapter 6, you learned about UITabBarController and how it allows a user to access different screens. A tab bar controller is great when you have screens that do not rely on each other, but what if you want to move between related screens?

For example, the Settings application has multiple related screens of information: a list of settings (like Sounds), a detailed page for each setting, and a selection page for each detail. This type of interface is called a drill-down interface.

Figure 10.1 Settings has a drill-down interface

Settings has a drill-down interface

In this chapter, you will use a UINavigationController to add a drill-down interface to Homepwner that lets the user view and edit the details of a BNRItem (Figure 10.2).

Figure 10.2 Homepwner with UINavigationController

Homepwner with UINavigationController

UINavigationController

When your application presents multiple screens of information, a UINavigationController maintains a stack of those screens. Each screen is the view of a UIViewController, and the stack is an array of view controllers. When a UIViewController is on top of the stack, its view is visible.

When you initialize an instance of UINavigationController, you give it one UIViewController. This UIViewController is the navigation controller’s root view controller. The root view controller is always on the bottom of the stack. More view controllers can be pushed on top of theUINavigationController’s stack while the application is running.

When a UIViewController is pushed onto the stack, its view slides onto the screen from the right. When the stack is popped, the top view controller is removed from the stack and its view slides off to the right, exposing the view of next view controller on the stack.

Figure 10.3 shows a navigation controller with two view controllers: a root view controller and an additional view controller above it at the top of the stack. The view of the additional view controller is what the user sees because that view controller is at the top of the stack.

Figure 10.3 UINavigationController’s stack

UINavigationController’s stack

Like UITabBarController, UINavigationController has a viewControllers array. The root view controller is the first object in the array. As more view controllers are pushed onto the stack, they are added to the end of this array. Thus, the last view controller in the array is the top of the stack.UINavigationController’s topViewController property keeps a pointer to the top of the stack.

UINavigationController is a subclass of UIViewController, so it has a view of its own. Its view always has two subviews: a UINavigationBar and the view of topViewController (Figure 10.4). You can set a navigation controller as the rootViewController of the window to make its view a subview of the window.

Figure 10.4 A UINavigationController’s view

A UINavigationController’s view

In this chapter, you will add a UINavigationController to the Homepwner application and make the BNRItemsViewController the UINavigationController’s rootViewController. Then, you will create another subclass of UIViewController that can be pushed onto the UINavigationController’s stack. When a user selects one of the rows, the new UIViewController’s view will slide onto the screen. This view controller will allow the user to view and edit the properties of the selected BNRItem. The object diagram for the updated Homepwner application is shown in Figure 10.5.

Figure 10.5 Homepwner object diagram

Homepwner object diagram

This application is getting fairly large, as you can see in the massive object diagram. Fortunately, view controllers and UINavigationController know how to deal with this type of complicated object diagram. When writing iOS applications, it is important to treat each UIViewController as its own little world. The stuff that has already been implemented in Cocoa Touch will do the heavy lifting.

Now let’s give Homepwner a navigation controller. Reopen the Homepwner project and then open BNRAppDelegate.m. The only requirements for using a UINavigationController are that you give it a root view controller and add its view to the window.

In BNRAppDelegate.m, create the UINavigationController in application:didFinishLaunchingWithOptions:, give it a root view controller of its own, and set the UINavigationController as the root view controller of the window.

- (BOOL)application:(UIApplication *)application

didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]];

// Override point for customization after application launch

BNRItemsViewController *itemsViewController

= [[BNRItemsViewController alloc] init];

// Create an instance of a UINavigationController

// its stack contains only itemsViewController

UINavigationController *navController = [[UINavigationController alloc]

initWithRootViewController:itemsViewController];

self.window.rootViewController = itemsViewController;

// Place navigation controller's view in the window hierarchy

self.window.rootViewController = navController;

self.window.backgroundColor = [UIColor whiteColor];

[self.window makeKeyAndVisible];

return YES;

}

Build and run the application. Homepwner will look the same as it did before – except now it has a UINavigationBar at the top of the screen (Figure 10.6). Notice how BNRItemsViewController’s view was resized to fit the screen with a navigation bar. UINavigationController did this for you.

Figure 10.6 Homepwner with an empty navigation bar

Homepwner with an empty navigation bar

An Additional UIViewController

To see the real power of UINavigationController, you need another UIViewController to put on the navigation controller’s stack. Create a new Objective-C class (File → New → File...). Name this class BNRDetailViewController and choose UIViewController as the superclass. Check the With XIB for user interfacebox (Figure 10.7).

Figure 10.7 Create UIViewController subclass with XIB

Create UIViewController subclass with XIB

In BNRDetailViewController.m, delete all of the code between the @implementation and @end directives so that the file looks like this:

#import "BNRDetailViewController.h"

@interface BNRDetailViewController ()

@end

@implementation BNRDetailViewController

@end

In Homepwner, you want the user to be able to tap an item to get another screen with editable text fields for each property of that BNRItem. This view will be controlled by an instance of BNRDetailViewController.

The detail view needs four subviews – one for each instance variable of a BNRItem instance. And because you need to be able to access these subviews during runtime, BNRDetailViewController needs outlets for these subviews. The plan is to add four new outlets to BNRDetailViewController, drag the subviews onto the view in the XIB file, and then make the connections.

In previous exercises, these were three distinct steps: you added the outlets in the interface file, then you configured the interface in the XIB file, and then you made connections. You can combine these steps using a shortcut in Xcode. First, open BNRDetailViewController.xib by selecting it in the project navigator.

Now, Option-click on BNRDetailViewController.m in the project navigator. This shortcut opens the file in the assistant editor, right next to BNRDetailViewController.xib. (You can toggle the assistant editor by clicking the middle button from the Editor control at the top of the workspace; the shortcut to display the assistant editor is Command-Option-Return; to return to the standard editor, use Command-Return.)

You will also need the object library available so that you can drag the subviews onto the view. Show the utility area by clicking the right button in the View control at the top of the workspace (or Command-Option-0).

Your window is now sufficiently cluttered. Let’s make some temporary space. Hide the navigator area by clicking the left button in the View control at the top of the workspace (the shortcut for this is Command-0). Then, change the dock in Interface Builder to show the icon view by clicking the toggle button in the lower left corner of the editor. Your workspace should now look like Figure 10.8.

Figure 10.8 Laying out the workspace

Laying out the workspace

Now, drag four UILabel objects and three UITextField objects onto the view in the canvas area and configure them to look like Figure 10.9.

Figure 10.9 Configured BNRDetailViewController XIB

Configured BNRDetailViewController XIB

It is important that the subviews you just added are not positioned near the very top of the XIB. This is because the view of a UIViewController extends beneath the UINavigationBar (this is also true for the UITabBar). To make configuring interfaces easier, the root level view in a XIB file hassimulated metrics that will show you what the interface will look like with a navigation bar at the top. You can also preview a tab bar along the bottom and a number of other situations that your interface might find itself in.

To see the simulated metrics, select the root level view, and open its attributes inspector. At the top, you will see Simulated Metrics. For Top Bar, choose Translucent Navigation Bar (Figure 10.10).

Figure 10.10 Simulated metrics

Simulated metrics

The three instances of UITextField and bottom instance of UILabel will be outlets in BNRDetailViewController. Here comes the exciting part. Control-drag from the UITextField next to the Name label to the class extension in BNRDetailViewController.m, as shown in Figure 10.11.

Figure 10.11 Dragging from XIB to source file

Dragging from XIB to source file

Let go while still inside the class extension, and a pop-up window will appear. Enter nameField into the Name field, select Weak from the Storage pop-up menu, and click Connect (Figure 10.12).

Figure 10.12 Auto-generating an outlet and making a connection

Auto-generating an outlet and making a connection

This will create an IBOutlet property of type UITextField named nameField in BNRDetailViewController. You chose Weak storage for this property because the object it will point to is not a top-level object in the XIB file.

In addition, this UITextField is now connected to the nameField outlet of the File's Owner in the XIB file. You can verify this by Control-clicking on the File's Owner to see the connections. Also notice that hovering your mouse above the nameField connection in the panel that appears will reveal theUITextField that you connected. Two birds, one stone.

Create the other three outlets the same way and name them as shown in Figure 10.13.

Figure 10.13 Connection diagram

Connection diagram

After making the connections, BNRDetailViewController.m should look like this:

#import "BNRDetailViewController.h"

@interface BNRDetailViewController ()

@property (weak, nonatomic) IBOutlet UITextField *nameField;

@property (weak, nonatomic) IBOutlet UITextField *serialNumberField;

@property (weak, nonatomic) IBOutlet UITextField *valueField;

@property (weak, nonatomic) IBOutlet UILabel *dateLabel;

@end

@implementation BNRDetailViewController

@end

If your file looks different, then your outlets are not connected right.

Fix any disparities between your file and the code shown above in three steps: First, go through the Control-drag process and make connections again until you have the four lines shown above in your BNRDetailViewController.m. Second, remove any wrong code (like non-property method declarations or instance variables) that got created. Finally, check for any bad connections in the XIB file. In BNRDetailViewController.xib, Control-click on the File's Owner. If there are yellow warning signs next to any connection, click the x icon next to those connections to disconnect them.

It is important to ensure there are no bad connections in a XIB file. A bad connection typically happens when you change the name of an instance variable but do not update the connection in the XIB file. Or, you completely remove an instance variable but do not remove it from the XIB file. Either way, a bad connection will cause your application to crash when the XIB file is loaded.

Now let’s make more connections. For each instance of UITextField in the XIB file, connect the delegate property to the File's Owner. (Remember, Control-drag from the UITextField to the File's Owner and select delegate from the list.)

Now that this project has a good number of source files, you will be switching between them fairly regularly. One way to speed up switching between commonly accessed files is to use Xcode tabs. If you double-click on a file in the project navigator, the file will open in a new tab. You can also open up a blank tab with the shortcut Command-T. The keyboard shortcuts for cycling through tabs are Command-Shift-} and Command-Shift-{. (You can see the other shortcuts for project organization by selecting the General tab from Xcode’s preferences.)

Navigating with UINavigationController

Now you have a navigation controller and two view controller subclasses. Time to put the pieces together. The user should be able to tap a row in BNRItemsViewController’s table view and have the BNRDetailViewController’s view slide onto the screen and display the properties of the selectedBNRItem instance.

Pushing view controllers

Of course, you need to create an instance of BNRDetailViewController. Where should this object be created? Think back to previous exercises where you instantiated all of your controllers in the method application:didFinishLaunchingWithOptions:. For example, in Chapter 6, you created both view controllers and immediately added them to the tab bar controller’s viewControllers array.

However, when using a UINavigationController, you cannot simply store all of the possible view controllers in its stack. The viewControllers array of a navigation controller is dynamic – you start with a root view controller and push view controllers depending on user input. Therefore, some object other than the navigation controller needs to create the instance of BNRDetailViewController and be responsible for adding it to the stack.

This object must meet two requirements: it needs to know when to push a BNRDetailViewController onto the stack, and it needs a pointer to the navigation controller to send the navigation controller messages, namely, pushViewController:animated:.

BNRItemsViewController fills both requirements. First, it knows when a row is tapped in a table view because, as the table view’s delegate, it receives the message tableView:didSelectRowAtIndexPath: when this event occurs. Second, any view controller in a navigation controller’s stack can get a pointer to that navigation controller by sending itself the message navigationController. As the root view controller, BNRItemsViewController is always in the navigation controller’s stack and thus can always access it.

Therefore, BNRItemsViewController will be responsible for creating the instance of BNRDetailViewController and adding it to the stack. At the top of BNRItemsViewController.m, import the header file for BNRDetailViewController.

#import "BNRDetailViewController.h"

@interface BNRItemsViewController : UITableViewController

When a row is tapped in a table view, its delegate is sent tableView:didSelectRowAtIndexPath:, which contains the index path of the selected row. In BNRItemsViewController.m, implement this method to create a BNRDetailViewController and then push it on top of the navigation controller’s stack.

@implementation BNRItemsViewController

- (void)tableView:(UITableView *)tableView

didSelectRowAtIndexPath:(NSIndexPath *)indexPath

{

BNRDetailViewController *detailViewController =

[[BNRDetailViewController alloc] init];

// Push it onto the top of the navigation controller's stack

[self.navigationController pushViewController:detailViewController

animated:YES];

}

Build and run the application. Create a new item and select that row from the UITableView. Not only are you taken to BNRDetailViewController’s view, but you also get a free animation and a back button in the UINavigationBar. Tap this button to get back to BNRItemsViewController.

Since the UINavigationController’s stack is an array, it will take ownership of any view controller added to it. Thus, the BNRDetailViewController is owned only by the UINavigationController after tableView:didSelectRowAtIndexPath: finishes. When the stack is popped, the BNRDetailViewControlleris destroyed. The next time a row is tapped, a new instance of BNRDetailViewController is created.

Having a view controller push the next view controller is a common pattern. The root view controller typically creates the next view controller, and the next view controller creates the one after that, and so on. Some applications may have view controllers that can push different view controllers depending on user input. For example, the Photos app pushes a video view controller or an image view controller onto the navigation stack depending on what type of media was selected.

(The iPad-only class UISplitViewController calls for a different pattern. The iPad’s larger screen size allows two view controllers in a drill-down interface to appear on screen simultaneously instead of being pushed onto the same stack. You will learn more about UISplitViewController inChapter 22.)

Passing data between view controllers

Of course, the text fields on the screen are currently empty. To fill these fields, you need a way to pass the selected BNRItem from the BNRItemsViewController to the BNRDetailViewController.

To pull this off, you will give BNRDetailViewController a property to hold a BNRItem. When a row is tapped, BNRItemsViewController will give the corresponding BNRItem to the instance of BNRDetailViewController that is being pushed onto the stack. The BNRDetailViewController will populate its text fields with the properties of that BNRItem. Editing the text in the text fields on BNRDetailViewController’s view will change the properties of that BNRItem.

In BNRDetailViewController.h, add this property. Also, at the top of this file, forward declare BNRItem.

#import <UIKit/UIKit.h>

@class BNRItem;

@interface BNRDetailViewController : UIViewController

@property (nonatomic, strong) BNRItem *item;

@end

In BNRDetailViewController.m, import BNRItem’s header file.

#import "BNRItem.h"

When the BNRDetailViewController’s view appears on the screen, it needs to set up its subviews to show the properties of the item. In BNRDetailViewController.m, override viewWillAppear: to transfer the item’s properties to the various instances of UITextField.

- (void)viewWillAppear:(BOOL)animated

{

[super viewWillAppear:animated];

BNRItem *item = self.item;

self.nameField.text = item.itemName;

self.serialNumberField.text = item.serialNumber;

self.valueField.text = [NSString stringWithFormat:@"%d", item.valueInDollars];

// You need an NSDateFormatter that will turn a date into a simple date string

static NSDateFormatter *dateFormatter = nil;

if (!dateFormatter) {

dateFormatter = [[NSDateFormatter alloc] init];

dateFormatter.dateStyle = NSDateFormatterMediumStyle;

dateFormatter.timeStyle = NSDateFormatterNoStyle;

}

// Use filtered NSDate object to set dateLabel contents

self.dateLabel.text = [dateFormatter stringFromDate:item.dateCreated];

}

In BNRItemsViewController.m, add the following code to tableView:didSelectRowAtIndexPath: so that BNRDetailViewController has its item before viewWillAppear: gets called.

- (void)tableView:(UITableView *)tableView

didSelectRowAtIndexPath:(NSIndexPath *)indexPath

{

BNRDetailViewController *detailViewController =

[[BNRDetailViewController alloc] init];

NSArray *items = [[BNRItemStore sharedStore] allItems];

BNRItem *selectedItem = items[indexPath.row];

// Give detail view controller a pointer to the item object in row

detailViewController.item = selectedItem;

[self.navigationController pushViewController:detailViewController

animated:YES];

}

Many programmers new to iOS struggle with how data is passed between view controllers. Having all of the data in the root view controller and passing subsets of that data to the next UIViewController (like you just did) is a clean and efficient way of performing this task.

Build and run your application. Create a new item and select that row in the UITableView. The view that appears will contain the information for the selected BNRItem. While you can edit this data, the UITableView will not reflect those changes when you return to it. To fix this problem, you need to implement code to update the properties of the BNRItem being edited. In the next section, you will see when to do this.

Appearing and disappearing views

Whenever a UINavigationController is about to swap views, it sends out two messages: viewWillDisappear: and viewWillAppear:. The UIViewController that is about to be popped off the stack is sent the message viewWillDisappear:. The UIViewController that will then be on top of the stack is sentviewWillAppear:.

When a BNRDetailViewController is popped off the stack, you will set the properties of its item to the contents of the text fields. When implementing these methods for views appearing and disappearing, it is important to call the superclass’s implementation – it might have some work to do and needs to be given the chance to do it. In BNRDetailViewController.m, implement viewWillDisappear:.

- (void)viewWillDisappear:(BOOL)animated

{

[super viewWillDisappear:animated];

// Clear first responder

[self.view endEditing:YES];

// "Save" changes to item

BNRItem *item = self.item;

item.itemName = self.nameField.text;

item.serialNumber = self.serialNumberField.text;

item.valueInDollars = [self.valueField.text intValue];

}

Notice the use of endEditing:. When the message endEditing: is sent to a view, if it or any of its subviews is currently the first responder, it will resign its first responder status, and the keyboard will be dismissed. (The argument passed determines whether the first responder should be forced into retirement. Some first responders might refuse to resign, and passing YES ignores that refusal.)

Now the values of the BNRItem will be updated when the user taps the Back button on the UINavigationBar. When BNRItemsViewController appears back on the screen, it is sent the message viewWillAppear:. Take this opportunity to reload the UITableView so the user can immediately see the changes. In BNRItemsViewController.m, override viewWillAppear:.

- (void)viewWillAppear:(BOOL)animated

{

[super viewWillAppear:animated];

[self.tableView reloadData];

}

Build and run your application now. Now you can move back and forth between the view controllers that you created and change the data with ease.

UINavigationBar

The UINavigationBar is not very interesting right now. A UINavigationBar should display a descriptive title for the UIViewController that is currently on top of the UINavigationController’s stack.

Every UIViewController has a navigationItem property of type UINavigationItem. However, unlike UINavigationBar, UINavigationItem is not a subclass of UIView, so it cannot appear on the screen. Instead, the navigation item supplies the navigation bar with the content it needs to draw. When aUIViewController comes to the top of a UINavigationController’s stack, the UINavigationBar uses the UIViewController’s navigationItem to configure itself, as shown in Figure 10.14.

Figure 10.14 UINavigationItem

UINavigationItem

By default, a UINavigationItem is empty. At the most basic level, a UINavigationItem has a simple title string. When a UIViewController is moved to the top of the navigation stack and its navigationItem has a valid string for its title property, the navigation bar will display that string (Figure 10.15).

Figure 10.15 UINavigationItem with title

UINavigationItem with title

In BNRItemsViewController.m, modify init to set the navigationItem’s title to read Homepwner.

- (instancetype)init

{

self = [super initWithStyle:UITableViewStylePlain];

if (self) {

UINavigationItem *navItem = self.navigationItem;

navItem.title = @"Homepwner";

}

return self;

}

Build and run the application. Notice the string Homepwner on the navigation bar. Create and tap on a row and notice that the navigation bar no longer has a title. You need to give the BNRDetailViewController a title, too. It would be nice to have the BNRDetailViewController’s navigation item title be the name of the BNRItem it is displaying. Obviously, you cannot do this in init because you do not yet know what its item will be.

Instead, the BNRDetailViewController will set its title when it sets its item property. In BNRDetailViewController.m, implement setItem:, replacing the synthesized setter method for item.

- (void)setItem:(BNRItem *)item

{

_item = item;

self.navigationItem.title = _item.itemName;

}

Build and run the application. Create and tap a row, and you will see that the title of the navigation bar is the name of the BNRItem you selected.

A navigation item can hold more than just a title string, as shown in Figure 10.16. There are three customizable areas for each UINavigationItem: a leftBarButtonItem, a rightBarButtonItem, and a titleView. The left and right bar button items are pointers to instances of UIBarButtonItem, which contains the information for a button that can only be displayed on a UINavigationBar or a UIToolbar.

Figure 10.16 UINavigationItem with everything

UINavigationItem with everything

Like UINavigationItem, UIBarButtonItem is not a subclass of UIView. Instead, UINavigationItem encapsulates information that UINavigationBar uses to configure itself. Similarly, UIBarButtonItem is not a view, but holds the information about how a single button on the UINavigationBar should be displayed. (A UIToolbar also uses instances of UIBarButtonItem to configure itself.)

The third customizable area of a UINavigationItem is its titleView. You can either use a basic string as the title or have a subclass of UIView sit in the center of the navigation item. You cannot have both. If it suits the context of a specific view controller to have a custom view (like a button, a slider, an image, or even a map), you would set the titleView of the navigation item to that custom view. Figure 10.16 shows an example of a UINavigationItem with a custom view as its titleView. Typically, however, a title string is sufficient, and that is what you will do in this chapter.

Let’s add a UIBarButtonItem to the UINavigationBar. You want this button to sit on the right side of the navigation bar when the BNRItemsViewController is on top of the stack. When tapped, it should add a new BNRItem to the list.

A bar button item has a target-action pair that works like UIControl’s target-action mechanism: when tapped, it sends the action message to the target. When you set a target-action pair in a XIB file, you Control-drag from a button to its target and then select a method from the list of IBActions. To programmatically set up a target-action pair, you pass the target and the action to the button.

In BNRItemsViewController.m, create a UIBarButtonItem instance and give it its target and action.

- (instancetype)init

{

self = [super initWithStyle:UITableViewStylePlain];

if (self) {

UINavigationItem *navItem = self.navigationItem;

navItem.title = @"Homepwner";

// Create a new bar button item that will send

// addNewItem: to BNRItemsViewController

UIBarButtonItem *bbi = [[UIBarButtonItem alloc]

initWithBarButtonSystemItem:UIBarButtonSystemItemAdd

target:self

action:@selector(addNewItem:)];

// Set this bar button item as the right item in the navigationItem

navItem.rightBarButtonItem = bbi;

}

return self;

}

The action is passed as a value of type SEL. Recall that the SEL data type is a pointer to a selector and that a selector is the entire message name including any colons. Note that @selector() does not care about the return type, argument types, or names of arguments.

Build and run the application. Tap the + button, and a new row will appear in the table. (Note that this is not the only way to set up a bar button item; check the documentation for other initialization messages that you can use to create an instance of UIBarButtonItem.)

Now let’s add another UIBarButtonItem to replace the Edit button in the table view header. In BNRItemsViewController.m, edit the init method.

- (instancetype)init

{

self = [super initWithStyle:UITableViewStylePlain];

if (self) {

UINavigationItem *navItem = self.navigationItem;

navItem.title = @"Homepwner";

// Create a new bar button item that will send

// addNewItem: to BNRItemsViewController

UIBarButtonItem *bbi = [[UIBarButtonItem alloc]

initWithBarButtonSystemItem:UIBarButtonSystemItemAdd

target:self

action:@selector(addNewItem:)];

// Set this bar button item as the right item in the navigationItem

navItem.rightBarButtonItem = bbi;

navItem.leftBarButtonItem = self.editButtonItem;

}

return self;

}

Surprisingly, that is all the code you need to get an edit button on the navigation bar. Build and run, tap the Edit button, and watch the UITableView enter editing mode! Where does editButtonItem come from? UIViewController has an editButtonItem property, and when sent editButtonItem, the view controller creates a UIBarButtonItem with the title Edit. Even better, this button comes with a target-action pair: it sends the message setEditing:animated: to its UIViewController when tapped.

Now that Homepwner has a fully functional navigation bar, you can get rid of the header view and the associated code. In BNRItemsViewController.m, delete the following methods.

- (UIView *)headerView

{

if (!_headerView) {

[[NSBundle mainBundle] loadNibNamed:@"HeaderView" owner:self options:nil];

}

return _headerView;

}

- (IBAction)toggleEditingMode:(id)sender

{

if (self.isEditing) {

[sender setTitle:@"Edit" forState:UIControlStateNormal];

[self setEditing:NO animated:YES];

} else {

[sender setTitle:@"Done" forState:UIControlStateNormal];

[self setEditing:YES animated:YES];

}

}

You can also delete the declaration of the headerView property.

Finally, you can also remove the file HeaderView.xib from the project navigator.

Build and run again. The old Edit and New buttons are gone, leaving you with a lovely UINavigationBar (Figure 10.17).

Figure 10.17 Homepwner with navigation bar

Homepwner with navigation bar

Bronze Challenge: Displaying a Number Pad

The keyboard for the UITextField that displays a BNRItem’s valueInDollars is a QWERTY keyboard. It would be better if it was a number pad. Change the Keyboard Type of that UITextField to the Number Pad. (Hint: you can do this in the XIB file using the attributes inspector.)

Silver Challenge: Dismissing a Number Pad

After completing the bronze challenge, you may notice that there is no return key on the number pad. Devise a way for the user to dismiss the number pad from the screen.

Gold Challenge: Pushing More View Controllers

Right now, instances of BNRItem cannot have their dateCreated property changed. Change BNRItem so that they can, and then add a button underneath the dateLabel in BNRDetailViewController with the title Change Date. When this button is tapped, push another view controller instance onto the navigation stack. This view controller should have a UIDatePicker instance that modifies the dateCreated property of the selected BNRItem.