Chapter 4. Writing an app with multiple views

published book

This chapter covers

  • Designing classes to match your sketches
  • Defining your models, views, and controllers
  • Changing views based on user interaction

In the last chapter, you learned the syntax for messages and properties and how you can make them into actions and outlets so that Interface Builder can connect views to them. In this chapter, you’re going to take the sketches for a real application and see how to create all of the various pieces. By the time you’re done, you’ll have made an application to show flashcards, which you can customize with your own content.

Designing a flashcard application

For the content of your flashcards, you’ll use U.S. state capitals. Do you know Juneau from Topeka without peeking? If not, this app will help.

This app is more complex than the ones you’ve seen so far, so it’s even more important that you plan it out a little before starting. So, let’s make some sketches of what you want it to look like. The opening screen is shown at right.

Figure 4.1. Sketch of flashcard start screen

Once you choose which way you want to practice, you’ll see a sequence like this:

Figure 4.2. Sketch of flashcards

The sketches show that you need three different views: one for the opening screen, another that shows text and has three buttons for answers, and another with the final score and a restart button.

Let’s look at how this app flows:

Figure 4.3. Behavior of flashcards

Finally, it’s a good idea to think of what model classes you might need. The first one, FCCard, represents a flashcard and can handle a recordAnswer message.

Figure 4.4. FCCard model class

To hold the state of the game, you’ll use a FCGame class. It has cards, gives you access to them, and tells you when you’re done.

Figure 4.5. FCGame and FCCard model classes

To configure a game, you’ll use a class called FCAnswerKey that can generate a deck of cards, each with one right answer and two wrong ones.

Figure 4.6. FCGame, FCCard, and FCAnswerKey model classes

These three classes hold all the information that each of the views might need. For example, to show a card, the card controller will ask the card for its card text and answers and then use them on the card view.

Figure 4.7. How the controller will use model information in the view

To show the final score, the result controller will ask the game for the number of right and wrong answers.

Figure 4.8. How the result controller uses game properties in the view

Here’s how the whole class diagram looks.

Figure 4.9. All flashcard classes working together

Each different view needs a controller, and, as you saw, the controllers use the models to figure out which view to show and what to show on it.

Init

This is the first message that should be sent to an object after allocating it. There are variants that take arguments if they’re needed. You should only call one init per object.

Another thing to notice is that some classes have a special init message. Every class automatically gets an init message to set it up right after it’s been allocated. If you would like to send parameters to the init, then you need to make special ones, and you name them starting with init. For example, you’ll want to give the view controllers the Game object that you create at the beginning.

Figure 4.10. How the controllers will share an FCGame model using initWithGame

Now that you’ve got the basic structure in place, you can learn how to code it. The more you do up front, the easier it will be to concentrate on coding one piece at a time. When you start coding, you’ll find that you made mistakes in the diagram. And once your code is running, you’ll get better ideas of how your app should work. Still, a little planning is good practice until you’re more comfortable jumping right in and coding.

Creating classes to match your designs

You did a lot of work to get to the point that you can code. This is typical, because once you write code, the app becomes a little harder to change. The sketches will help you keep yourself organized while you’re coding, and they’re also a great way to explain the code to someone else. Sometimes that “someone else” is you in the future when you come back to the project after being away, so do a good job, and remember to thank yourself later.

With your design and specification reasonably fleshed out, you’re ready to begin trying to make this app. In chapter 3, you created your view first and then used the Assistant Editor to create actions and properties. In some cases it’s easy to define your controllers and models first and then create the view. This is especially true when your project is well planned. This is what you’ll do in this chapter. First you’ll go through the view controllers and add the messages the view will need. Then you’ll code the models. Finally, you’ll draw the views and finish the controllers.

To start, open Xcode and create a new project called FlashCards using the Single View Application template. This is exactly the same way you started your Hello World! project:

  1. Choose File > New > New Project.
  2. Click Single View Application, and click Next.
  3. Type in FlashCards as the product name, fill in your company identifier (using your domain or anything unique), use FC as the prefix, uncheck Storyboard, use ARC, and click Next.
  4. Pick a folder in which to save the project.

To organize your files a little better, create a Classes group and a Resources group. To create each group, choose File > New > New Project. You’re also going to create folders in the Finder that mirror your project’s group structure. For each group, open a file dialog by selecting the group and then clicking the small square under the Path dropdown in the Utilities tray, which is located on the right side of the Xcode window.

Figure 4.11. For each group, open the file dialog to create a folder.

When the file dialog opens, click the New Folder button, name the folder Classes (or Resources), click Create, and then click Choose. Move the headers and modules to Classes and the XIB files to Resources.

Open the Classes group, and click FCViewController.h. It looks like this.

Listing 4.1. FCViewController.h overview

As you saw in the last chapter, the parts of your diagram map onto the class definition like this:

Figure 4.12. How the class diagram maps to the header

The Xcode template helps structure your files, so you just need to put each part in the correct place. Unfortunately, it only knows about Objective-C source files. To add structure to the rest of the things in your life, you’re on your own.

Declaring a view-controller message for your views to send

Xcode creates your first view and controller for you when you use the view-based template. Because you have that one already, let’s start with it. It’s easy to use because it only has actions.

In the diagram, FCViewController looked as shown here.

Figure 4.13. FCViewController class diagram

The class diagram lists two messages, showStates and showCapitals, that will be called by the view, depending on what button is clicked. These are actions, so they need to return IBAction and take a sender. Here’s the code for the header:

@interface FCViewController : UIViewController

-(IBAction) showStates:(id)sender;
-(IBAction) showCapitals:(id)sender;

@end

And here’s how you define the messages in the FCViewController.m file:

-(IBAction)showStates:(id)sender {
}

-(IBAction)showCapitals:(id)sender {
}

Eventually, you’ll put the code for the messages there. But the application can now be built, so let’s make sure you’ve done everything right by pressing Cmd-B. Xcode should say Build Succeeded. If you get any errors, check all of the code carefully against the listings.

Now that you’ve written most of this controller and declared and defined its messages, you’re ready to create the rest of your views and controllers. Controllers define the flow of your application, and as you saw, if the actions and outlets are done, Interface Builder will let you attach them to your views.

Creating your other views and controllers

The design has three view/view-controller pairs. The one you made will be the startup screen, but you also need to be able to show a flashcard and the final results. Let’s do that.

In Xcode, chose File > New > File, and then choose Cocoa Touch in the list at the left and Objective-C Class in the template list. The window should look like this:

Figure 4.14. Creating a viewcontroller class

Click Next. In the next window, name the class FCCardViewController, and choose the subclass UIViewController (this makes your class a UIViewController). Make sure to create a XIB.

Figure 4.15. Choosing the base class and target
Subclass

In an is-a relationship, where class A is-a class B, A is called the base or superclass, and B is called the subclass. You can also say that B inherits from A.

Click the Next button, and in the next dialog, make sure the location is set to the Classes subfolder and group of your project.

Figure 4.16. Saving the viewcontroller class

Click the Finish button. Xcode will create three files: FCCardViewController.m, FCCardViewController.h, and FCCardViewController.xib. Drag the .xib file to your Resources group with the other files. Repeat these steps for FCResultViewController.

Your FCCardViewController class now looks like this.

Figure 4.17. FCCardViewController class diagram

The code for the messages and outlet properties looks like this in the header:

@interface FCCardViewController : UIViewController

@property (nonatomic, strong) IBOutlet UILabel* cardLabel;
@property (nonatomic, strong) IBOutlet UIButton* answer1Button;
@property (nonatomic, strong) IBOutlet UIButton* answer2Button;
@property (nonatomic, strong) IBOutlet UIButton* answer3Button;

-(void) nextCard;
-(IBAction)answerButtonTouched:(id)sender;

And here’s the module (you’ll create initWithGame when you make the FCGame class):

-(void) nextCard {
}

-(IBAction)answerButtonTouched:(id)sender {
}

Check your work by pressing Cmd-B to build.

Now you can work on FCResultViewController. It looks like this:

Figure 4.18. FCResultViewController class diagram

Add these messages after the closing curly brace and before the @end:

@interface FCResultViewController : UIViewController {
    UILabel* numRightLabel;
    UILabel* numWrongLabel;
}
@property(nonatomic, strong) IBOutlet UILabel* numRightLabel;
@property(nonatomic, strong) IBOutlet UILabel* numWrongLabel;

-(IBAction)startAgain:(id)sender;

And add this code in the module file:

-(IBAction)startAgain:(id)sender {
}

Check your work with Cmd-B. If you have any errors, look carefully at each part of the syntax. Xcode will usually put you on the line it’s having problems with, so check that one against these listings. If they match, make sure you entered the code in the correct place.

Creating the model classes

In previous chapters, the apps you built didn’t do much with user input, and they certainly didn’t need to store anything for later. Now you’re going to see what to do when your apps get complex enough to need models.

The model classes, FCGame, FCCard, and FCAnswerKey, have all the information about the app. They work together to set up the game and keep track of what card you’re on, when you’re done, and how many right and wrong answers you have.

Figure 4.19. The model classes

Let’s start with FCCard. To create it, in Xcode, right-click the Classes group, and then choose New File from the menu. In the following dialog, choose to create an Objective-C class; and in the next dialog, call it FCCard and make it a subclass of NSObject (all classes have a parent, so use this if you don’t need a different one).

Figure 4.20. Creating a class

In the next dialog, put the class in the Classes folder.

Click FCCard.h in your Project Navigator. Here you need to declare its four properties and two messages. It looks like this.

Listing 4.2. Adding properties and messages to FCCard.h

Now you need to define your class in the module. In it, you’ll create objects, store them in properties, and send messages to them.

The first thing to do is define the initWithQuestion: answer: wrongAnswer1: wrongAnswer2: message. To do that, you’ll need to send messages for the first time. The syntax for that is as follows:

Figure 4.21. Objective-C syntax to send a message

If a message returns an object, and you need to send a message to it, you can nest message sends. It’s common to do this when allocating and initializing an object. It looks like this:

Figure 4.22. Objective-C syntax to set a property to a new object

Here’s the code for the message.

Listing 4.3. FCCard.m init message

In the init, you build up an array of answers by allocating it , adding your answers to it , randomly swapping the correct answer with a wrong one , and then making sure you remember where the right answer is .

Figure 4.23. How answers are stored in a Card object

The built-in function arc4random() returns a random integer. Then you use the modulo operator, %, to find the remainder when you divide by three. This leaves you with a random number between zero and two, which means the right answer can end up in any spot with equal probability.

Modulo

An operator that returns the remainder after dividing. The result is always between zero and the right-hand operand minus one.

In recordAnswer, you need to remember if the answer matches the correct one:

-(void) recordAnswer:(int)answerNum
{
    self.isCorrect = (answerNum == self.correctAnswer);
}

This class is done, so build the project to make sure you’ve done everything right. You started with FCCard because it didn’t depend on any other class. The next class we’ll look at, FCAnswerKey, creates the cards that make up the game and only depends on the FCCard class, which is already finished.

Implementing FCAnswerKey

So far, most of the classes you’ve seen don’t know anything about states or capitals and could easily be reused in any flashcard application. FCAnswerKey is different. If you were to adapt this app to a different topic, this class is where you’d put the information that is used on the cards.

The purpose of the FCAnswerKey class is to create the deck of cards that is used to play the game. It has two messages to allocate the two kinds of decks you’ll use in the game.

Figure 4.24. FCAnswerKey and FCCard class diagram

Create the class files by choosing File > New > File. Then choose the Objective-C class template, and enter the class name FCAnswerKey. FCAnswerKey should be a subclass of NSObject. Here’s the header:

#import "FCCard.h"

@interface FCAnswerKey : NSObject

-(NSMutableArray*) allocStateCards;
-(NSMutableArray*) allocCapitalCards;

@end

The only thing to note here is that there is a convention of starting a message with alloc if its main purpose is to create an object. Here is the FCAnswerKey module.

Listing 4.4. FCAnswerKey.m

The first message allocates an array and then fills it with cards, and returns it . You make a similar one for state capitals.

Figure 4.25. Creating cards from the String data on them

Build the project to make sure everything is OK. Next you’ll write the last model class, FCGame, which uses FCAnswerKey to create cards and then keeps track of them and lets you know when the game is over.

The FCGame class

The FCGame class has a lot of methods, but each is pretty simple. Here’s how it fits in your overall structure:

Figure 4.26. FCGame, FCCard, and FCAnswerKey class diagrams

Create FCGame as a subclass of NSObject, and fill in the header.

Listing 4.5. FCGame.h interface to the FCGame class

The header is straightforward. Remember that you use strong properties so ARC will deal with memory for you.

Let’s go through the module piece by piece. First is initWithCards, which takes a list of cards and assigns it to a property. Because you’re going to provide the list items one by one, you set the current card to the beginning:

-(id)initWithCards:(NSMutableArray*)c
{
    if (self = [super init]) {
        self.cards = c;
        self.currentCard = 0;
    }
    return self;
}

The next two messages let you know if there are any more cards, and if so, a way to get the next one:

-(bool) hasMoreCards
{
    return self.currentCard < [self.cards count];
}

-(FCCard*) getNextCard
{
    FCCard* card = [self.cards objectAtIndex:self.currentCard];
    self.currentCard++;
    return card;
}

To get the final score, you call the next two messages.

Listing 4.6. FCGame.m num right/wrong

To keep it simple, you’ll just loop through the cards and count the number of correct ones . The number of wrong cards is the number of cards minus the number of correct ones .

Build the project, and fix any errors.

The last part of the app draws the views, hooks them up to the view controllers, and then has the view controllers use the models to play the game. You’ve already laid out the outlet and actions in the view controllers, so they’re ready to be connected to the views. You also have fully functional model classes, so each action in the controller can be fully implemented as well. You’ll finish it in the next section.

Connecting code to Interface Builder

You’re ready to draw your views and hook them up. In chapter 3, you Ctrl-dragged from Interface Builder into the Assistant Editor and let the Assistant Editor create your properties and actions. This time, you’re going Ctrl-drag from Interface Builder into the Assistant Editor to connect your view to code you already wrote. The process is so similar to what you’ve already done that you’ve probably got your mouse hovering over the .xib, ready to click. Go ahead, do it, it’s OK.

Let’s begin with the starting screen, which is shown here.

Figure 4.27. Sketch of the FlashCards main screen

Here’s how you build it:

  1. Select FCViewController.xib in the Resources group.
  2. Drag a Label from the Library to the View dialog (as in the Hello World! example).
  3. Double-click the label, and type State Capital Flash Cards.
  4. Drag two Round Rect Buttons from the Library to the View dialog.
  5. Double-click the top one and type Show States. Then double-click the other one and type Show Capitals.
  6. Show the Assistant Editor by clicking the button on the toolbar that looks like a tuxedo. FCCardViewController.h should appear in the Assistant Editor.
  7. Ctrl-drag from the Show States button in Interface Builder to the showStatesIBAction in FCCardViewController.h. When you’re over the action, it will become highlighted, and you’ll see a tooltip that says Connect Action.
  8. Repeat the process for the Show Capitals button, Ctrl-dragging from the button to the action. You may have noticed little filled-in circles adjacent to your actions. These represent connections between the view and the file’s owner. You can also inspect, create, or delete connections using the Connections Inspector. To bring up the Connections Inspector, select an element in your view and press Opt-Cmd-6, or choose View > Utilities > Show Connection Inspector. Go ahead and save. Next you’ll draw the card, which uses outlets in addition to actions. If you build and run this application now, it will show the screen you just built. Your action messages are empty, so the buttons don’t do anything yet, but you’re getting close.
Figure 4.29. Connecting to an action
Figure 4.28. The FCViewController view

Connecting the FCCardViewController view

The next view to draw is the one for the FCCardViewController, which is shown for each card in the deck. It’s different from the last view because it will be customized rather than having the label and buttons preset with text. You want to access these items in code, so you’ll need to connect them to the outlets you created.

Here’s what you want the view to look like.

Figure 4.30. Sketch of a card

Here’s how you build it:

  1. Select FCCardViewController.xib in Xcode.
  2. Drag a Label and three buttons onto the view. Make sure they’re wide enough to display any state or capital name, and set their text as shown at right.
  3. Bring up the Assistant Editor, and then Ctrl-drag to connect the first button to the answerButtonTouched action.
  4. Repeat with the other two buttons. Notice that it’s OK to connect multiple buttons to the same action.
  5. Ctrl-drag the label to the cardLabel outlet.
Figure 4.31. Card in Interface Builder
Figure 4.32. Connecting an outlet

Ctrl-drag each button to its respective outlet: answer1button, answer2button, and answer3button.

Save and build again to make sure everything is OK. You’re ready for the last view now.

Connecting the FCResultViewController view

Next is the result view. This one needs two labels to show the score and a button to restart the game. The labels are connected to outlets, and the button is connected to an action. Here’s what you want it to look like:

Figure 4.33. Sketch of the result view

Because you’ve done this twice, you should already know how to do it, but here’s a brief guide:

  1. Bring up the view in Interface Builder.
  2. Put two Labels and a button on the view, and set their Title Text properties.
  3. Connect the button’s event to the startAgain action.
  4. Connect the numRightLabel and numWrongLabel outlets of the file’s owner to the corresponding views.

You have completed views and models, and now you need to translate the actions you take in the views to appropriate messages to send to the models. Messages are the way your models determine how the controllers change the views. Controllers have a lot of responsibility. Don’t let working on them go to your head.

Orchestrating your app with controllers

As you learned, controllers are where you interpret the various things the user is doing with your app and then respond to the model’s results by switching views or updating the current view. In the last section, you used Interface Builder to attach the button events to action messages, so all you have left is to implement those actions.

The first controller you’ll write is FCViewController, which is the opening screen of the app. Here’s what it needs to do when you click one of its buttons:

Figure 4.34. FCViewController sequence to show the first card

Because showStates and showCapitals are similar, you’ll create a helper message called showCards that’s shared. Here’s the header code.

Listing 4.7. FCViewController.h

You add some imports for the other classes you communicate with, and you also need to add a property for the FCCardViewController because you’ll need to keep it around after the message is complete.

Now, let’s go through the model code. Define the helper message, showCards:

-(IBAction)showCards:(NSMutableArray*)cards {
    FCGame* game = [[FCGame alloc] initWithCards:cards];

    self.cardVC = [[FCCardViewController alloc]
                          initWithGame:game];
    [self.view.window addSubview:self.cardVC.view];
}

The addSubView message is used to put the card into the main window on top of your start view. Here’s how you call the helper message in the two show messages:

-(IBAction)showStates:(id)sender {
    FCAnswerKey* key = [[FCAnswerKey alloc] init];
    NSMutableArray *cards = [key allocStateCards];
    [self showCards:cards];
}
-(IBAction)showCapitals:(id)sender {
    FCAnswerKey* key = [[FCAnswerKey alloc] init];
    NSMutableArray *cards = [key allocCapitalCards];
    [self showCards:cards];
}

The last thing each show message does is add a view to the top of the application’s window, so after either of the action messages is complete, the view will change to show a card. You’ll see the FCCardViewController next.

Handling card events in the FCCardViewController

FCCardViewController goes through each card in the game until you’re done. It receives the init message to start, and then the iPhone sends it a viewDidLoad message when it’s been attached to the view that was created from the XIB file. FCCardViewController receives a succession of answerButtonTouched messages from the view, which you record; then you proceed to the next card until you’re finished. Finally, you need to show the result view.

Figure 4.35. FCCardViewController sequence

Let’s look at the complete header.

Listing 4.8. FCCardViewController.h

This is pretty similar to what you’ve seen so far. One thing to notice is that you need to keep track of the current card because you get it from the game in nextCard, but you need to record an answer later when an answer button is touched.

The module is also similar to what you’ve seen. Here are the initWithGame and viewDidLoad messages.

Listing 4.9. FCCardViewController.m init and viewDidLoad

In your init, you need to connect it to a view created from the XIB file you drew in Interface Builder by calling initWithNibName . The connection is completed when the iPhone sends you the viewDidLoad message, where you can get the next card and set up the card’s label and answer buttons. Until viewDidLoad is sent, you can’t access the outlets to update the view. Here’s nextCard.

Listing 4.10. FCCardViewController.m nextCard

The nextCard message checks to see if any cards are left in the game . If so, it gets the card and uses it to set up the view . If not, it’s time to show results, so nextCard creates a result view and adds it to the window . Because you’re finished showing cards, the last step is to remove the card view from the window .

Each time an answer button is touched, it calls this message.

Listing 4.11. FCCardViewController.m answerButtonTouched

All the answer buttons are set to call this message when they’re touched. You need to check which one was touched and record the answer on the currentCard. Then, you call nextCard.

You’re almost there. The last class you have to define is the result view that shows the final score.

Showing the result in the FCResultViewController

The last view of your app shows how many you got right and wrong. If you know the difference between Pierre and Bismarck, then you should be fine. If not, well, at least you got Oklahoma City right.

The result view works like this:

Figure 4.36. FCResultViewController sequence

And the code is pretty similar to what you’ve seen.

Listing 4.12. FCResultViewController.h
#import <UIKit/UIKit.h>
#import "FCGame.h"

@interface FCResultViewController : UIViewController

@property(nonatomic, strong) IBOutlet UILabel* numRightLabel;
@property(nonatomic, strong) IBOutlet UILabel* numWrongLabel;
@property(nonatomic, strong) FCGame* game;

-(id) initWithGame:(FCGame*)g;
-(IBAction)startAgain:(id)sender;

@end
Listing 4.13. FCResultViewController.m

The init message needs to load the XIB file . Then, in the viewDidLoad message, you get the score from the game and update the labels . When the startAgain message is sent , all you need to do is remove yourself from the window to reveal the FCViewController’s view.

The app is done. You should be able to build and run the application using Cmd-R.

Reflecting on your progress

There you have it: a complete model-view-controller app that you can use to practice your state capitals (once you fill in the other 47 states, of course). More important, by changing how the AnswerKey works and altering a few message names, you can turn this app into a set of flashcards for anything you know about, from Spanish verbs to Lady Gaga song lyrics.

Even a simple app like this one required you to juggle nine different classes, three views, three controllers, and three model classes. As you progress, it will be important to make sure you map out your app and maintain a good overall picture of what is going on. Even though in this case it might seem as though the design was completely developed and then the code progressed from there, it wasn’t like that. The design was revised several times as the code was developed, to reflect new choices that were discovered as it was coded. You shouldn’t worry about getting the design perfect, but keep it up to date with what you figure out as you code.

Get Hello! iOS Development
add to cart
sitemap
×

Unable to load book!

The book could not be loaded.

(try again in a couple of minutes)

manning.com homepage