Views and Menus - iOS Game Development Cookbook (2014)

iOS Game Development Cookbook (2014)

Chapter 2. Views and Menus

When you fire up a game, you don’t often get immediately dropped into the action. In most games, there’s a lot of “non-game” stuff that your game will need to deal with first, such as showing a settings screen to let your player change volume levels and the like, or a way to let the player pick a chapter in your game to play.

While it’s definitely possible to use your game’s graphics systems to show this kind of user interface, there’s often no reason to re-create the built-in interface libraries that already exist on iOS.

UIKit is the framework that provides the code that handles controls like buttons, sliders, image views, and checkboxes. Additionally, UIKit has tools that let you divide up your game’s screens into separate, easier-to-work-with units called view controllers. These view controllers can in turn be linked up together using storyboards, which let you see how each screen’s worth of content connects to the others.

The controls available to you in UIKit can also be customized to suit the look and feel of your game, which means that UIKit can fit right into your game’s visual design. In addition to simply tinting the standard iOS controls with a color, you can use images and other material to theme your controls. This means that you don’t have to reimplement standard stuff like sliders and buttons, which saves you a lot of time in programming your game.

To work with menus, it’s useful to know how to work with storyboards. So, before we get into the meat of this chapter, we’ll first talk about how to set up a storyboard with the screens you want.

Working with Storyboards

Problem

You need a way to organize the different screens of your game, defining how each screen links to other screens and what content is shown on each screen.

Solution

You can use storyboards to organize your screens:

1. Create a new single-view application. Call it whatever you like.

2. Open the Main.storyboard file. You’re now looking at an empty screen.

3. Open the Utilities pane, if it isn’t already open, by clicking on the Utilities pane button at the far right of the toolbar.

At the bottom of the pane, the objects library should be visible, showing a list of objects you can add to the storyboard (see Figure 2-1). If it isn’t visible, click the “Show or hide object library” button. Alternatively, press Control-Option-Command-3.

An empty screen.

Figure 2-1. The objects library, visible at the bottom-right

4. Scroll down in the objects library until you find the button, as shown in Figure 2-2. You can also type “button” into the search field at the bottom of the objects library.

Finding buttons in the Object Library.

Figure 2-2. Finding the button in the objects library

5. Drag a button into the window.

6. Run the application. A button will appear on the screen, as shown in Figure 2-3. You can tap it, but it won’t do anything yet.

A button shown on the iPhone’s screen

Figure 2-3. A button shown on the iPhone’s screen

Next, we’ll set up the application so that tapping on the button displays a new screen.

7. Find the navigation controller in the objects library, and drag one into the storyboard.

Navigation controllers come with an attached Table View. We don’t want this—select it and delete it.

8. The original screen currently has a small arrow attached to it. This indicates that it’s the screen that will appear when the application begins. Drag this arrow from where it is right now to the navigation controller.

9. Hold down the Control key, and drag from the navigation controller to the first screen.

10.A window containing a list of possible “segues” will appear, as shown in Figure 2-5. Choose the “root view controller” option.

Connecting the button to the new view controller.

Figure 2-4. Selecting a segue

When the application starts up, the navigation controller will appear, and inside it, you’ll see the screen you designed. Next, we’ll make it so that the button shows an additional screen when it’s tapped.

11.Drag a new view controller into the storyboard.

A new, empty screen will appear.

12.Hold down the Control key, and drag from the button to the new screen.

Another list of possible segues will appear. Choose “push.”

13.The two screens will appear linked with a line, as shown in Figure 2-4, which indicates that it’s possible to go from one screen to the next.

The navigation controller

Figure 2-5. The linked view controllers

14.Run the application.

When you tap the button, a new screen will appear. A Back button will also appear in the navigation bar at the top of the screen.

When you create a new project, it will come with a storyboard file. Storyboards are files that define what view controllers are used in your application, and how those view controllers are linked together via segues.

A view controller is an Objective-C object that contains the logic that controls an individual screen. Typically, you have one view controller per screen. For each type of screen that the user will see, you create a subclass of the UIViewController class, and you instruct the storyboard to use that subclass for specific screens. (For information on how to do this, see Creating View Controllers.)

In a storyboard, screens can be linked together using segues. A segue is generally a transition from one screen to the next (e.g., a push segue tells the system that, when the segue is activated, the next screen should be pushed into the current navigation controller, assuming one exists).

Segues are also used to indicate relationships between different view controllers. For example, navigation controllers need to know which screen they should display when they’re first shown; this is done by creating a root view controller segue.

Creating View Controllers

Problem

You have a project that has a storyboard, and you want to keep all of the logic for your screens in separate, easily maintainable objects. For example, you want to keep your main menu code separate from the high scores screen.

Solution

Follow these steps to create a new view controller:

1. Create a subclass of UIViewController.

Choose New→File from the File menu. Select the Cocoa Touch category, and choose to make an Objective-C subclass, as shown in Figure 2-6.

Selecting the ‘Objective-C subclass’ template.

Figure 2-6. Creating a new Objective-C subclass

2. Create the new UIViewController subclass.

Name the class “MainMenuViewController,” and set “Subclass of” to “UIViewController.”

Make sure that both “Targeted for iPad” and “With XIB for user interface” are “unchecked,” as shown in Figure 2-7.

Naming the view controller +MainMenuViewController+.

Figure 2-7. Setting up the new file

3. Create the files.

Xcode will ask you where to put the newly created files. Choose somewhere that suits your particular purpose.

Once this is done, two new files will appear in the Project Navigator: MainMenuViewController.m and MainMenuViewController.h. In order to actually use this new class, you need to indicate to the storyboard that it should be used on one of the screens. In this example, there’s only one screen, so we’ll make it use the newly created MainMenuViewController class.

4. Open the storyboard.

Find Main.storyboard in the Project Navigator, and click it.

5. Open the outline view.

The outline view lists all of the parts making up your user interface. You can open it either by choosing Show Document Outline from the Editor menu, or by clicking on the Show Outline button at the bottom left of the Interface Builder.

6. Select the view controller.

You’ll find it at the top of the outline view.

7. Open the Identity inspector.

You can do this by choosing View→Utilities→Identity Inspector, or by pressing Command-Option-3. (There’s also a toolbar at the top of the Utilities pane, which you can use to open the Identity inspector.)

8. Change the class of the selected view controller to MainMenuViewController.

At the top of the Identity inspector, you’ll find the class of the currently selected view controller. Change it to “MainMenuViewController,” as shown in Figure 2-8. Doing this means that the MainMenuViewController class will be the one used for this particular screen.

Setting the class to +MainMenuViewController+.

Figure 2-8. Changing the class of the view controller

Now that this is done, we’ll add a text field to the screen and make the view controller class able to access its contents (on its own, a text field can’t communicate with the view controller—you need an outlet for that). We’ll also add a button to the screen, and make some code run when the user taps it.

9. Add a text field.

The objects library should be at the bottom of the Utilities pane, on the righthand side of the Xcode window. If it isn’t visible, choose View→Utilities→Objects Library, or press Command-Control-Option-3.

Scroll down until you find the Text Field control. Alternatively, type “text field” into the search bar at the bottom of the objects library.

Drag and drop a text field into the screen, as shown in Figure 2-9.

Finding the text field in the Object Library.

Figure 2-9. A text field is added to the screen

On its own, a text field can’t communicate with the view controller—you need an outlet for that.

10.Open the view controller’s code in the Assistant editor.

Open the Assistant editor, by clicking on the Assistant button at the top right of the Xcode window or by choosing View→Assistant Editor→Show Assistant Editor.

Once you’ve opened it, MainMenuViewController.m should be visible in the editor. If it isn’t, choose Automatic→MainMenuViewController.h from the jump bar at the top of the Assistant editor (Figure 2-10).

Selecting +MainMenuViewController.h+ in the Assistant.

Figure 2-10. Selecting MainMenuViewController.h in the Assistant

11.Add the outlet for the text field.

Hold down the Control key, and drag from the text field into the @interface of MainMenuViewController. When you finish dragging, a dialog box will appear, a new property will appear (see Figure 2-11) asking you what to name the variable.

Type “textField,” and click the Connect button.

Creating an outlet for the text field.

Figure 2-11. Creating an outlet for the text field

Finally, we’ll add a button, which will run code when the button is tapped.

12.Add a button to the screen.

Follow the same instructions for adding a text field, but this time, add a button.

13.Add the action.

Hold down the Control key, and drag from the button into the @implementation part of MainMenuViewController’s code.

Another dialog will appear, as seen in Figure 2-12, asking you what to name the action. Name it “buttonPressed,” and click Connect. A new method will be added to MainMenuViewController.

Creating an action.

Figure 2-12. Creating an action

14.Add some code to the newly created method.

We’ll use the following code:

UIAlertView* alertView = [[UIAlertView alloc] init];

alertView.title = @"Button tapped";

alertView.message = @"The button was tapped!";

[alertView addButtonWithTitle:@"OK"];

[alertView show];

Discussion

In almost every single case, different screens perform different tasks. A “main menu” screen, for example, has the task of showing the game’s logo, and probably a couple of buttons to send the player off to a new game, to continue an existing game, to view the high scores screen, and so on. Each one of these screens, in turn, has its own functionality.

The easiest way to create an app that contains multiple screens is via a storyboard. However, a storyboard won’t let you define the behavior of the screens—that’s the code’s job. So, how do you tell the application what code should be run for different screens?

Every view controller that’s managed by a storyboard is an instance of the UIViewController class. UIViewControllers know how to be presented to the user, how to show whatever views have been placed inside them in the interface builder, and how to do a few other things, like managing their life cycle (i.e., they know when they’re about to appear on the screen, when they’re about to go away, and when other important events are about to occur, like the device running low on memory).

However, a UIViewController doesn’t have any knowledge of what its role is—they’re designed to be empty templates, and it’s your job to create subclasses that perform the work of being a main menu, or of being a high scores screen, and so on.

When you subclass UIViewController, you override some important methods:

§ viewDidLoad is called when the view has completed loading and all of the controls are present.

§ viewWillAppear is called when the view is about to appear on the screen, but is not yet visible.

§ viewDidAppear is called when the view has finished appearing on the screen and is now visible.

§ viewWillDisappear is called when the view is about to disappear from the screen, but is currently still visible.

§ viewDidDisappear is called when the view is no longer visible.

§ applicationReceivedMemoryWarning is called when the application has received a memory warning and will be force quit by iOS if memory is not freed up. Your UIViewController subclass should free any objects that can be re-created when needed.

Additionally, your UIViewController is able to respond to events that come from the controls it’s showing, and to manipulate those controls and change what they’re doing.

This is done through outlets and actions. An outlet is a property on the UIViewController that is connected to a view; once viewDidLoad has been called, each outlet property has been connected to a view object. You can then access these properties as normal.

To define an outlet, you create a property that uses the special keyword IBOutlet, like so:

@property (strong) IBOutlet UITextField* aTextField;

Actions are methods that are called as a result of the user doing something with a control on the screen. For example, if you want to know when a button has been tapped, you create an action method and connect the button to that method. You create similar action methods for events such as the text in a text field changing, or a slider changing position.

Action methods are defined in the @interface of a class, like this:

- (IBAction) anActionMethod:(id)sender;

Note that IBAction isn’t really a return type—it’s actually another name for void. However, Xcode uses the IBAction keyword to identify methods that can be connected to views in the interface builder.

In the preceding example, the action method takes a single parameter: an id (i.e., an object of any type) called sender. This object will be the object that triggered the action: the button that was tapped, the text field that was edited, and so on.

If you don’t care about the sender of the action, you can just define the method with no parameters, like so:

- (IBAction) anActionMethod;

Using Segues to Move Between Screens

Problem

You want to use segues to transition between different screens.

Solution

We’ll step through the creation of two view controllers, and show how to create and use segues to move between them:

1. Create a new single-view project.

2. Open Main.storyboard.

3. Add a new view controller.

You can do this by searching for “view controller” in the objects library.

4. Add a button to the first view controller.

Label it “Automatic Segue.”

5. Add a segue from the button to the second view controller.

Hold down the Control key, and drag from the button to the second view controller. A menu will appear when you finish dragging, which shows the possible types of segues you can use. Choose “modal.”

6. Run the application.

When you tap the button, the second (empty) view controller will appear.

Currently, there’s no way to return to the first one. We’ll now address this:

1. Open ViewController.m.

This is the class that powers the first screen.

2. Add an exit method.

Add the following method to ViewController’s @implementation section:

- (IBAction)closePopup:(UIStoryboardSegue*)segue {

NSLog(@"Second view controller was closed!");

}

3. Add an exit button to the second view controller.

Add a button to the second view controller, and label it “Exit.”

Then, hold down the Control key, and drag from the button to the Exit icon underneath the screen. It looks like a little green box.

A menu will appear, listing the possible actions that can be run. Choose “closePopup:.”

4. Run the application.

Open the pop-up screen, and then tap the Exit button. The screen will disappear, and the console will show the “Second view controller was closed!” text.

Finally, we’ll demonstrate how to manually trigger a segue from code. To do this, we’ll first need a named segue in order to trigger it. One already exists—you created it when you linked the button to the second view controller:

1. Give the segue an identifier.

In order to be triggered, a segue must have a name. Click on the segue you created when you linked the button to the second view controller (i.e., the line connecting the first view controller to the second).

In the Attributes inspector (choose View→Utilities→Show Attributes Inspector, or press Command-Option-4), set the identifier of the segue to “ShowPopup,” as seen in Figure 2-13.

Naming the segue.

Figure 2-13. Naming the segue

WARNING

Make sure you use the same capitalization as we’ve used here. “ShowPopup” isn’t the same as “showpopup” or even “ShowPopUp.”

2. Add a button that manually triggers the segue.

Add a new button to the first view controller. Label it “Manual Segue.”

Open ViewController.m in the Assistant editor.

Hold down the Control key, and drag from the new button into ViewController’s @implementation section. Create a new method called “showPopup”—note the capitalization.

Add the following code to this new method:

[self performSegueWithIdentifier:@"ShowPopup" sender:self];

3. Run the application.

Now, tapping on the Manual Segue button shows the second screen.

Discussion

A segue is an object that describes a transition from one view controller to the next.

When you run a segue, the segue takes care of presenting the new view controller for you. For example, if you create a push segue, the segue will handle creating the view controller and pushing the new view controller onto the navigation controller’s stack.

A segue performs its work of transitioning between view controllers when it’s triggered. There are two ways you can trigger a segue: you can connect it to a button in the interface builder, or you can trigger it from code.

When you hold down the Control key and drag from a button to a different screen, you create a segue. This new segue is set up to be triggered when the button is tapped.

Triggering a segue from code is easy. First, you need to give the segue an identifier, which is a string that uniquely identifies the segue. In our example, we set the identifier of the segue to “ShowPopup.”

Once that’s done, you use the performSegueWithIdentifier:sender: method. This method takes two parameters—the name of the segue you want to trigger, and the object that was responsible for triggering it:

[self performSegueWithIdentifier:@"ShowPopup" sender:self];

You can trigger any segue from code, as long as it has an identifier. You don’t need to create multiple segues from one view controller to the next.

When a view controller is about to segue to another, the view controller that’s about to disappear is sent the prepareForSegue:sender: message. When you want to know about the next screen that’s about to be shown to the user, you implement this method, like so:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

NSLog(@"About to perform the %@ segue", segue.identifier);

}

The method has two parameters: the segue that’s about to run, and the object that triggered the segue. The segue object itself contains two particularly useful properties: the identifier of the segue, which allows you to differentiate between different segues; and thedestinationViewController, which is the view controller that’s about to be displayed by the segue. This gives you an opportunity to send information to the screen that’s about to appear.

Finally, exit segues are segues that allow you to return to a previously viewed view controller. You don’t create these segues yourself; rather, you define an action method in the view controller that you’d like to return to, and then connect a control to the “exit” segue in the interface builder.

Using Constraints to Lay Out Views

Problem

You have a screen with another view (such as a button) inside it. You want the views to stay in the correct places when the screen rotates.

Solution

You use constraints to position views, as illustrated here:

1. Drag the view into the place you’d like to put it.

2. Add the constraint.

Select the view, and open the Pin menu. This is the second button in the second group of buttons at the bottom right of the interface builder canvas (Figure 2-14).

The Constraints menu buttons. The Pin button is the second one from the left.

Figure 2-14. The Constraints menu buttons (the Pin button is the second one from the left)

3. Apply the values you want to use for the new constraints.

Let’s assume that you want the view to always be 20 pixels from the top edge of the screen and 20 pixels from the left edge of the screen, as shown in Figure 2-15.

The view should always be 20 pixels from the top and left edges.

Figure 2-15. The view should always be 20 pixels from the top and left edges

Type “20” into the top field, and “20” into the left field.

Set “Update Frames” to “Items of New Constraints.” This will reposition the view when the constraints are added (Figure 2-16).

Creating the constraints

Figure 2-16. Creating the constraints

4. Add the constraints.

The button at the bottom of the menu should now read “Add 2 Constraints.” Click it, and the button will be locked to the upper-right corner.

Discussion

To control the positioning and sizing of views, you use constraints. Constraints are rules that are imposed on views, along the lines of “Keep the top edge 10 pixels beneath the top edge of the container,” or “Always be half the width of the screen.” Without constraints, views don’t change position when the size and shape of their superview changes shape (such as when the screen rotates).

Constraints can modify both the position and the size of a view, and will change these in order to make the view fit the rules.

Adding Images to Your Project

Problem

You want to add images to your game’s project in Xcode, so that they can be used in your menu interface.

Solution

When you create a new project in Xcode, an asset catalog is created, which contains your images. Your code can then get images from the catalog, or you can use them in your interface.

If your project doesn’t have one, or if you want a new one, choose File → New → File, choose Resource, and choose Asset Catalog.

Select the asset catalog in the Project Navigator, and then drag and drop your image into the catalog.

You can rename individual images by double-clicking on the name of the image in the left pane of the asset catalog.

The easiest way to display an image is through a UIImageView. To add an image view, search for “image view” in the objects library, and add one to your screen. Then, select the new image view, open the Attributes inspector, and change the Image property to the name of the image you added (Figure 2-17).

Setting the image for a +UIImageView+.

Figure 2-17. Setting the image for a UIImageView

You can also use images in your code. For example, if you’ve added an image called “Spaceship” to an asset catalog, you can use it in your code as follows:

UIImage* image = [UIImage imageNamed:@"Spaceship"];

Once you have this image, you can display it in a UIImageView, display it as a sprite, or load it as a texture.

Discussion

It’s often the case that you’ll need to use different versions of an image under different circumstances. The most common example of this is when working with devices that have a retina display, as such devices have a screen resolution that’s double the density of non-retina-display devices.

In these cases, you need to provide two different copies of each image you want to use: one at regular size, and one at double the size.

Fortunately, asset catalogs make this rather straightforward. When you add an image to the catalog, you can add alternative representations of the same image, making available both 1x (non-retina) and 2x (retina) versions of the image. You can also add specific versions of an image for the iPhone and iPad; when you get the image by name, the correct version of the image is returned to you, which saves you having to write code that checks to see which platform your game is running on.

Slicing Images for Use in Buttons

Problem

You want to customize a button with images. Additionally, you want the image to not get distorted when the button changes size.

Solution

To customize your button, perform the following steps:

1. Add the image you want to use to an asset catalog.

See Adding Images to Your Project to learn how to do this.

2. Select the newly added image, and click Show Slicing.

The button is at the bottom right of the asset catalog’s view.

3. Click Start Slicing.

Choose one of the slicing options. You can slice the image horizontally, vertically, or both horizontally and vertically, as seen in Figure 2-18.

The available slicing options are

Figure 2-18. The available slicing options are, from left to right, horizontal, both horizontal and vertical, and vertical

4. Open your storyboard, and select the button you want to theme.

5. Open the Attributes inspector, and set the background.

Select the name of the image that you added to the Xcode catalog. The button will change to use the new background image. You can also resize the image, and the background image will scale appropriately.

Discussion

One of the most effective ways to customize the look and feel of your game’s user interface is to use custom images for buttons and other controls, which you can do by setting the “Background” property of your buttons.

However, if you want to use background images, you need to take into account the size of the controls you’re theming: if you create an image that’s 200 pixels wide and try to use it on a button that’s only 150 pixels wide, your image will be squashed. Do the reverse, and you’ll end up with a repeating background, which may look worse.

To solve this issue, you can slice your images. Slicing divides your image into multiple regions, of which only some are allowed to squash and stretch or repeat.

In this solution, we looked at customizing a button; however, the same principles apply to other other controls as well.

Using UI Dynamics to Make Animated Views

Problem

You want to make the views on your screen move around the screen realistically.

Solution

You can add physical behaviors to any view inside a view controller, using UIDynamicAnimator. In these examples, we’ll assume that you’ve got a view called animatedView. It can be of any type—button, image, or anything else you like.

First, create a UIDynamicAnimator object in your view controller. Add this property to your view controller’s @interface section:

@property (strong) UIDynamicAnimator* animator;

Then, in your view controller’s viewDidLoad method, add the following code:

self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

We’ll now talk about how you can add different kinds of physical behaviors to your views. Because these behaviors interact with each other, we’ll go through each one and, in each case, assume that the previous behaviors have been added.

Adding gravity to views

Add the following code to your viewDidLoad method:

UIGravityBehavior* gravity =

[[UIGravityBehavior alloc] initWithItems:@[animatedView]];

[self.animator addBehavior:gravity];

Run the application. The view will move down the screen (and eventually fall off!).

Adding collision

Add the following code to your viewDidLoad method:

UICollisionBehavior* collision =

[[UICollisionBehavior alloc] initWithItems:@[animatedView]];

[collision setTranslatesReferenceBoundsIntoBoundary:YES];

[self.animator addBehavior:collision];

Run the application. The button will fall to the bottom of the screen.

NOTE

By default, the collision boundaries will be the same as the boundaries of the container view. This is what’s set when setTranslatesReferenceBoundsIntoBoundary: is called. If you would prefer to create boundaries that are inset from the container view, use setTranslatesReferenceBoundsIntoBoundaryWithInsets: instead:

// Inset the collision bounds by 10 points on all sides

[collision setTranslatesReferenceBoundsIntoBoundaryWithInsets:

UIEdgeInsetsMake(10, 10, 10, 10)];

Adding collision

Add the following code to your viewDidLoad method:

UIAttachmentBehavior* attachment =

[[UIAttachmentBehavior alloc] initWithItem:animatedView

attachedToAnchor:CGPointZero];

[self.animator addBehavior:attachment];

Run the application. The button will swing down and hit the side of the screen.

When you create a UIAttachmentBehavior, you can attach your views to specific points, or to other views. If you want to attach your view to a point, you should use initWithItem:attachedToAnchor:, as seen in the previous example. If you want to attach a view to another view, you use initWithItem:attachedToItem::

[[UIAttachmentBehavior alloc] initWithItem:animatedButton

attachedToItem:anotherView];

Discussion

UIKit has a physics engine in it, which you can use to create a complex set of physically realistic behaviors.

The dynamic animation system is designed for user interfaces, rather than games—if you’re interested in using physics simulation for games, use Sprite Kit (see Chapter 6).

Keep in mind that controls that move around too much may end up being disorienting for users, who are used to buttons generally remaining in one place. Additionally, if you’re displaying your UI content on top of your game, you may end up with performance problems if you have lots of controls that use the dynamic animation system.

Moving an Image with Core Animation

Problem

You want an image to move around the screen, and smoothly change its position over time.

Solution

To animate your image, follow these steps:

1. Create a new single-view application for iPhone, named “ImageAnimation.”

2. Add an image of a ball to the project (one has been provided as part of the sample code):

o Open ViewController.xib.

o Add an image view to the screen. Set it to display the image you dragged in.

o Open the Assistant editor by clicking on the middle button in the Editor selector, at the top right of the Xcode window. The code for ViewController.h will appear.

o Hold down the Control key, and drag from the image view into ViewController.h, between the @interface and @end lines.

A small window will pop up, prompting you to provide information. Use the following settings:

§ Connection: Outlet

§ Name: ball

§ Type: UIImageView

§ Storage: Strong

o Click Connect. The following line will appear in your code:

@property (strong, nonatomic) IBOutlet UIImageView *ball;

o Close the Assistant editor, and open ViewController.m.

o Add the following method to the code:

o - (void)viewDidAppear:(BOOL)animated {

o [UIView animateWithDuration:2.0 animations:^{

o self.ball.center = CGPointMake(0, 0);

o }];

}

3. Run the application.

When the app starts up, the ball will slowly move up to the upper-left corner of the screen.

Discussion

This application instructs a UIImageView to change its position over the course of two seconds.

Image views, being completely passive displays of images, have no means (nor any reason) to move around on their own, which means that something else needs to do it for them. That “something else” is the view controller, which is responsible for managing each view that’s displayed on the screen.

In order for the view controller to be able to tell the image view to move, it first needs an outlet to that image view. An outlet is a variable in an object that connects to a view. When you hold the Control key and drag and drop from the image view into the code, Xcode recognizes that you want to add a connection and displays the “add connection” dialog.

When the application launches, the first thing that happens is that all connections that were created in the interface builder are set up. This means that all of your code is able to refer to properties like self.ball without having to actually do any of the work involved in setting them up.

Once the connection is established, the real work of moving the ball around on the screen is done in the viewWillAppear: method. This method is called, as you can probably tell from the name, when the view (i.e., the screen) is about to appear to the user. This snippet of code is where the actual work is done:

[UIView animateWithDuration:2.0 animations:^{

self.ball.center = CGPointMake(0, 0);

}];

The animateWithDuration:animations method takes two parameters: the duration of the animation, and a block that performs the changes that should be seen during the animation.

In this case, the animation being run takes two seconds to complete, and a single change is made: the center property of whatever view self.ball refers to is changed to the point (0,0). Additional changes can be included as well. For example, try adding the following line of code between the two curly braces ({}):

self.ball.alpha = 0;

This change causes the view’s alpha setting (its opacity) to change from whatever value it currently is to zero, which renders it fully transparent. Because this change is run at the same time as the change to the view’s position, it will fade out while moving.

Rotating an Image

Problem

You want to rotate an image on the screen.

Solution

To rotate an image view, use the transform property:

self.rotatedView.transform = CGAffineTransformMakeRotation(M_PI_2);

In this example, self.rotatedView is a UIImageView. Any view can be rotated, though, not just image views.

Discussion

The transform property allows you to modify a view’s presentation without affecting its contents. This allows you to rotate, shift, squash, and stretch a view however you want.

The value of transform is a CGAffineTransform, which is a 4-by-4 matrix of numbers. This matrix is multiplied against the four vertices that define the four corners of the view. The default transform is the identity transform, CGAffineTransformIdentity, which makes no changes to the presentation of the view.

To create a transform matrix that rotates a view, you use the CGAffineTransformMakeRotation method. This method takes a single parameter: the amount to rotate by, measured in radians. There are 2π radians in a circle; therefore, a rotation of one-quarter of a circle is 2π/4 = π/2. This value is available via the built-in shorthand M_PI_2.

In our example, we have created a transform matrix using the CGAffineTransformMakeRotation function. Other functions you can use include:

CGAffineTransformMakeTranslation

Adjusts the position of the view

CGAffineTransformMakeScale

Scales the view, on the horizontal or vertical axis

Once you’ve created a transform matrix, you can modify it by scaling, translating (moving), or rotating it. You can do this using the CGAffineTransformScale, CGAffineTransformTranslate, and CGAffineTransformRotate functions, which each take an existing transform and modify it. Once you’re done making changes to the transform, you can then apply it.

For example:

CGAffineTransform transform = CGAffineTransformIdentity; 1

transform = CGAffineTransformTranslate(transform, 50, 0); 2

transform = CGAffineTransformRotate(transform, M_PI_2); 3

transform = CGAffineTransformScale(transform, 0.5, 2); 4

self.myTransformedView.transform = transform; 5

This code does the following:

1

Start with the default identity transform.

2

Translate the transform 50 pixels to the right.

3

Rotate the transform one quarter-circle clockwise.

4

Scale the transform by 50% on the horizontal axis and 200% on the vertical axis.

5

Apply the transform to a view.

The transform property of a view can be animated, just like its opacity and position. This lets you create animations where views rotate or squash and stretch.

Animating a Popping Effect on a View

Problem

You want the main menu of your game to feature buttons that appear with a visually appealing “pop” animation, which draws the player’s eye (the object starts small, expands to a large size, and then shrinks back down to its normal size, as shown in Figure 2-19).

In a “pop” animation

Figure 2-19. A “pop” animation

Solution

The solution to this problem makes use of features available in the Quartz Core framework, which allows access to animation features in UIKit. To use it, you’ll need to add the Quartz Core framework to your project. For instructions on doing this, see Getting Information About What the iPod Is Playing. Then, add the following line to the top of your source code:

#import <QuartzCore/QuartzCore.h>

Finally, add the following code at the point where you want the popping animation to happen:

CAKeyframeAnimation* keyframeAnimation =

[CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];

keyframeAnimation.values = @[@0.0, @1.2, @1.0];

keyframeAnimation.keyTimes = @[@0.0, @0.7, @1.0];

keyframeAnimation.duration = 0.4;

keyframeAnimation.timingFunction =

[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];

[self.poppingButton.layer addAnimation:keyframeAnimation forKey:@"pop"];

NOTE

In this example, self.poppingButton is a UIView, but any view can be animated in this way.

Discussion

Every single view on the screen can be animated using Core Animation. These animations can be very simple, such as moving an item from one location to another, or they can be more complex, such as a multiple-stage “pop” animation.

This can be achieved using a CAKeyframeAnimation. CAKeyframeAnimations allow you to change a visual property of any view over time, and through multiple stages. You do this by creating a CAKeyframeAnimation, providing it with the values that the property should animate through, and providing corresponding timing information for each value. Finally, you give the animation object to the view’s CALayer, and the view will perform the animation.

Every animation object must have a “key path” that indicates what value should change when the animation runs. The key path used for this animation is transform.scale, which indicates that the animation should be modifying the scale of the view.

The values property is an NSArray that contains the values that the animation should move through. In this animation, there are three values: 0, 1.2, and 1.0. These will be used as the scale values: the animation will start with a scale of zero (i.e., scaled down to nothing), then expand to 1.2 times the normal size, and then back down to the normal size.

The keyTimes property is another NSArray that must contain the same number of NSNumbers as there are values in the values array. Each number in the keyTimes array indicates at which point the corresponding value in the values array will be reached. Each key time is given as a value from 0 to 1, with 0 being the start of the animation and 1 being the end.

In this case, the second value (1.2) will be reached at 70% of the way through the animation, because its corresponding key time is 0.7. This is done to make the first phase of the animation, in which the button expands from nothing, take a good amount of time, which will look more natural.

The duration of the animation is set to 0.4 seconds, and the timing function is set to “ease out.” This means that the “speed” of the animation will slow down toward the end. Again, this makes the animation feel more organic and less mechanical.

Lastly, the animation is given to the view’s layer using the addAnimation:forKey: method. The “key” in this method is any string you want—it’s an identifier to let you access the animation at a later time.

Theming UI Elements with UIAppearance

Problem

You want to change the color of views, or use background images to customize their look and feel.

Solution

You can set the color of a view by changing its tint color or by giving it a background image:

UIButton* myButton = ... // a UIButton

[myButton setTintColor:[UIColor redColor]];

UIImage* backgroundImage = ... // a UIImage

[myButton setBackgroundImage:backgroundImage forState:UIControlStateNormal];

Discussion

Most (though not all) controls can be themed. There are two main ways you can change a control’s appearance:

§ Set a tint color, which sets the overall color of the control.

§ Set a background image, which sets a background image.

To set a tint color for a specific control, you call one of the setTintColor: family of methods. Some controls have a single tint color, which you set with setTintColor:, while other controls can have multiple tint colors, which you set independently. For example, to tint aUIProgressView, do the following:

// self.progressView is a UIProgressView

[self.progressView setProgressTintColor:[UIColor orangeColor]];

You can also set the tint color for all controls of a given type, like this:

[[UIProgressView appearance] setProgressTintColor:[UIColor orangeColor]];

Background images work similarly:

UIImage* backgroundImage = [UIImage imageNamed:@"NavigationBarBackground.png"];

[[UIProgressView appearance] setBackgroundImage:backgroundImage

forBarMetrics:UIBarMetricsDefault];

You’ll notice that the specific method used to customize different controls varies, because different controls have different visual elements that need theming in different ways. There isn’t room in this book to list all of the different ways each control can be customized, but if you Command-click on the name of a class, such as UIButton, you’ll be taken to the header file for that class. In the header file, any property that has UI_APPEARANCE_SELECTOR is one that can be used for theming—that is, you can use [UIButton appearance] to change that property for allUIButtons.

You can customize the appearance of individual controls by using the appearance methods. If you want to customize every control of a given class, you first get that class’s appearance proxy. The appearance proxy is a special object that stores all changes submitted to it; all views consult it when they’re appearing on-screen and update their appearance based on it.

To get the appearance proxy for a class, you use the appearance method, like so:

[UIProgressView appearance];

You then send the appearance customization messages to it, as if it were a specific instance of that class. All instances of that class will update their appearance to reflect what you provide:

// Set ALL UIProgressViews to have an orange tint

[[UIProgressView appearance] setProgressTintColor:[UIColor orangeColor]];

Rotating a UIView in 3D

Problem

You want to make a view rotate in 3D, and have a perspective effect as it does so (i.e., as parts of the view move away from the user, they get smaller, and as they get closer, they get larger).

Solution

To implement this functionality, you’ll need to add the Quartz Core framework to your project and import the QuartzCore/QuartzCore.h header file (see Recipes and ).

Then, when you want the animation to begin, you do this:

CABasicAnimation* animation =

[CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];

animation.fromValue = @(0);

animation.toValue = @(2 * M_PI);

animation.repeatCount = INFINITY;

animation.duration = 5.0;

[self.rotatingView.layer addAnimation:animation forKey:@"rotation"];

CATransform3D transform = CATransform3DIdentity;

transform.m34 = 1.0 / 500.0;

self.rotatingView.layer.transform = transform;

To stop the animation, you do this:

[self.rotatingView.layer removeAnimationForKey:@"rotation"];

NOTE

In this example code, self.rotatingView is a UIView that’s on the screen. This technique can be applied to any view, though—buttons, image views, and so on.

Discussion

CABasicAnimation allows you to animate a property of a view from one value to another. In the case of rotating a view, the property that we want to animate is its rotation, and the values we want to animate from and to are angles.

When you use [CABasicAnimation animationWithKeyPath:] to create the animation, you specify the property you want to animate. In this case, the one we want is the rotation around the y-axis:

CABasicAnimation* animation =

[CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];

The animation is then configured. In this example, we made the rotation start from zero, and proceed through to a full circle. In Core Animation, angles are measured in radians, and there are 2π radians in a full circle. So, the fromValue and toValue are set thusly:

animation.fromValue = @(0);

animation.toValue = @(2 * M_PI);

Next, the animation is told that it should repeat an infinite number of times, and that the full rotation should take five seconds:

animation.repeatCount = INFINITY;

animation.duration = 5.0;

The animation is started by adding it to the view’s layer, using the addAnimation:forKey: method. This method takes two parameters, the animation object that you want to use and a key (or name) to use to refer to the animation:

[self.rotatingView.layer addAnimation:animation forKey:@"rotation"];

Don’t be confused by the similarity between the “key” that you use when you add the animation and the “key path” you use when creating the animation. The former is just a name you give the animation, and can be anything; the key path describes exactly what the animation modifies.

The last step is to give the rotating view a little perspective. If you run the code while omitting the last few lines, you’ll end up with a view that appears to horizontally squash and stretch. What you want is for the edge that’s approaching the user’s eye to appear to get bigger, while the edge that’s moving away from the user’s eye appears to get smaller.

This is done by modifying the view’s 3D transform. By default, all views have a transform matrix applied to them that makes them all lie flat over each other. When you want something to have perspective, though, this doesn’t apply, and you need to override it:

CATransform3D transform = CATransform3DIdentity;

transform.m34 = 1.0 / 500.0;

self.rotatingView.layer.transform = transform;

The key to this part of the code is the second line: the one where the m34 field of the transform is updated. This part of the transform controls the sharpness of the perspective. (It’s basically how much the z coordinate gets scaled toward or away from the vanishing point as it moves closer to or further from the “camera.”)

Overlaying Menus on Top of Game Content

Problem

You want to overlay controls and views on top of your existing game content. For example, you want to overlay a pause menu, or put UIButtons on top of sprites.

Solution

You can overlay any UIView you like on top of any other UIView. Additionally, both OpenGL views and Sprite Kit views are actually UIViews, which means anything can be overlaid on them.

To create a view that you can show, you can use nibs. A nib is like a storyboard, but only contains a single view.

To make a nib, choose File→New→File from the menu, choose User Interface, and choose View. Save the new file wherever you like. You can then edit the file and design your interface.

To instantiate the nib and use the interface you’ve designed, you first create a UINib object by loading it from disk, and then ask the nib to instantiate itself:

UINib* nib = [UINib nibWithNibName:@"MyNibFile" bundle:nil];

NSArray* nibObjects = [nib instantiateWithOwner:self options:nil];

The instantiateWithOwner:options: method returns an NSArray that contains the objects that you’ve designed. If you’ve followed these instructions and created a nib with a single view, this array will contain a single object—the UIView object that you designed:

UIView* overlayView = [nibObjects firstObject];

Once you have this, you can add this view to your view controller using the addSubview: method:

// In your view controller code:

[self.view addSubview:overlayView];

Discussion

Keep in mind that if you overlay a view on top of Sprite Kit or OpenGL, you’ll see a performance decrease. This is because the Core Animation system, which is responsible for compositing views together, has to do extra work to combine UIKit views with raw OpenGL.

That’s not to say that you shouldn’t ever do it, but be mindful of the possible performance penalty.

Designing Effective Game Menus

Problem

You want to build a game that uses menus effectively.

Solution

You should make your menus as simple and easy to navigate as possible. There’s nothing worse than an iOS game that has an overly complicated menu structure, or tries to present too many options to the player. Menus should be simple and have the minimum amount of options required to make your game work.

Including only the minimum required set of options will make players feel more confident and in control of the game—ideally they’ll be able to play through your entire game without ever using any menus beyond those necessary to start and end the game. And those menus should be super simple too!

Discussion

It’s important to keep your game menus as simple as possible. When your game is the app running on a device, a simple and clear menu will ensure that the user is more likely to be playing the game than trying to configure it. You’re building a game, not an elaborate set of menus!