To present a view controller, you need a view controller to present. From the object library, drag in a plain-vanilla view controller object, positioning it to the right of the first view controller.
Create a modal transition by right/control-clicking on the dark gray info button and dragging it to the view controller you just added, as shown in Figure 12-8. When you release the mouse, choose modal from the list of styles.
Figure 12-8. Creating a modal segue
What just happened is this. Interface Builder has connected the button to a storyboard segue,
which it created for this purpose. The segue is configured to perform a modal transition. If you were doing this programmatically, you would connect the button to your own action method which would send the first view controller a -presentViewController:animated:completed: message.
Using a storyboard segue saves you from having to create an action and write that code.
Select the storyboard segue by clicking on the segue line, or the circle in the middle of the segue line, as shown in Figure 12-9. Using the attributes inspector, change the presentation to Form Sheet and the transition to FlipHorizontal.
Figure 12-9. Editing a segue
Now put something in the new view controller. Drag in an image view and a text view object from the object library. Set the text of the text view (using option+return to insert carriage returns) to:
Lewis Carroll
a.k.a. Charles Lutwidge Dodgson
27 January 1832 – 14 January 1898
Set the alignment of the field to centered (middle button) and uncheck the Editable behavior. Now select the image view and change its image property to info-charles.
Select the text field object and choose Editor ➤ Size to Fit Contents. Repeat with the image object. (If you’re working on the iPhone version, select the image view and set its size to 164x244.) Position both towards the bottom of the view, as shown in Figure 12-10.
Figure 12-10. Creating the author info view
Set the project’s scheme to use an iPad Simulator and run the project. The first view controller appears in the first tab of your app. Tapping the gray info button triggers a transition to the view controller you just created,making it appear in a “form sheet” floating above the screen. Isn’t that cool?
It would be even cooler if the app wasn’t stuck now. When presenting a view controller modally, you’re responsible for providing the interface that will return the user to the previous view controller. Add that now.
Dismissing a View Controller
The controller that presents a view controller is normally responsible for dismissing it. You’ve already done this in your DrumDub and MyStuff apps. You presented a modal picker view controller that allowed the user to pick animage or song. When it was done, it sent your view controller a delegate message with the choice. That method retrieved the image/song and then sent itself a -dismissView ControllerAnimated:completion: message, causing thepicker interface to retract.
If your modal view controller did something similar (let the user pick a planet to invade, or a pattern for their bowling ball), you’d create a delegate protocol—I explain how in Chapter 20—and send
a completion message when your modal view controller was done. The presenting view controller would then dismiss it.
That, however, sounds like a lot of work and I’m quite fond of not doing too much work. In this situation, there’s no information that needs to be sent back to the presenting controller. You just want the modal view controller to goaway. For that, there’s a simple solution.
In your project navigator, create a new Objective-C source file (File ➤ New File...).
Base it on the Objective C class template. Name it WLAuthorInfoViewController. Make it a subclass of UIViewController. Make sure the With XIB for user interface option is not checked. Create the new file.
Select the WLAuthorInfoViewController.h interface file. In the @interface section, add an action method declaration:
- (IBAction)done:(id)sender;
In the WLAuthorInfoViewController.m implementation file, add the method to the @implementation
section:
- (IBAction)done:(id)sender
{
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
Return to the Main_iPad.storyboard file. Select the view controller in the view controller scene you just created (by clicking on the view controller object in the outline or the dock at the bottom of the scene) and use theidentity inspector to change its class from UIViewController to
WLAuthorInfoViewController. Add a button to the interface and change its title to “Done”. (For the
iPhone interface, you may want to rearrange the views a little.) Right/control-drag from the button to the view controller’s placeholder object, as shown in Figure 12-11. Connect the button to the -done: action. When using storyboards, the key view controller objects for each scene are conveniently located below that scene, as well as in the outline.
Figure 12-11. Connecting the “Done” button
Run the project again. Tap the info button and the modal view controller appears. Tap the “done” button, and it disappears again. How does it work? It’s simple.
When one view controller presents another view controller, a relationship is formed. The initial view controller’s presentedViewController property is set to the view controller it just presented. The presented view controller’spresentingViewController property is, reflexively, set to the view
controller that presented it. The -done: method simply gets the view controller that presented it from
its presentingViewController property, and sends that object a -dismissViewControllerAnimated: completed: message on its behalf.
The “form sheet” presentation style is only applicable to the iPad. The iPhone version of the segue doesn’t even have a presentation property. Since this is an app about a book, and you want both versions (iPhone and iPad) towork similarly, choose a modal presentation style that works on both devices and is a little more thematic.
Return to the Main_iPad.storyboard file and select the storyboard segue between the info button and the author info view controller. Use the attributes inspector and change the presentation to Default and the transition to PartialCurl. If you’re in the iPhone interface, just set the transition to Partial Curl.
Run the app again. Now tap the info button and see what happens, shown in Figure 12-12.
That’s pretty slick! This effect works for both the iPad and the iPhone, as shown on the right in Figure 12-12.
Figure 12-12. Partial Curl transition for iPad and iPhone
Congratulations! The first third of your Wonderland app is finished. You’ve created a custom view controller that presents a second view controller modally, and wrote the necessary code to dismiss that view controller when it’sdone. There are lots of different transition styles to choose from, but the basic formula for modally presenting and dismissing a view controller remains the same. Now you’re ready to move on to the second tab.
Creating a Navigable Table View
The second tab of your Wonderland app presents a list of characters in a table view. Tapping a row navigates to a screen with some character details. Does this sound familiar? It should. You already built this app in Chapter 5.Well, you get to build it again. But this time, the focus is going to be on navigation.
You know from Chapter 5 that you’re going to need:
n A navigation view controller
n A custom subclass of UITableViewController (for the table view)
n A data model
n A table view delegate object
n A data source object
n A table view cell object
n A custom subclass of UIViewController (for the detail view)
n View objects to display the detail view
Start with the navigation view controller. A navigation view controller is a container view controller. The view it initially displays is its root view controller. This view is its home base; the view that all navigation starts from, andeventually returns back to. To have the second tab of the Wonderland app present a navigable table view, you need to install a navigation controller as the second view controller in the tab bar, and then install a table view controller as the root view controller of the navigation controller. This is easier than it sounds.
Start by clearing some room.
Select the Main_iPhone.storyboard (or _iPad) file. A WLSecondViewController already occupies the second tab. You don’t need it. Select the second view controller scene in the Interface Builder canvas and delete it. Then select the WLSecondViewController.h and WLSecondViewController.m files and delete them too.
From the object library, drag in a navigation controller and place it underneath the first view controller, as shown in Figure 12-13. A new navigation controller comes pre-installed with a table view controller, which is exactly whatyou want. (See, I told you this wouldn’t be too hard.) You’ll also need a detail view, so drop in another view controller object next to the table view.
Figure 12-13. Adding a navigation controller, table view controller, and view controller
To add the navigation controller to the tab bar, right/control-drag from the tab bar controller to the navigation controller, as shown in Figure 12-14. When the pop-up appears, find the Relationship Segue category and choose view controllers. This special connection adds the view controller to the collection of controllers that the container view controller manages. A second tab appears in the tab view, and a companion tab bar item object is added to thenavigation controller’s scene.
Figure 12-14. Making the navigation controller the second tab
Expand the Navigation Controller group in the navigation controller’s outline and select the tab bar item. Use the attributes inspector to set the title to “Characters” and the image to tab-chars.
You’ve now added a navigable table view to your tab bar (container view) controller. It’s the second tab. It has a title and icon. Tapping it will present the table (content) view controller inside the navigation (container view)controller. It sounds complicated, but the storyboard makes the organization easy to follow.
Breathing Data Into Your Table View
You can run your app right now, tap on the Characters tab, and marvel at the raging emptiness of your table view. You know, from Chapter 5, that without a data source and some data your table view has nothing to display.Let’s tackle that now.
You’re going to need a custom subclass of UITableViewController, so create one. You also know that you’re going to need a custom subclass of UIViewController for your detail view. While you’re here, you might as well createthat too:
1. In the Wonderland group of the project, add a new file
a. Use the iOS ➤ Cocoa Touch ➤ Objective‑C class template
b. Name the class WLCharacterTableViewController
c. Make it a subclass of UITableViewController
d. Do not create an XIB file for the new controller
2. Add a second new file:
a. Use the Objective-C class template
b. Name the class WLCharacterDetailViewController
c. Make it a subclass of UIViewController
d. Do not create an XIB file for the new controller
Reviewing the list of things you need to do to get the table view working, you now have a navigation controller and custom subclasses of the table and view controllers. But the objects in the interface aren’t your customsubclasses yet.
Select the Main_iPhone.storyboard (or_iPad) file, select the table view controller, and use the identity inspector to change its class to WLCharacterTableViewController.
Do the same for the detail view controller, making its class WLCharacterDetailViewController.
Creating the Detail View
Since you’re already in the character detail view controller, go ahead and create its interface. Use the object library to add a label, an image view, and a text view to the character detail view controller, as follows:
1. Label
a. Make its width 420 (iPad) or 280 (iPhone)
b. Set alignment to centered (the middle button)
c. Pin the height (Editor ➤ Pin ➤ Height)
2. Image View
a. Make its size 420x420 (iPad) or 280x280 (iPhone)
b. Set mode to Aspect Fit
c. Pin the height (Editor ➤ Pin ➤ Height)
3. Text View
a. Make its size 420x100 (iPad) or 280x100 (iPhone)
4. Constraints (Universal)
a. Position the three views so they are centered in the view and stacked vertically, approximately like those shown in Figure 12-15.
b. Select all three view objects (label, image view, and text view) using the shift key or by dragging out a selection rectangle
c. Choose Editor ➤ Pin ➤ Width
d. Select all three view objects
e. Choose Editor ➤ Align ➤ Horizontal Center in Container (not Horizontal Center!)
Figure 12-15. Rough position of detail views
5. Constraints (iPhone)
a. Select just the label and the text view
b. Choose Editor ➤ Pin ➤ Height
c. Control/right-drag from the label to the image view and choose Vertical Spacing
d. Control/right-drag from the image view to the text view and choose Vertical Spacing
e. Control/right-drag from the label to the Top Layout Guide, as shown in Figure 12-16, and choose Vertical Spacing
f. Control/right-drag from the text view to the Bottom Layout Guide and choose
Vertical Spacing
g. Select the vertical constraint above the label and use the attribute inspector to check the Standard option
h. Select the vertical constraint below the text view and use the attribute inspector to check the Standard option.
Figure 12-16. Adding a constraint to the top layout guide
6. Constraints (iPad)
a. Select all three views
b. Choose Editor ➤ Pin ➤ Height
c. Select the image view
d. Choose Editor ➤ Align ➤ Vertically Center in Container
e. Control/right-drag from the top label to the image view and add a Vertical Spacing constraint
f. Control/right-drag from the bottom label to the image view and add a Vertical Spacing constraint
On the iPhone, the label will be at the top of the container, the text view at the bottom, and the
image view will resize to fill the space between. On an iPad, the three views will be grouped together, centered in the root view.
This seems like a lot of constraints, but it precisely describes how the views should adapt when their container view changes size. There are two things that will affect that size: the dimensions of different devices and thenavigation bar and tab bar you’re about to introduce (in the next section).
You’ll need outlets for these view objects. Switch to the assistant editor; the WLCharacterDetailViewController.h interface file will appear on the right.
(If it doesn’t choose, Automatic ➤ WLCharacterDetailViewController.hfrom the navigation ribbon.)
Add the following outlet declarations to the @interface section:
@property (weak,nonatomic) IBOutlet UILabel *nameLabel;
@property (weak,nonatomic) IBOutlet UIImageView *imageView;
@property (weak,nonatomic) IBOutlet UITextView *descriptionView;
Use the outlet connectors, which now appear next to the property declarations, to connect them to their respective objects in the interface, as shown in Figure 12-17.
Figure 12-17. Connecting character detail view outlets
Adding the Data Model
What’s left? You still have to create a data model and provide the table view with a data source and table view cell object. Start with the data model.
I have a surprise for you. I created a data model for you. Isn’t that nice of me? The character detail info is stored in an object array (NSArray).
Each element of the array contains a dictionary (NSDictionary). Each dictionary hasthe name of the character,the filename of its image, and a brief description. All of this information is stored in the Characters.nsarray file, one of the resource files you added earlier in this chapter.
Add the data model to your table view controller by creating a property to store the array in your
WLCharacterTableViewController.h interface file:
@property (strong,nonatomic) NSArray *tableData;
Switch to WLCharacterTableViewController.m and add this code to -viewDidLoad:
NSURL *dataURL = [[NSBundle mainBundle] URLForResource:@"Characters" withExtension:@"nsarray"];
self.tableData = [NSArray arrayWithContentsOfURL:dataURL];
This code locates the Characters.nsdata file in your apps resources, reads it in as an NSArray
object, and stores it in your tableData property. You now have a data model!
Implementing Your Data Source
Now you need to feed the table view this information, via its data source object. The table view controller file template includes dummy implementations for all the key data source and delegate methods, they just need a littleadjustment.
While still in the WLCharacterTableViewController.m file, find the -numberOfSectionsInTableView:
method and delete it. You don’t need it. Your table has one section, and that’s the default.
Find the -tableView:numberOfRowsInSection: method and replace its code with this (shown in bold):
- (NSInteger)tableView:(UITableView*)table numberOfRowsInSection:(NSInteger)sec
{
return self.tableData.count;
}
This method provides the table view with the number of rows in the list, which is the number of objects in the array.
Defining a Table View Cell Object
The last piece of the puzzle is to supply the table view a table cell object for each row—the table view’s rubber stamp. Find the -tableView:cellForRowAtIndexPath: method and edit it so it looks like this (new code in bold):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellId = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellId forIndexPath:
indexPath];
NSDictionary *characterInfo = _tableData[indexPath.row]; cell.textLabel.text = characterInfo[kNameKey];
return cell;
}
This code should look very familiar—unless you skipped Chapter 5. The cell’s appearance is defined by the cell prototype in the storyboard. Switch back to the Main_iPhone.storyboard
(or _iPad) file and locate the table viewcontroller.
At the top of the table view you’ll see an area labeled Prototype Cells, as shown in Figure 12-18. Select the first, blank, cell and use the attributes inspector to change the style to Basic, set its identifier to Cell, and change itsaccessory to Disclosure Indicator (see Figure 12-18).
Figure 12-18. Defining a table cell
Now when your -tableView:cellForRowAtIndexPath: method asks for the “Cell” table cell object, it’s already there. Your code just configures the text of the cell and it’s done.
The code in -tableView:cellForRowAtIndexPath: is still showing an error because you haven’t defined the keys used to retrieve the values from the dictionaries. Do that in WLCharacterTableView Controller.h, by adding this codeimmediately after the #import statements:
#define kImageKey @"image"
#define kNameKey @"name"
#define kDescriptionKey @"description"
Your table view is now finished. Run the app in the simulator, tap the second tab, and this time your table is populated with the names from the data model, as shown in Figure 12-19.
Figure 12-19. Working table view
Pushing the Detail View Controller
Tapping a row in the table, however, doesn’t do much (on the right in Figure 12-19). That’s because you haven’t defined the action that presents the detail view. Also, the table’s title is Root View Controller, which is a bit“on the nose.” Fix the second one by selecting the Main_iPhone.storyboard (or _iPad) file, locating the character table view controller, double-clicking the title in the navigation bar, and changing it to “Characters,” as shown in Figure 12-20.
Figure 12-20. Creating a segue for the table cell
Right/control-drag from the prototype cell object over to the character detail view controller (also shown in Figure 12-20). When you release the mouse, choose the push option from the Selection Segue group. This configuresall rows that use this cell object to “push” the character details view controller onto the navigation controller’s stack, presenting it as the active view controller.
Just as you did in Chapter 5, you need some code to prepare the detail view based on the row
the user tapped. Return to the WLCharacterTableViewController.m file. Add this method to your @ implementation:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
WLCharacterDetailViewController *detailsController = segue.destinationViewController; detailsController.characterInfo = _tableData[self.tableView.indexPathForSelectedRow.row];
}
The segue object contains information about the view controllers involved (both coming and going).
Use it to get the details view controller object the storyboard just created and loaded. You then use the tableView object to get the row number of the currently selected row—the one the user is tapping—and use that to get thecharacter details from the data model and configure the new view controller (by setting characterInfo).
There are still a few loose ends. The list view controller doesn’t know anything about WLCharacterDetailViewController, and that controller doesn’t have a characterInfo property yet. These are pretty trivial tasks. Start by addingthis #import statement immediately after the other
#import statement:
#import "WLCharacterDetailViewController.h"
Switch to your WLCharacterDetailViewController.h interface file and add a property declaration to hold the details of one character:
@property (strong,nonatomic) NSDictionary *characterInfo;
Select the WLCharacterDetailViewController.m implementation file. After the existing #import statement, add a new one to pick up the dictionary key constants (kNameKey and so on) you wrote earlier:
#import "WLCharacterTableViewController.h"
Finally, add this method to the @implementation:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated]; self.nameLabel.text = _characterInfo[kNameKey];
self.imageView.image = [UIImage imageNamed:_characterInfo[kImageKey]]; self.descriptionView.text = _characterInfo[kDescriptionKey];
}
When the view controller is about to appear on the screen, this code will populate the view objects with details in characterInfo. Run the app in the simulator and try it out, as shown in Figure 12-21.
Figure 12-21. Finished character table
Your app is now two-thirds finished. In this section, you created a navigable table view by nesting a table (content) view controller inside a navigation (container view) controller. You used a storyboard to configure the table’s cellobject and created its segue to the detail view controller.
By now you should be getting pretty comfortable with content and container view controllers, connecting them together, and creating segues to define your app’s navigation. The final section of this chapter is going to showyou how to use a page view controller.














No comments:
Post a Comment