Chapter    1

Application Recipes

We’re going to start this book with a set of recipes dealing with the iOS application, its project, and various basic Xcode tasks. The first eight recipes are fundamental, showing things like how to setup an application, how to connect and reference user interface elements to your code, and how to add images and sound files to your project. If you’re new to iOS development we suggest you go through those first before moving on.

We also recommend that you take a closer look at Recipe 1-9 to see whether Storyboards is something for you. Storyboards is the new way of designing user interface in iOS, allowing you to gather several views in one file. Although the examples in this book are based on the old way of creating user interfaces, having one .xib file per view controller, you could just as easily do them the storyboards way.

The last four recipes in this chapter deal with miscellaneous topics like how to set up simple APIs for default error and exception handling; how to include a Lite version of your app in your projects, and how to make the app launch seem shorter in the eyes of the user.

Recipe 1-1: Setting Up a Single-View Application

Many of the recipes in this book are implemented in a test application with a single view. Such a project is easy to setup in Xcode using the Single View Application template.

To create a new single-view application project in Xcode, go to the main menu and select File image New image Project. This brings up the dialog with available project templates (see Figure 1-1). The template you’re looking for is located in the Application page under the iOS section.

9781430245995_Fig01-01.jpg

Figure 1-1.  The single view application template in the iOS application section

After you’ve selected the Single View Application template and clicked Next, you need to enter a few properties for your application:

  • A Product Name, for example My Test App
  • An Organization Name, which unless you have one can be your name
  • A Company Identifier, preferably your Internet domain if you have one

If you like, you can also enter a class prefix that will be applied to all classes you create using the Objective-C file template. This can be a good idea if you want to avoid future name conflicts with third party code, but if this app is only meant for testing a feature, you can leave it blank.

You also need to say which device type your application is for: iPad, iPhone, or both (Universal). Pick iPhone or iPad if you’re testing. You can also pick Universal, but then the template will generate more code, which you probably don’t need if your only purpose is trying a new feature.

All the code examples in this book assume you’re using ARC (Automatic Reference Counting) so make sure that Use Automatic Reference Counting is checked. Also, if you’re not planning on using Storyboards (see Recipe 1-9) or unit tests, be sure that the corresponding options are unchecked. Figure 1-2 shows an example of this configuration.

9781430245995_Fig01-02.jpg

Figure 1-2.  Configuring the project

Finally, click the Next button and then select a folder where the project is stored. Bear in mind that Xcode creates a new folder for the project within the folder you picked, so select the root folder for your projects.

There’s often a good reason to place the project under version control. It allows you to check changes to the code so that you can go back to a previous version if something goes wrong or you just want to see what’s been done. Xcode comes with Git, a well-spread open-source version control system. To initialize it for your project, check the Create local git repository for this project checkbox, as in Figure 1-3.

9781430245995_Fig01-03.jpg

Figure 1-3.  Selecting the parent folder for the project

Now when you click the Create button, an application with an app delegate and a view controller will be generated for you (see Figure 1-4). The setup is complete and you can build and run the application (which of course at this point only shows a blank screen).

9781430245995_Fig01-04.jpg

Figure 1-4.  A basic application with an app delegate and a view controller

Recipe 1-2: Linking a Framework

The iOS operating system is divided into so called frameworks. To use the functionalities of a framework you need to link the corresponding binary to your project. For the UIKit, Foundation and CoreGraphics frameworks Xcode does this automatically when you create a new project. However, many important features and functions reside in frameworks like CoreMotion, CoreData, MapKit, and so on. For those other frameworks you need to follow these steps to add them.

  1. Select the project node (the root node) in the Project navigator panel on the left-side of the Xcode project window. This brings up the Project editor panel.
  2. Select the target in the Targets dock. If you have more than one target, for example, a unit test target, you need to perform these steps for all of them.
  3. Navigate to the Build Phases tab and expand the section called Link Binary With Libraries. There you see a list of the currently linked frameworks.
  4. Click on the Add items (+) button at the bottom of the list. This brings up a list of available frameworks.
  5. Select the framework you want to link and use the Add button to include it (see Figure 1-5).

9781430245995_Fig01-05.jpg

Figure 1-5.  Adding the Core Data framework

Tip  To make it easier to find a particular framework you can use the search field to filter the list.

When you add a framework to your project, a corresponding framework reference node is placed in your project tree (see Figure 1-6). What you may want to do is to drag and drop the node to the Frameworks folder where the other framework references reside. It’s not strictly necessary but helps your project tree stay organized.

9781430245995_Fig01-06.jpg

Figure 1-6.  When adding a framework, a reference node is created at the top of your project tree

Now, to use the functions and classes from within your code you only need to import the framework API (Application Programming Interface.) This is normally done in a header file (.h) within your project, as in this example where we import the CoreData API in the ViewController.h file.

//
//  ViewController.h
//  My Test App
//

#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>

@interface ViewController : UIViewController

@end

Note  If you don’t know the header file for a framework, don’t worry, all framework APIs follow the same pattern, namely #import <FrameworkName/FrameworkName.h>.

With the framework binary linked, and the API imported, you can start using its functions and classes in your code.

Recipe 1-3: Adding a User Interface Control View

iOS provides a number of built-in control views, things such as buttons, labels, text fields, and so on, with which you can compose your user interface. Xcode makes designing user interfaces easy with a built-in editor, Interface Builder. All you need to do is to drag the controls you want from the Object Library and position them the way you want in your view. The editor helps you make a pleasing user interface by snapping to standard spaces.

In this recipe we’ll show you how to add a Round Rect Button to your view. We’ll assume you’ve already created a single-view application in which to try this.

Start by selecting the ViewController.xib file to bring up Interface Builder. Be sure the Utilities View (the panel on the right) is visible. If it isn’t, select the corresponding button in the toolbar (see Figure 1-7).

9781430245995_Fig01-07.jpg

Figure 1-7.  The button to hide or show the Utilities View is located in the upper-right corner of Xcode

Now be sure the Object Library shows in the Utilities View (lower-right corner of Xcode.) Click the Show the Object Library button (see Figure 1-8) if it isn’t.

9781430245995_Fig01-08.jpg

Figure 1-8.  The Object Library contains the built-in user interface controls

Locate Round Rect Button in the Object library and drag it onto the view, as in Figure 1-9.

9781430245995_Fig01-09.jpg

Figure 1-9.  Dragging a Round Rect Button from the Object library

You can change the text either by double-clicking the button or by setting the corresponding attribute in the Attribute inspector as shown in Figure 1-10. In the Attribute inspector you can also change other attributes, such as color or font.

9781430245995_Fig01-10.jpg

Figure 1-10.  Setting the button text in the Attribute inspector

You can now build and run your application. Your button shows but it won’t respond to you tapping it. For this you need to connect it to your code via outlets and actions, which is the topic of the next two recipes.

Recipe 1-4: Creating an Outlet

iOS is built on the Model-View-Controller design pattern. One effect of this is that the views are completely separated from code that operates on the views (the so-called controllers). To reference a view from a view controller you need to create an outlet in your controller and hook it up with the view. This can be accomplished in many different ways but the simplest is to use Xcode’s Assistant editor.

We’ll build on what you did in Recipe 1-3 and create an outlet for the button. Although the referenced view in this example is a button, the steps are the same for any other type of view, be it labels, text fields, table views, and so on.

With Interface Builder active showing your button, click on the Assistant editor button in the upper-right corner of Xcode (see Figure 1-11).

9781430245995_Fig01-11.jpg

Figure 1-11.  The center button in the Editor group activates the Assistant editor

With the Assistant editor active, the edit area is split in two showing Interface Builder on the left and the view controller’s header file on the right. Press and hold the Ctrl while dragging a blue line from the button to the code window. A hint with the text Insert Outlet, Action, or Outlet Collection should appear as in Figure 1-12.

9781430245995_Fig01-12.jpg

Figure 1-12.  Creating an outlet in the assistant editor using Ctrl-drag

Note  Because an outlet is really only a special kind of an Objective-C property, you need to drag the blue line to somewhere it can be declared in code, that is, somewhere between the @interface and @end declarations.

In the dialog that appears (shown in Figure 1-13,) give the outlet a name. This will be the name of the property that you’ll use to reference the button later from your code, so name it accordingly. Be sure that Connection is set to Outlet and that the type is correct (should be UIButton for Round Rect Buttons). Also, because you are using ARC, outlets should always use the Weak storage type.

Note  Although Objective-C properties generally should use the Strong storage type, outlets are an exception. The details are beyond the scope of this book, but briefly the reason has to do with internal memory management and that using Weak spares you from writing certain cleanup code that you otherwise had to write. Throughout this book, we assume that you’re creating your outlets using Weak storage.

9781430245995_Fig01-13.jpg

Figure 1-13.  Configuring an outlet

When you click the Connect button, Xcode creates a property and hooks it up with the button. Your view controller’s header file should now look like in Figure 1-14; the little dot next to the property indicates that it is connected to a view in the .xib file.

9781430245995_Fig01-14.jpg

Figure 1-14.  An outlet property connected to a button in the .xib file

The outlet is now ready and you can reference the button from your code using the property. To demonstrate that, add the following code to the viewDidLoad method in the ViewController.m file:

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [self.myButton setTitle:@"Outlet!" forState:UIControlStateNormal];
}

If you build and run your application, as Figure 1-15 shows, the button’s title should now read ”Outlet!” instead of ”Click Me!”

9781430245995_Fig01-15.jpg

Figure 1-15.  The button title changed from code using an outlet reference

The next step is to make something happen when the button is tapped. This is what actions are for, the topic of the next recipe.

Recipe 1-5: Creating an Action

Actions is the way in which user interface controls notifies your code (usually the view controller) that a user event has occurred; for example when a button has been tapped or a value has been changed. The control responds to such an event by calling the action method you’ve provided.

You will continue to build on what you’ve done in Recipes 1-3 and 1-4. In this recipe you create and connect an action method to receive Touch Up Inside events from the button. You then add code that displays an alert when the user taps the button.

Your Xcode should still be in Assistant editor mode with both the user interface and the header file showing. If not, follow the steps from Recipe 1-4 to make it show.

Now, Ctrl-click and drag the line from the button to the view controller’s @interface section, exactly like as you did when you created the outlet earlier. Only this time, change the connection type to Action as in Figure 1-16.

9781430245995_Fig01-16.jpg

Figure 1-16.  Configuring an action method

When you set the connection type to Action, you’ll notice that the dialog changes to show a different set of attributes than for connection type Outlet. (Compare Figure 1-16 to Figure 1-13.) New attributes are Type, Event, and Arguments. Usually, the default values provided by Xcode are fine but there may be situations where you’d want to change them. Here’s a short description of the three attributes:

  • Type: The type of the sender argument. This can be either the generic type id or the specific type, UIButton in this case. It’s usually a good idea to use the generic type so that you can invoke the action method in other situations and not be forced to provide a UIButton (in this case).
  • Event: This is the event type you want the action method to respond to. The most common events are touch events of various kinds and events that indicate that a value has changed.
  • Arguments: This attribute dictates what arguments the action method shall have. Possible values are
    • None, no argument
    • Sender, which has the type you entered in the Type attribute
    • Sender and Event, which is an object holding additional information about the event that occurred

For the sake of this recipe, leave the attributes at id, Touch Up Inside and Sender, respectively, but enter showAlert as the name.

Note  The convention in iOS is to name actions after what will happen when an event triggers it rather than a name that conveys the event type. So, pick names such as showAlert, playCurrentTrack and shareImage over names like buttonClicked or textChanged.

You finalize the creation of the action by clicking Connect button in the dialog. Xcode then creates an action method in the view controller’s class and hooks it up with the button. Your ViewController.h file should now look like Figure 1-17.

9781430245995_Fig01-17.jpg

Figure 1-17.  An outlet and an action connected to an object in the .xib file

Now you’re ready to implement the behavior you want when the user taps the button. In this case you show an alert view that says hello.

@implementation ViewController

// ...

- (IBAction)showAlert:(id)sender
{
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Testing Actions"
                                              message:@"Hello Brother!"
                                              delegate:nil
                                              cancelButtonTitle:@"Dismiss"
                                              otherButtonTitles:nil];
    [alert show];
}

@end

You can now build and run the application. When you tap the button, you should see your greeting alert as in Figure 1-18.

9781430245995_Fig01-18.jpg

Figure 1-18.  An action method showing an alert when the button is tapped

Sometimes it happens that the code and the .xib files get out of sync with connected outlets and actions. Usually this happens when you remove an action method or an outlet property in your code and replace them with new ones. In those cases you get a runtime error and what you need to do is to remove the connection from Interface Builder. You do this in the Connections inspector. Figure 1-19 shows an example where you’ve ended up with two connected action methods for the same event. You remove the lingering action method by clicking the × icon next to it.

9781430245995_Fig01-19.jpg

Figure 1-19.  A button with two different action methods (showAlert: and sayHello:) connected to the same event

Recipe 1-6: Creating a Class

A common task in iOS programming is to create new classes. Whether your aim is to subclass an existing class, or create a new domain model class to hold your data, you can use the Objective-C class template to generate the necessary files.

In this recipe we’ll show you how to create and add a new class to your project. If you don’t have a suitable project to try this in, create a new single-view application.

In the Project Navigator, select the group folder in which you want the files for your new class. Normally this is the group folder with the same name as your project, but as your application grows you may want to organize your files into sub folders.

Go to the main menu and select File image New image File . . . (or simply use the keyboard shortcut Command (image) + N). Then select the Objective-C class template in the iOS Cocoa Touch section (see Figure 1-20).

9781430245995_Fig01-20.jpg

Figure 1-20.  Using the Objective-C class template to create a new class

Then on the following page, name the class. The convention in Objective-C is to name classes using the PascalCase style. You’ll also need to declare the parent class. If you just want a basic class, for example an internal domain class, you should subclass the NSObject class. It is the base class for all objects in Objective-C.

For the sake of this recipe, enter MyClass and NSObject (Figure 1-21

9781430245995_Fig01-21.jpg

Figure 1-21.  Configuring a new class

Note  Depending on which class you select as parent, you may or may not set additional settings such as Targeted for iPad, or With XIB for user interface. These options are active if you subclass a view controller of some kind.

The next step is to select a physical location on the hard disk and a logical location within your project for your new class. That is, the file folder and the group folder. In this step (see Figure 1-22), you can also decide whether your class should be included in the target (that is, the executable file). This is usually what you want, but there may be situations when you want to exclude files. For example, when you have more than one target (maybe a unit test target).

9781430245995_Fig01-22.jpg

Figure 1-22.  Selecting the physical (file folder) and logical (group folder) places for a class

Most of the time you can just accept the default values for the locations, so go ahead and click Create. Xcode then generates two new files to your project: MyClass.h and MyClass.m. They contain the code of an empty class, as in this header file:

//
//  MyClass.h
//  My App
//

#import <Foundation/Foundation.h>

@interface MyClass : NSObject

@end

And this implementation file:

//
//  MyClass.m
//  My App
//

#import "MyClass.h"

@implementation MyClass

@end

Recipe 1-7: Adding an Info.plist Property

The iOS platform uses a special file called Info.plist to store application-wide properties. The file resides in the Supporting File folder of your project and is named after your project with -Info.plist as suffix. The format of the file is XML (eXtensible Markup Language) but you can more conveniently edit the values in Xcode’s property list editor, shown in Figure 1-23.

9781430245995_Fig01-23.jpg

Figure 1-23.  The .plist editor in Xcode

The structure of a property list file is that the root element is a dictionary that contains values identified by string keys. The values are often a string but can be other types, such as Booleans, dates, arrays of values, or even dictionaries.

If you select the Info.plist in the Project navigator, you see that it already contains several items. These are the most commonly used keys. However, sometimes you need to add a value that isn’t contained by default. For example, if your app is using location services and you want to set the NSLocationUsageDescription property.

Follow these steps to add a new application property key and value:

  1. Expand the Supporting Files folder in the Project navigator.
  2. Select the file <Application Name>−Info.plist. This brings up the property list editor.
  3. Select the root item, called Information Property List.
  4. Press the Return key. Xcode adds a new row to the dictionary.
  5. Type the property’s key identifier or select one from the list that is presented to you. Note that if you enter an identifier and Xcode recognizes it as a standard property, it displays a more descriptive key. For example, NSLocationUsageDescription changes into Privacy – Location Usage Description after pressing Return. Behind the scenes, though, it’s the identifier you typed in that’s stored.
  6. If the property key isn’t defined within iOS, that is, it is your own custom key, you are allowed to change the property type. You do this by simply clicking on the type in the Type column and a list of possible values is presented to you.
  7. Enter a value for the key by double-clicking the value column of the new row and typing the new value.

Recipe 1-8: Adding a Resource File

Most apps need to access resource files such as images or sound files. You do that by adding them to your project and then referencing them through their names. In this recipe you add an image file to your project and then use it to populate an image view. Although you use an image file in this example, the process is the same for any other type of file.

As usual, you need a single-view project to try this in, so go ahead and create one if you don’t have a suitable one already.

The best way to import a file is to simply drag it from Finder, or iPhoto, or any other application that supports the dragging of files. Drag an image file of your liking into the Project Navigator in Xcode. A good place to put resource files is in the Supporting Files group folder, but you can add it to any group folder within your project.

Note  You can also use the File image Add Files to My App menu item to add resource files to your project.

In the dialog that appears, be sure to check the box Copy items into destination group’s folder (if needed), as Figure 1-24 shows. This ensures that your image stays with the project even if you move it to a different location.

9781430245995_Fig01-24.jpg

Figure 1-24.  Making sure the Copy items box is checked when adding files

Your image, as Figure 1-25 shows, is now part of your project and can be referenced through its filename.

9781430245995_Fig01-25.jpg

Figure 1-25.  An application with an embedded image file

To see how you can reference the image within your application, add to your user interface an Image View and make it fill the entire view. Be sure the image view is selected and go to the Attributes inspector to connect it to your image file. You do that by selecting your file from the Image attribute’s drop-down menu. You probably also want to change the Mode attribute to Aspect Fill, or your image may look stretched.

Your app should now resemble the one in Figure 1-26.

9781430245995_Fig01-26.jpg

Figure 1-26.  A user interface with an image view referencing an embedded image file

Recipe 1-9: Using Storyboards

Remember the days when you had to use paper and pen to sketch out design flows for your apps? Then came flowcharting software, in which you could digitally record your workflows and processes. But it was a manual process to convert those workflows into source code. Apple provides a tool called Storyboards that offers a visual representation of an app’s workflow, which can produce a working framework for your app.

In this recipe you’ll use Storyboards to build a simple multipage application that contains information about a made-up app-making company. We’ll also show you how you can embed the storyboard to an existing application, making it show when you tap an About button.

So What’s in a Story(board)?

A storyboard is a collection of .xib files packaged together along with some metadata about the views and their relationships to each other. It is the ultimate separation of views from models and controllers that you have been hearing about since the early days of MVC (Model-View-Controller) programming. The storyboard has two main components: scenes and segues.

Scenes

Scenes are any view that fills the screen of the device. They contain UI objects and are controlled by view controllers. This is almost exactly like the .xib files that you are familiar with editing in Interface Builder. Figure 1-27 displays five different scenes in the storyboard that you will soon build.

9781430245995_Fig01-27.jpg

Figure 1-27.  A storyboard with five scenes

Segues

Segues are the transitions that present subsequent views in a storyboard. The segue can present a view with a push, as a modal view, as a pop-over, or with a custom transition. A segue is of the class UIStoryboardSegue and contains three properties: sourceViewController, destinationViewController, and identifier. The identifier is an NSString that can be used to identify specific segues from your code.

You would normally initiate a segue based on an action from the user. This can be the touching of a button or tableview cell, or it could be the result of a gesture recognizer. Segues are represented on the storyboard by a line connecting two scenes, as shown in Figure 1-28.

9781430245995_Fig01-28.jpg

Figure 1-28.  A segue connecting two scenes

Setting Up the Application with a Storyboard

Storyboards are available in all the application templates in Xcode except the empty project template. For this recipe you’ll use the Single View Application template. Name the project About Us and be sure to the Use Storyboards option is checked, as demonstrated in Figure 1-29.

9781430245995_Fig01-29.jpg

Figure 1-29.  Configuring a project for storyboard use

After you’ve created your project, you’ll see in the Project Navigator, as Figure 1-30 demonstrates, that your project contains a file named MainStoryboard.storyboard. Click on this file to load it into Interface Builder and start building your storyboard.

9781430245995_Fig01-30.jpg

Figure 1-30.  A project with a storyboard file

In this example you build a simple project that displays information about your company. It uses a navigation bar to display the title of the current page so the first thing you’re going to do is to embed the main view in a navigation controller. You do this by selecting the view and then selecting Editor image Embed In image Navigation Controller in the main menu. This creates a navigation controller and adds it to your storyboard, connected to your main view through a segue. Figure 1-31 shows an example.

9781430245995_Fig01-31.jpg

Figure 1-31.  The main view in a storyboard, embedded in a view controller

Now create the user interface of the main view. Add a label, a text view, and two buttons to it and make it resemble Figure 1-32.

9781430245995_Fig01-32.jpg

Figure 1-32.  A simple user interface to display company information

Adding a New Scene to the Storyboard

The next step is to add a new scene that displays the company contact information when the Contact Us button is tapped. You do this by dragging a View Controller from the Object library onto the storyboard.

Set up the user interface of the new scene so that it resembles the view on the right in Figure 1-33.

9781430245995_Fig01-33.jpg

Figure 1-33.  The storyboard with a new scene for contact information

To connect the new contact information view to the About Us view, you are going to Ctrl-click the Contact Us button and drag a line to the Contact Info view, as in Figure 1-34. This is the same action used to connect outlets, only this time you’re using it to create a transition between two scenes.

9781430245995_Fig01-34.jpg

Figure 1-34.  Pressing and holding the Ctrl key while dragging a line between the button and the scene creates a transition

When you release the mouse button, a pop-up will display, asking how you want the transition to be performed. You can choose between push, modal and custom. Because you are using a Navigation Controller, select push (as in Figure 1-35) so that a back button is be automatically setup for you in the navigation bar.

9781430245995_Fig01-35.jpg

Figure 1-35.  Selecting push action segue

After the connection is made, you’ll notice that the navigation bar is automatically added to the new view. Now, let’s make the navigation bar display the title of the current scene. One way to do that is to select the Navigation Item in the View Controller navigator (see Figure 1-36). (Another way is to simply click on the navigation bar.)

9781430245995_Fig01-36.jpg

Figure 1-36.  Selecting the Navigation Item of a Storyboard View Controller

You can then set the Title attribute in the Attribute inspector, as shown in Figure 1-37. Set the title of the main view controller to About Us. Then select the view containing the contact information and set its Navigation item’s title to Contact Info.

9781430245995_Fig01-37.jpg

Figure 1-37.  Setting the title of a navigation item

One habit to get into is providing your segues with an identifier. This helps future-proof your apps if you end up connecting multiple segues to one view. You can to check the identifier of the calling segue to see the path the user took to reach that view and respond accordingly. You can set the identifier of a segue by selecting it in the storyboard and viewing its properties in the Attributes inspector, as shown in Figure 1-38.

9781430245995_Fig01-38.jpg

Figure 1-38.  Setting a segue identifier in the Attributes inspector

If you run this app now, as shown by Figures 1-39 and 1-40, the Contact Us button will work and will display the Contact Info view. Note that this works without having to write one single line of code!

9781430245995_Fig01-39.jpg

Figure 1-39.  Main simulated view

9781430245995_Fig01-40.jpg

Figure 1-40.  The resulting view when Contact Us has been tapped

Adding a Table View Scene

Now let’s shift focus to the Our Apps button. What you want to do is to display a view that lists the company’s other apps so they can get some cross promotion. The first thing that should come to mind when talking about lists in iOS is UITableViewController. And storyboarding takes UITableViewController to a whole new level of convenience.

You’re going to drag a UITableViewController to the storyboard, creating an Apps Table view. The first thing that you may notice is that this looks a little different than the regular UITableViewController available in Interface Builder. (Refer to Chapter 3 for an extensive introduction to table views as they are used outside Storyboards.)

In a table view scene there is a section called Prototype Cells at the top (see Figure 1-41). With storyboards, you can customize the layout and objects of a UITableViewCell with something called a prototype. We’ll go into this further later on.

9781430245995_Fig01-41.jpg

Figure 1-41.  A Table View Controller scene

Select the Table View, and in the Attributes inspector, change the table view’s Content attribute from Dynamic Prototypes to Static Cells. Also, change the Style attribute to Grouped, which gives the cell group nice rounded edges. Figure 1-42 shows these settings and the resulting table view.

9781430245995_Fig01-42.jpg

Figure 1-42.  A table view scene with Content set to Static Cells and Style to Grouped

Because every cell is going to have the same layout, delete the bottom two cells so that you can customize and then quickly duplicate the top cell. You can customize the cell like any other container view, by dragging objects from the Object library. We’re going to make a simple customization using only two labels, a bold text label for the title and a smaller one for the subtitle. If you feel up to it, go ahead and add additional components, for example, an image view for an app icon. This example stops at the two labels, as shown in Figure 1-43.

9781430245995_Fig01-43.jpg

Figure 1-43.  A customized table view cell

Now you’ll duplicate the cell to create three instances of it. Do that by clicking and holding the Alt key Figure01.jpg, while dragging the cell downward to duplicate it. Repeat again to add a third row to the table view. Now you can customize the two new cells to contain unique information, resembling Figure 1-44.

9781430245995_Fig01-44.jpg

Figure 1-44.  A table view with three custom cells

All that is left is to connect the Our Apps button to this new view. Select the button in the About Us view, and Ctrl-click-drag to the table view scene you just created to set up a push segue between the Our Apps button and the table view scene. Your storyboard now looks something like that shown in Figure 1-45.

9781430245995_Fig01-45.jpg

Figure 1-45.  A storyboard with three scenes

Note  What you may discern from Figure 1-45 is that Storyboards require a lot of screen space. As your app grows it can get quite difficult to work effectively with the user interface, especially so for iPad apps. This is one of the reasons why some developers, despite all the advantages that come with Storyboards, prefer to stick with .xib files to keep designing the user interfaces individually.

Now when you run the application, you can tap the Our Apps button and the table view scene will show as in Figure 1-46—still without having written any code. Amazing!

9781430245995_Fig01-46.jpg

Figure 1-46.  The table view scene showing a list of “awesome” apps

Adding a Detail View

The previous app segment works well enough without any code, but just by adding a little bit of code behind the scenes you can create an even more powerful interface, in a very short period of time.

When you see a table view, you instinctively know that there is likely to be a detailed view waiting for you when you touch one of the cells. Let’s add that detail view now. Drag and drop a new view controller onto the storyboard. Make it look like Figure 1-47 by adding a label for the title and a text view for the description text. In a real scenario, this page would likely contain additional information like a link to a web page from where you can purchase the app in question. However, we’re going to keep this example as simple as possible and just show the name and the description.

9781430245995_Fig01-47.jpg

Figure 1-47.  A detail view user interface

You want each of the table view cells to segue to this view when touched, so Ctrl-drag the line from each of the three cells to the detail view. This time when you release the mouse button, you’ll notice that you can choose between setting up a Selection Segue or an Accessory Action. (See Figure 1-48.) For the purpose of this recipe, though, create push selection segues that will trigger a transition when the cell is selected (as opposed to when its accessory button is tapped).

9781430245995_Fig01-48.jpg

Figure 1-48.  A pop-up with options to create either a Selection Segue or an Accessory Button Action for a table view cell

Again, once you’ve connected the cells to the detail view using the push method, the view will display the navigator bar. In the same way you did before, set the title in the corresponding Navigator Item to App Details.

As Figure 1-49 shows, there should now be three segues connecting the table view to the app details scene.

9781430245995_Fig01-49.jpg

Figure 1-49.  Multiple segues connecting a table view to a detail view

Select the first segue and enter an identifier for it in the Attributes inspector. Set it to PushAppDetailsFromCell1 as shown in Figure 1-50.

9781430245995_Fig01-50.jpg

Figure 1-50.  Setting an identifier for a segue that’s pushing a detail view

Repeat the process for the other two segues, and set their identifier to PushAppDetailsFromCell2 and PushAppDetailsFromCell3, respectively. You’ll use these identifiers later to identify which segue triggered a transition to the App Details scene.

You’ve gotten this far without using any code, but that convenience is about to end. You need to start generating some dynamic content on the App Details view controller, and you are going to need to dive into some code for that.

First, you are going to create a model class to hold information about your apps. Create a new class, a subclass of NSObject, with the name AppDetails. (Refer to Recipe 1-6 on how to create a new class.)

Add the following properties and init method declarations to the header file of the new class:

//
//  AppDetails.h
//  About Us
//

#import <Foundation/Foundation.h>
@interface AppDetails : NSObject

@property(strong, nonatomic) NSString *name;
@property(strong, nonatomic) NSString *description;

-(id)initWithName:(NSString *)name description:(NSString *)descr;

@end

The implementation of the initWithName:description: method goes into the AppDetails.m file, like so:

//
//  AppDetails.m
//  About Us
//

#import "AppDetails.h"

@implementation AppDetails

-(id)initWithName:(NSString *)name description:(NSString *)descr
{
    self = [super init];
    if (self)
    {
        self.name = name;
        self.description = descr;
    }
    return self;
}

@end

Setting Up a Custom View Controller

With the data object in place, you can make the detail view controller display the attributes of an AppDetails object. For that, you need to attach a custom view controller class to the detail view.

To set up the custom view controller, create a new Objective-C class with the name AppDetailsViewController and make it a subclass of UIViewController. Be sure to uncheck the With XIB for user interface option because this class is used to control a storyboard view, you don’t need a .xib file to go with it.

The next step is to attach the new view controller class to the App Details scene. Go back to the storyboard editor and select the view controller object at the bottom of the App Details scene (see Figure 1-51).

9781430245995_Fig01-51.jpg

Figure 1-51.  Selecting the view controller object of a scene

With the view controller object selected, go to the Identity inspector and set the Class attribute to AppDetailsViewController, as Figure 1-52 demonstrates.

9781430245995_Fig01-52.jpg

Figure 1-52.  Attaching a custom view controller class to a scene

The next step is to create outlets for the name label and the description text view. You do this the same way as with .xib files, using the Assistant Editor (see Recipe 1-4.) Name the outlets nameLabel and descriptionTextView, respectively.

You also need a property to hold the current app details object. Add the following code to your AppDetailsViewController.h file:

//
//  AppDetailsViewController.h
//  About Us
//

#import <UIKit/UIKit.h>
#import "AppDetails.h"

@interface AppDetailsViewController : UIViewController

@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) IBOutlet UITextView *descriptionTextView;
@property (strong, nonatomic) AppDetails *appDetails;

@end

And in AppDetailsViewController.m, add the following code to the viewDidLoad method:

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.nameLabel.text = self.appDetails.name;
    self.descriptionTextView.text = self.appDetails.description;
}

The App Details scene is now ready to accept an AppDetails object and populate its label and text view with data from it. What’s left is to provide the scene with an appropriate object. For this you need to create yet another custom view controller class, this time for the table view scene.

So, as you did before with the App Details scene, create a new Objective-C class. This time make it a subclass of UITableViewController and name it OurAppsTableViewController. Attach the new class to the Our Apps scene by selecting its view controller object and in the Identity inspector set OurAppsTableViewController as its class.

Open the new OurAppsTableViewController.m file. The first thing you’re going to do is to get rid of the existing table view datasource and delegate methods that are there by default. This is because you’re using a static table view content defined in your storyboard. So delete or comment out the following methods:

  • numberOfSectionsInTableView:
  • tableView:numberOfRowsInSection:
  • tableView:cellForRowAtIndexPath:
  • tableView:didSelectRowAtIndexPath:

Now what you want to do is to intersect when one of the three segues are triggered to perform a transition to the App Details scene. This can be done by overriding the prepareForSegue:sender: method.

Add the following code to OurAppsTableViewController.m:

//
//  OurAppsTableViewController.m
//  About Us
//

#import "OurAppsTableViewController.h"
#import "AppDetailsViewController.h"
#import "AppDetails.h"

@implementation OurAppsTableViewController

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    NSString *name;
    NSString *description;
    if ([segue.identifier isEqualToString:@"PushAppDetailsFromCell1"])
    {
        name = @"Awesome App";
        description = @"Long description of the awesome app...";
    }
    else if ([segue.identifier isEqualToString:@"PushAppDetailsFromCell2"])
    {
        name = @"Even More Awesome App";
        description = @"Long description of the even more awesome app...";
    }
    else if ([segue.identifier isEqualToString:@"PushAppDetailsFromCell3"])
    {
        name = @"The Most Awesome App Ever";
        description = @"Long description of the most awesome app ever seen...";
    }
    else
    {
        return;
    }

    AppDetailsViewController *appDetailsViewController = segue.destinationViewController;
    appDetailsViewController.appDetails =
        [[AppDetails alloc] initWithName:name description:description];
}

// ...

@end

As you can see from the code, you are identifying the segue by its identifier and creating an AppDetails object with information about the corresponding app. You then hand over the object to the view controller for the App Details scene.

If you run your app now, you’ll see that each of the table view cells will show different information in the App Details scene. Figure 1-53 demonstrates a simulated result of this application.

9781430245995_Fig01-53.jpg

Figure 1-53.  Three App Details scenes with information about three apps

Using Cell Prototypes

The app is working as intended up to this point, but what if you add new apps to your inventory? With the current implementation, you would have to update the table view with new cells for each new app item. In this section we show you how you can update the table view dynamically instead, using cell prototypes instead of static cell instances.

You start by changing the table view from static to dynamic. Go back to the Our Apps scene and delete the three rows. This removes the three connected segues as well. Now, select the table view and then in the Attributes inspector, change the Content attribute from Static Cells to Dynamic Prototypes, as shown in Figure 1-54.

9781430245995_Fig01-54.jpg

Figure 1-54.  Changing table view content from Static Cells to Dynamic Prototypes

Next you need a prototype cell that will be the template for the cells you’ll add dynamically later, so go ahead and drag a new table view cell from the Object library onto the table view. You can design the cell the same way you did with the static cells earlier, however you’ll make one change by adding a Disclosure Indicator icon to it. (You do this with the Accessory attribute for the cell.) Figure 1-55 shows the prototype cell.

9781430245995_Fig01-55.jpg

Figure 1-55.  A table view cell prototype with two labels and a disclosure indicator

What you need to do now is to create a custom table view cell class and connect it to the prototype. Create a new Objective-C class, name it AppTableViewCell and make it a subclass of UITableViewCell. Then in the Identity inspector for the prototype cell, change the Class attribute to AppTableViewCell (as shown in Figure 1-56).

9781430245995_Fig01-56.jpg

Figure 1-56.  Attaching a custom class to a prototype cell

You also need to set a Reuse Identifier that will be used later to get instances based on the prototype cell. Go to the Attributes inspector and enter AppCell for the Identifier attribute, as shown by Figure 1-57.

9781430245995_Fig01-57.jpg

Figure 1-57.  Setting a reuse identifier for a cell prototype

Next, create outlets for the name label and the description label in AppTableViewCell.h. Name the outlets nameLabel and descriptionLabel, respectively.

You also need to create a segue for the transition to the App Details scene. Ctrl-drag a line from the prototype cell to the App Details scene. As you did before with the static cells, use the push selection segue type.

Select the segue you just created and set its Identifier attribute to PushAppDetails.

You are now finished with the setup of the prototype cell, what’s left is to write the code for the dynamic displaying of the table view content. Switch to OurAppsTableViewController.h and import the prototype cell class:

#import "AppTableViewCell.h"

Then go to OurAppsTableViewController.m and add the following code:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 3;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    //Set the CellIdentifier that you set in the storyboard
    static NSString *CellIdentifier = @"AppCell";

    AppTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    switch (indexPath.row)
    {
        case 0:
            cell.nameLabel.text = @"Awesome App";
            cell.descriptionLabel.text = @"Long description of the awesome app...";
            break;
        case 1:
            cell.nameLabel.text = @"Even More Awesome App";
            cell.descriptionLabel.text = @"Long description of the even more awesome app...";
            break;
        case 2:
            cell.nameLabel.text = @"The Most Awesome App Ever";
            cell.descriptionLabel.text =
                @"Long description of the most awesome app ever seen...";
            break;

        default:
            cell.nameLabel.text = @"Unkown";
            cell.descriptionLabel.text = @"Unknown";
            break;
    }

    return cell;
}

You can now make the prepareForSegue:sender: method a lot simpler by utilizing the information stored in the cells.

Then change the prepareForSegue:sender: method’s implementation into this:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"PushAppDetails"])
    {
        AppDetailsViewController *appDetailsViewController = segue.destinationViewController;
        AppTableViewCell *cell = sender;
        appDetailsViewController.appDetails =
            [[AppDetails alloc] initWithName:cell.nameLabel.text
                                 description:cell.descriptionLabel.text];
    }
}

Now when you run the app, the table view loads as before using the one prototype cell and the datasource. Figure 1-58 shows the simulated result of your newest updates to your application. In this example you are still using static data for the AppDetails class, but this app could easily be extended to use a core data object model or even pull a list of apps from a remote file on your server. Those features will be covered more in Chapter 12.

9781430245995_Fig01-58.jpg

Figure 1-58.  The same table view loading its content through its view controller using a prototype cell

Recipe 1-10: Handling Errors

How well your application is handling errors may very well be the difference between success and failure in terms of user experience. Nevertheless error handling is an often neglected part of software development, and iOS developers are no exception. Apps that crash without explanation or fail silently with all sorts of weird behavior are not uncommon.

Handling errors is something you should spend a little extra effort on to get right, and like all things boring you’re better off dealing with them straight away. One thing you can do to make the effort easier is to create a default error handler, a single piece of code that can handle most of your errors.

This recipe shows you how to set up a simple error handler that, based on the error object you provide, alerts the user and even provides him or her with recovery options when available.

Setting Up a Framework for Error Handling

By convention in iOS, fatal, non-recoverable errors are implemented using exceptions. For potentially recoverable errors, however, the idiom is to use a more traditional method with a Boolean return value and an out parameter containing the error details. Here’s an example of this pattern, with Core Data’s NSManagedObjectContext:save: method:

NSError *error = nil;
if ([managedObjectContext save: &error] == NO)
{
    NSLog(@"Unhandled error: %@, %@", error, [error userInfo]);
}

The error handling in the preceding example dumps the error details to the standard output and then silently carries on as if nothing happened; Obviously, this approach is rather poor and is not the best strategy from a usability perspective.

What you do in this recipe, is build a small framework that helps you handle errors with the same ease as here, but with a much better user experience. Specifically, you’ll write code like this:

NSError *error = nil;
if ([managedObjectContext save:&error] == NO)
{
    [ErrorHandler handleError:error fatal:NO];
}

Let’s set up the scaffolding for the internal error handling framework. Start by creating a new single-view application project. In this new project, create a new NSObject subclass with the name ErrorHandler. Open ErrorHandler.h and add the following declaration:

//
//  ErrorHandler.h
//  Recipe 1-10: Default Error Handling
//

#import <Foundation/Foundation.h>

@interface ErrorHandler : NSObject

+(void)handleError:(NSError *)error fatal:(BOOL)fatalError;

@end

For now, just add an empty stub of this class method in the ErrorHandler.m file, like so:

//
//  ErrorHandler.m
//  Recipe 1-10: Default Error Handling
//

#import "ErrorHandler.h"

@implementation ErrorHandler

+(void)handleError:(NSError *)error fatal:(BOOL)fatalError
{
    // TODO: Handle the error
}

@end

You’ll start implementing this method in a minute, but first you’re going to set up code that will test it. You’re going to create a simple user interface with two buttons that fake a non-fatal and a fatal error, respectively.

Open ViewController.xib and drag out two buttons from the Object library and make them resemble the ones in Figure 1-59.

9781430245995_Fig01-59.jpg

Figure 1-59.  Buttons that fake an error and invoke the error handling method

Create actions named fakeNonFatalError and fakeFatalError for the respective buttons. Also, import ErrorHandler.h in the view controller’s header file. ViewController.h should now resemble this code:

//
//  ViewController.h
//  Recipe 1-10: Default Error Handling
//

#import <UIKit/UIKit.h>
#import "ErrorHandler.h"

@interface ViewController : UIViewController

- (IBAction)fakeNonFatalError:(id)sender;
- (IBAction)fakeFatalError:(id)sender;

@end

Now, switch to ViewController.m and add the following implementation for the fakeNonFatalError: action method. It simply creates a fake NSError object with a single recovery option (Retry). The error object is then handed over to the error handler class, with a flag saying it’s not a fatal error (meaning it shouldn’t cause the app to shut itself down):

- (IBAction)fakeNonFatalError:(id)sender
{
    NSString *description = @"Connection Error";
    NSString *failureReason = @"Can't seem to get a connection.";
    NSArray *recoveryOptions = @[@"Retry"];
    NSString *recoverySuggestion = @"Check your wifi settings and retry.";

    NSDictionary *userInfo =
        [NSDictionary dictionaryWithObjects:
            @[description, failureReason, recoveryOptions, recoverySuggestion, self]
        forKeys:
            @[NSLocalizedDescriptionKey,NSLocalizedFailureReasonErrorKey,
              NSLocalizedRecoveryOptionsErrorKey, NSLocalizedRecoverySuggestionErrorKey,
              NSRecoveryAttempterErrorKey]];

    NSError *error = [[NSError alloc] initWithDomain:@"com.hans-eric.ios6recipesbook" code:42
        userInfo:userInfo];

    [ErrorHandler handleError:error fatal:NO];
}

For now, you don’t need to know the details of the preceding method, but the userInfo dictionary that you’ve built for this fake error contains the key NSRecoveryAttempterErrorKey with the value set to self. What this means, is that the view controller acts as the Recovery attempter object for this error. For reasons that will become clear later, you need to implement one method from the NSRecoveryAttempting protocol, namely attemptRecoveryFromError:optionIndex:. This method is invoked on the recovery attempts that you implement later. For the purpose of this recipe, you simply fake a failed recovery attempt by returning NO, as shown here:

- (BOOL)attemptRecoveryFromError:(NSError *)error optionIndex:(NSUInteger)recoveryOptionIndex
{
    return NO;
}

Next, you implement the fakeFatalError: action method. It creates a simpler error, without recovery options, but flags the error as fatal in the error handling method. Here’s the implementation:

- (IBAction)fakeFatalError:(id)sender
{
    NSString *description = @"Data Error";
    NSString *failureReason = @"Data is corrupt. The app must shut down.";
    NSString *recoverySuggestion = @"Contact support!";

    NSDictionary *userInfo = [NSDictionary dictionaryWithObjects:
            @[description, failureReason]
        forKeys:
            @[NSLocalizedDescriptionKey, NSLocalizedFailureReasonErrorKey]];

    NSError *error = [[NSError alloc] initWithDomain:@"com.hans-eric.ios6recipesbook"
                                                code:22 userInfo:userInfo];

    [ErrorHandler handleError:error fatal:YES];
}

Now that you have the interface of the default error handling API set up and the ways to test it, you can start implementing the actual error handling.

Notifying the User

The very least a decent default error handling method should do, is to notify the user that an error has occurred. Switch back to the ErrorHandler.m file and add the following code to the handleError:fatal: method:

+(void)handleError:(NSError *)error fatal:(BOOL)fatalError
{
    NSString *localizedCancelTitle = NSLocalizedString(@"Dismiss", nil);
    if (fatalError)
        localizedCancelTitle = NSLocalizedString(@"Shut Down", nil);

    // Notify the user
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:[error localizedDescription]
                                                    message:[error localizedFailureReason]
                                                   delegate:nil
                                          cancelButtonTitle:localizedCancelTitle
                                          otherButtonTitles:nil];

    [alert show];
    // Log to standard out
    NSLog(@"Unhandled error: %@, %@", error, [error userInfo]);
}

You can now build and run the application to see how this will look. Try tapping the non-fatal error button first, which should display an alert like the one in Figure 1-60.

9781430245995_Fig01-60.jpg

Figure 1-60.  An error alert with a fake error

If you tap the button that fakes a fatal error, you’ll get a similar alert. However, what you want for errors flagged as fatal, is that the app is shut down automatically after the user has been notified. Let’s implement that now.

What you’re going to do is to provide the alert view with a delegate. That way you can intercept when the user dismisses the alert view, so that you can abort the execution there. Add the following code to the handleError:fatal: method. At this point it does not compile, but don’t worry, you sort it out in a minute:

+(void)handleError:(NSError *)error fatal:(BOOL)fatalError
{
    NSString *localizedCancelTitle = NSLocalizedString(@"Dismiss", nil);
    if (fatalError)
        localizedCancelTitle = NSLocalizedString(@"Shut Down", nil);

    // Notify the user
    ErrorHandler *delegate = [[ErrorHandler alloc] initWithError:error fatal:fatalError];
    if (!retainedDelegates) {
        retainedDelegates = [[NSMutableArray alloc] init];
    }
    [retainedDelegates addObject:delegate];

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:[error localizedDescription]
                                                    message:[error localizedFailureReason]
                                                   delegate: delegate
                                          cancelButtonTitle:localizedCancelTitle
                                          otherButtonTitles:nil];

    [alert show];
    // Log to standard out
    NSLog(@"Unhandled error: %@, %@", error, [error userInfo]);
}

The retainedDelegates hack deserves some explanation. The delegate property of an alert view is a so-called weak reference. What this means is that it will not keep the delegate from deallocating if all other references to it are released.

To keep the delegate from being prematurely deallocated (by ARC) you’ll use a static array. As long as a delegate is a member of the retainedDelegates array, a strong reference is kept that will keep the delegate alive. As you’ll see later, once the delegate’s job is finished it will remove itself from the array and allow it to become deallocated.

Add the actual declaration at the top of the @implementation block of the ErrorHandler class, like so

//
//  ErrorHandler.m
//  Recipe 1-10: Default Error Handling
//

#import "ErrorHandler.h"

@implementation ErrorHandler

static NSMutableArray *retainedDelegates = nil;

// ...

@end

Now, go to the ErrorHandler.h file again, and add the following code

//
//  ErrorHandler.h
//  Recipe 1-10: Default Error Handling
//

#import <Foundation/Foundation.h>

@interface ErrorHandler : NSObject <UIAlertViewDelegate>

@property (strong, nonatomic)NSError *error;
@property (nonatomic)BOOL fatalError;

-(id)initWithError:(NSError *)error fatal:(BOOL)fatalError;

+(void)handleError:(NSError *)error fatal:(BOOL)fatalError;

@end

What you’ve done now is to turn the ErrorHandler class into an alert view delegate. You could of course have created a new class for this purpose, but this is a bit easier. Now, switch to ErrorHandler.m and implement the initWithError: method:

-(id)initWithError:(NSError *)error fatal:(BOOL)fatalError
{
    self = [super init];
    if (self) {
        self.error = error;
        self.fatalError = fatalError;
    }
    return self;
}

Finally, implement the clickedButtonAtIndex: delegate method to abort in case of a fatal error. As you can see from the code that follows, you also release the delegate by removing it from the retainedDelegates array:

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (self.fatalError) {
        // In case of a fatal error, abort execution
        abort();
    }
    // Job is finished, release this delegate
    [retainedDelegates removeObject:self];
}

If you re-run the app now and tap the Fake a Fatal Error button, the app will abort execution right after you’ve dismissed the error message by tapping the Shut Down button (see Figure 1-61).

9781430245995_Fig01-61.jpg

Figure 1-61.  An alert view displaying a fake fatal error

Notifying the user and logging the error is the least we should do when handling an unexpected error so the code you’ve implemented so far is a good candidate for a default error handling method. However, there is one more feature of the NSError class that you’d want to support: Recovery options.

Implementing Recovery Options

NSError offers a way for the notifying method to provide custom recovery options. For example, a method that is called to establish a connection of some kind may provide a ”Retry” option in case of a timeout failure.

The localizedRecoveryOptions array of an NSObject holds the titles of available recovery options defined by the callee method. Additionally, the localizedRecoverySuggestion property can be used to give the user an idea of how to handle the error. The option titles as well as the recovery suggestion are suitable for communicating directly with the user, for example in an alert view.

The last piece of the NSError recovery functionality is the recoveryAttempter property. It references an object that conforms to the NSErrorRecoveryAttempting informal protocol, which is what you will use to invoke a particular recovery action.

Let’s integrate this information in the handleError:fatalError: method:

+(void)handleError:(NSError *)error fatal:(BOOL)fatalError
{
    NSString *localizedCancelTitle = NSLocalizedString(@"Dismiss", nil);
    if (fatalError)
        localizedCancelTitle = NSLocalizedString(@"Shut Down", nil);

    // Notify the user
    ErrorHandler *delegate = [[ErrorHandler alloc] initWithError:error fatal:fatalError];
    if (!retainedDelegates) {
        retainedDelegates = [[NSMutableArray alloc] init];
    }
    [retainedDelegates addObject:delegate];

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:[error localizedDescription]
                                                    message:[error localizedFailureReason]
                                                   delegate:delegate
                                          cancelButtonTitle:localizedCancelTitle
                                          otherButtonTitles:nil];

    if ([error recoveryAttempter])
    {
        // Append the recovery suggestion to the error message
        alert.message = [NSString stringWithFormat:@"%@ %@", alert.message,
            error.localizedRecoverySuggestion];
        // Add buttons for the recovery options
        for (NSString * option in error.localizedRecoveryOptions)
        {
            [alert addButtonWithTitle:option];
        }
    }

    [alert show];
    // Log to standard out
    NSLog(@"Unhandled error: %@, %@", error, [error userInfo]);
}

You implement the actual recovery attempts in the alertView:clickedButtonAtIndex: method, by making the following changes to it:

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex != [alertView cancelButtonIndex])
    {
        NSString *buttonTitle = [alertView buttonTitleAtIndex:buttonIndex];
        NSInteger recoveryIndex = [[self.error localizedRecoveryOptions]
                                   indexOfObject:buttonTitle];
        if (recoveryIndex != NSNotFound)
        {
            if ([[self.error recoveryAttempter] attemptRecoveryFromError:self.error
                                                             optionIndex:recoveryIndex] == NO)
            {
                // Redisplay alert since recovery attempt failed
                [ErrorHandler handleError:self.error fatal:self.fatalError];
            }
        }
    }
    else
    {
        // Cancel button clicked

        if (self.fatalError)
        {
            // In case of a fatal error, abort execution
            abort();
        }
    }

    // Job is finished, release this delegate
    [retainedDelegates removeObject:self];
}

You’re now finished with the default error handling method. To test this last feature, build and run the app and tap the Non-Fatal Error button again. This time the error alert resembles the one in Figure 1-62.

9781430245995_Fig01-62.jpg

Figure 1-62.  An error alert with a Retry option

Now you have a convenient default error handling method that extracts information from an NSError object and does the following:

  1. Alerts the user.
  2. If available, provides the user with recovery options.
  3. Logs the error to the standard error output.

In the next recipe we show you how you can handle exceptions with the same ease.

Recipe 1-11: Handling Exceptions

Exceptions in iOS are by convention reserved for unrecoverable errors. When an exception is raised, it should ultimately result in program termination. For that reason iOS apps rarely catch exceptions internally but let the default exception handling method deal with them.

The default exception handling method catches any uncaught exceptions, writes some debug information to the console and then terminates the program. Although this is the safe way to deal with iOS exceptions, there is a severe usability problem attached to this approach. Because the console can’t be seen from a real device, the app just disappears at the hands of the user—with no explanation whatsoever.

This recipe shows you how you can make the user experience a little better while keeping a reasonably safe exception handling approach that will work in most situations.

A Strategy for Handling Exceptions

As a general solution, terminating the app on uncaught exceptions is a good strategy since it’s safe and will prevent bad things like data corruption from happening. However, we’d like to make a couple of improvements to the default exception handling of iOS.

The first thing we want to do is to notify the user about the uncaught exception. From a user’s point of view this is best done at the time of the exception, just before the program terminates. However, the program may then be operating in a hostile environment due to fatal errors like out-of-memory or pointer errors. In those situations, complex things like user interface programming should be avoided.

A good compromise is therefore to log the error and alert the user on the next app launch. To accomplish this you need to intercept uncaught exceptions and set a flag that persists between the two sessions.

Setting Up a Test Application

Like you did in the previous recipe you’re going to setup a test application to try this out. Start by creating a new single-view application. Then add a button to the main view, make it look something like Figure 1-63.

9781430245995_Fig01-63.jpg

Figure 1-63.  A button to throw a fake exception

Add an action for the button called throwFakeException and add the following implementation to it:

- (IBAction)throwFakeException:(id)sender
{
    NSException *e = [[NSException alloc] initWithName:@"FakeException"
        reason:@"The developer sucks!" userInfo:[NSDictionary dictionaryWithObject:@"Extra info"
        forKey:@"Key"]];
    [e raise];
}

With the test code in place, you can move on to the implementation of the handling of exceptions.

Intercepting Uncaught Exceptions

The way to intercept uncaught exceptions in iOS is to register a handler with the NSSetUncaughtException function. The handler is a void function with an NSException reference as the only parameter, like this one:

void myExceptionHandler(NSException *exception)
{
    // Handle Exceptions
}

The exception handler you’re going to implement should set a flag that informs the app that an exception occurred on the last run. For that you’ll use NSUserDefaults, which is designed for persisting settings between sessions. Now, in the AppDelegate.m file, add the following method:

void exceptionHandler(NSException *exception)
{
    //Set flag
    NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];

    [settings setBool:YES forKey:@"ExceptionOccurredOnLastRun"];
    [settings synchronize];
}

Next, register the exception handler in the application:didFinishLaunchingWithOptions: method, like so:

-(BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSSetUncaughtExceptionHandler(&exceptionHandler);

    // Normal Setup Code
    // ...
}

The next thing you’ll do is to add a check in application:didFinishLaunchingWithOptions: to see if the last session ended in an exception. If that’s the case you’ll reset the flag and notify the user with an alert view. Add this code before the line where you register the exception handler, like so

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Default exception handling code
    NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];

    if ([settings boolForKey:@"ExceptionOccurredOnLastRun"])
    {
        // Reset exception occurred flag
        [settings setBool:NO forKey:@"ExceptionOccurredOnLastRunKey"];
        [settings synchronize];

        // Notify the user
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"We're sorry"
            message:@"An error occurred on the previous run." delegate:nil
            cancelButtonTitle:@"Dismiss" otherButtonTitles:nil];
        [alert show];
    }

    NSSetUncaughtExceptionHandler(&exceptionHandler);

    // ...
}

Now you have the basic structure of your improved default handling method in place. If you run the app now and tap the button to raise the fake exception, your app will terminate. However, if you start it up again you’ll get an alert like the one in Figure 1-64, notifying you of the error that occurred earlier.

9781430245995_Fig01-64.jpg

Figure 1-64.  An error message notifying the user of an error that caused the app to close down on the last run

That’s great, but let’s make this feature a bit more useful.

Reporting Errors

Many uncaught exceptions in iOS apps are pure programming errors, bugs that you can fix if you only get proper information about them. For this reason iOS creates a crash report when an app has terminated due to an uncaught exception.

However, even though a developer can extract the information about crashes, he or she needs to connect the device to a computer to do so. If your app is out there in the hands of real users, retrieving the crash report may be impossible or inconvenient at best.

As an alternative we’d like users to be able to send these error reports directly to us. You can do so by adding an Email Report button to the error alert.

First, you need to store information about the exception so that you can retrieve it on the next run if the user decides to send the error report. A good way to do this is to use the natural source of error logging, the stderr stream.

The stderr stream is the channel to which NSLog sends the log messages. By default this is the console but it’s possible to redirect the stderr stream to a file by using the freopen function. To do that, add the following code to the application:didFinishLaunchingWithOptions: method:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Default exception handling code
    NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];

    if ([settings boolForKey:@"ExceptionOccurredOnLastRun"])
    {
        // Reset exception occurred flag
        [settings setBool:NO forKey:@"ExceptionOccurredOnLastRunKey"];
        [settings synchronize];

        // Notify the user
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"We're sorry" message:@"An error occurred on the previous run." delegate:nil cancelButtonTitle:@"Dismiss" otherButtonTitles:nil];
        [alert show];
    }


    NSSetUncaughtExceptionHandler(&exceptionHandler);

    // Redirect stderr output stream to file
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                                         NSUserDomainMask, YES);
    NSString *documentsPath = [paths objectAtIndex:0];
    NSString *stderrPath = [documentsPath stringByAppendingPathComponent:@"stderr.log"];

    freopen([stderrPath cStringUsingEncoding:NSASCIIStringEncoding], "w", stderr);
    // ...
}

This makes it so that all entries to NSLog are written to a file named stderr.log in the app’s documents directory on the device. The file is re-created (thanks to the "w" argument) for each new session so the file only contains information and error logs from the last run, which is what you want for the error report.

On uncaught exceptions, iOS writes some basic information about the crash to stderr (and thus your file). However, two important pieces of information are not logged, namely the exception’s userInfo dictionary and a symbolized (that is, readable) call stack. Fortunately you can add that information from your exception handling function:

void exceptionHandler(NSException *exception)
{
    NSLog(@"Uncaught exception: %@ Reason: %@ User Info: %@ Call Stack: %@",
          exception.name, exception.reason, exception.userInfo, exception.callStackSymbols);
    //Set flag
    NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];

    [settings setBool:YES forKey:@"ExceptionOccurredOnLastRun"];
    [settings synchronize];
}

Now that you have the exception data persisted, you can move on to add the button with which the user can send the information to you.

Adding the Button

To add an Email Report button, you make two small changes to the code that creates the alert view:

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"We're sorry"
    message:@"An error occurred on the previous run." delegate: self
    cancelButtonTitle:@"Dismiss" otherButtonTitles:nil];
[alert addButtonWithTitle:@"Email a Report"];
[alert show];

You added a button to the alert view and told it to send all events to the AppDelegate object (by declaring self as the delegate). To avoid the compiler warning, you also need to make the AppDelegate an alert view delegate by declaring the UIAlertViewDelegate protocol. To do that, open AppDelegate.h and make the following change:

@interface AppDelegate : UIResponder <UIApplicationDelegate
, UIAlertViewDelegate>
//...

@end

Now you can add the alertView:didDismissWithButtonIndex: alert view delegate method to intercept when a user taps the Email Report button. Go back to AppDelegate.m and add the following code:

-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 1)
    {
        //todo: Email a Report here
    }
}

Before you go on and write the code for the email report, you need to fix a problem with the current code. Because alert views in iOS are displayed asynchronously, the code for redirecting the stderr output is run before application:didDismissWithButtonIndex: is invoked. This erases the content of the file prematurely.

To fix this you have to make two changes.

First, in application:didFinishLaunchingWithOptions: be sure that the exception handling setup code is only run if there has been no exception, by adding the following code:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Default exception handling code
    NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];

    if ([settings boolForKey:@"ExceptionOccurredOnLastRun"])
    {
        // Reset exception occurred flag
        [settings setBool:NO forKey:@"ExceptionOccurredOnLastRunKey"];
        [settings synchronize];

        // Notify the user
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"We're sorry" message:@"An error occurred on the previous run." delegate:self cancelButtonTitle:@"Dismiss" otherButtonTitles:nil];
        [alert addButtonWithTitle:@"Email a Report"];
        [alert show];
    }
    else
    {

        NSSetUncaughtExceptionHandler(&exceptionHandler);

        // Redirect stderr output stream to file
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                                         NSUserDomainMask, YES);
        NSString *documentsPath = [paths objectAtIndex:0];
        NSString *stderrPath = [documentsPath stringByAppendingPathComponent:@"stderr.log"];

        freopen([stderrPath cStringUsingEncoding:NSASCIIStringEncoding], "w", stderr);
    }

    // ...
}

The second thing you need to do is to add the setup code to the alertView:didDismissWithButtonIndex:so that it sets up the exception handling for the other case too, when there has been an exception. Here are the necessary changes:

-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                                         NSUserDomainMask, YES);
    NSString *documentsPath = [paths objectAtIndex:0];
    NSString *stderrPath = [documentsPath stringByAppendingPathComponent:@"stderr.log"];

    if (buttonIndex == 1)
    {
        //todo: Email a Report here
    }

    NSSetUncaughtExceptionHandler(&exceptionHandler);

    // Redirect stderr output stream to file
    freopen([stderrPath cStringUsingEncoding:NSASCIIStringEncoding], "w", stderr);
}

Now you’re ready for the final step: composing an error report email.

Emailing the Report

When the user presses the Email Report button, you’ll use the MFMailComposeViewController class to handle the email. This class is part of the MessageUI framework so go ahead and link it to the project. (Details about how to link framework binaries can be found in Recipe 1-2).

Then you need to import MessageUI.h and MFMailComposeViewController.h to your AppDelegate.h file. Also, because you’ll need to respond to events from the mail view controller, add MFMailComposeViewControllerDelegate to the list of supported protocols. The AppDelegate.h file should now look like this code:

#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>
#import <MessageUI/MFMailComposeViewController.h>

@class ViewController;

@interface AppDelegate : UIResponder <UIApplicationDelegate, UIAlertViewDelegate , MFMailComposeViewControllerDelegate>

// ...

@end

Now you can create and present a MFMailComposeViewController with the error report. Add the following code to the alertView:didDismissWithButtonIndex: method:

-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                                         NSUserDomainMask, YES);
    NSString *documentsPath = [paths objectAtIndex:0];
    NSString *stderrPath = [documentsPath stringByAppendingPathComponent:@"stderr.log"];

    if (buttonIndex == 1)
    {
        // Email a Report
        MFMailComposeViewController *mailComposer = [[MFMailComposeViewController alloc] init];
        mailComposer.mailComposeDelegate = self;
        [mailComposer setSubject:@"Error Report"];
        [mailComposer setToRecipients:[NSArray arrayWithObject:@"[email protected]"]];
        // Attach log file
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                                             NSUserDomainMask, YES);
        NSString *documentsPath = [paths objectAtIndex:0];
        NSString *stderrPath = [documentsPath stringByAppendingPathComponent:@"stderr.log"];
        NSData *data = [NSData dataWithContentsOfFile:stderrPath];
        [mailComposer addAttachmentData:data mimeType:@"Text/XML" fileName:@"stderr.log"];
        UIDevice *device = [UIDevice currentDevice];
        NSString *emailBody =
            [NSString stringWithFormat:@"My Model: %@ My OS: %@ My Version: %@",
                [device model], [device systemName], [device systemVersion]];
        [mailComposer setMessageBody:emailBody isHTML:NO];
        [self.window.rootViewController presentViewController:mailComposer animated:YES
                                                   completion:nil];
    }

    NSSetUncaughtExceptionHandler(&exceptionHandler);

    // Redirect stderr output stream to file
    freopen([stderrPath cStringUsingEncoding:NSASCIIStringEncoding], "w", stderr);
}

To dismiss the mail compose controller you also need to respond to the mailComposeController:didFinishWithResult:error: message, sent by the controller upon send or cancel events:

-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
    [self.window.rootViewController dismissViewControllerAnimated:YES completion:nil];
}

Your app now has a good default exception handling that is both user friendly, useful, and safe. When you test it now, you’ll see that the alert view has an additional button that allows you to send the report through mail. Figure 1-65 shows you an example of this.

9781430245995_Fig01-65.jpg

Figure 1-65.  An error alert with an option to send an error report by mail

A Final Touch

There is one more thing you may want to consider when implementing this recipe. Because you redirect stderr to a file, no error logging is displayed in the output console. This may be a problem during development when you conduct most of your testing in the simulator. During that time, you probably want the error messages to show in the console rather than being stored in a file.

Fortunately, there is a simple solution. You can use the predefined conditional TARGET_IPHONE_SIMULATOR to set up our exception handling code only when the app is run on a real device. Here’s how you do it:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    #if !TARGET_IPHONE_SIMULATOR
    // Default exception handling code

    // ...

    #endif

    // Other Setup Code

    // ...
}

This concludes our default exception handling recipe. By having a well thought-through strategy for handling errors you not only save time for yourself, you also pay the users the respect they deserve. We hope that with the help of these two recipes you have seen the value of good exception handling, and that it’s really not that difficult to achieve. You’ve probably already seen ways in which these simple examples can be improved. Why not venture into that? After all, the sooner you build these features into your app, the more use you’ll have for them.

Recipe 1-12: Adding a Lite Version

Offering a lite version of your app is a great way to give customers a chance to try your app before buying it. Maintaining two code bases however can be quite tiresome and get out of hand as you implement new features into your app. This recipe shows you how you can set up the project to include both versions in one code base.

Adding a Build Target

For this recipe we have created a new Single View Application with the name SampleProject.

Select your project file in the Navigator area, and then select the build target for your project. Now press image+D to duplicate the target. You are prompted to Duplicate Only or Duplicate and Transition to iPad, as shown in Figure 1-66. Click Duplicate Only to create a new target to use for your Lite build. This results in a separate build target with which you can implement a second version.

9781430245995_Fig01-66.jpg

Figure 1-66.  Project duplication options

Rename the new target with an appending “Lite” text by double-clicking the target name. You also want to change the new target’s Product Name attribute to signal that it’s a Lite version. You find the Product Name attribute in the Build Settings tab under the Packaging heading (see Figure 1-67 ).

Tip  The easiest way to find a particular build attribute is to use the search field in the upper-left corner of the Build Settings tab. Just type Product Name and the attributes are filtered out as you type.

9781430245995_Fig01-67.jpg

Figure 1-67.  Appending “Lite” to the target’s name and the Product Name build setting

You can now build and run the lite version. To do that you need to change the active scheme. When you duplicated the regular target earlier, Xcode created a new build scheme for you. This new scheme is named the same as the initial name of the duplicated target, namely SampleProject copy. Even though you have changed the name of the target to SampleProject Lite, Xcode doesn’t change the scheme name accordingly. If this bothers you, you can rename it in the Manage Schemes window, which you can reach from the Active Scheme button (see Figure 1-68).

9781430245995_Fig01-68.jpg

Figure 1-68.  Go to Manage Schemes if you want to change the name of the Lite build scheme

Note  Keep in mind that the two targets must have separate bundle identifiers to show up as separate apps when you install and run them on your device (or simulator). Xcode sets up different bundle identifiers by default, but be careful when making changes.

Coding for a Specific Version

Now you need a way to differentiate between the two builds in your source code. For instance, you may want to limit some features in the Lite version, or show ads to the non-paying users. Some code in the Lite version should not be compiled into the full version and vice versa. This is where Preprocessor Macros come in handy.

What you want to do is to add a preprocessing macro named LITE_VERSION. Again this is done in the Build Settings tab, under the compiler’s preprocessing header. Hover over the Debug macro, click on its add icon (+) that appears next to it and enter LITE_VERSION in the edit field. Be sure to do the same for the Release macro as well. Figure 1-69 shows an example of these changes.

9781430245995_Fig01-69.jpg

Figure 1-69.  Defining the LITE_VERSION conditional in the preprocessor macros

To build different features into your app, you will need to use that preprocessor macro you created. Anywhere in your code that you want to specify different code for your lite version versus the full version, use the following #ifdef directive:

#ifdef LITE_VERSION
// Stuff for Lite version
#else
// Stuff for Full version
#endif

Alternatively, you can use the negated form if that’s more convenient.

#ifndef LITE_VERSION
// Code exclusively for Full version
#endif

Note  You can also control what files are included in each build. For instance, you may not need to include the full version artwork in the lite version. Click your Lite project target and go to the Build Phases tab. Expand the Copy Bundle Resources ribbon, and remove or add any files that are specific to the lite version.

Recipe 1-13: Adding Launch Images

For the sake of user experience it is generally considered bad practice to show a so-called “splash screen” at application startup. The user is normally eager to start playing with your app and not interested in your branding information. Nor are they particularly eager to watch your cool video, regardless of how much time and money you spent on it.

The extra few seconds it takes for the user to view and/or dismiss the splash screen can be quite annoying and may be unfavorable to the overall experience.

However, there is an exception to the rule. Many apps take a little while to load, a few seconds during which it remains unresponsive and uninteresting. Because this may be as bad a splash screen in terms of user experience, your app should do what it can to mitigate the effect. One way of doing this is to display an image that is removed as soon as the launch is finished and the app is ready to take on user actions.

These images are called “launch images” and are really easy to implement in iOS. The function of displaying an image while the app is loading is already built-in. All you have to do is to provide the appropriate images.

Launch Image Files

Depending on the nature of your app and which devices it supports, you’ll need to create one or more launch images. To include a launch image in your app is just a matter of adding it to the project. A simple naming convention then tells iOS if it’s a launch image and of what kind.

Launch images are required to be of the PNG file format, and their filenames begin with the identifier Default (with a capital D) and then optionally followed by orientation, scale, and device specifiers in the following form:

Default [−<orientation>] [@<scale>] [∼<device>] .png

Table 1-1 lists some common Launch image types and their filenames.

Table 1-1. Common Launch Image Types

Filename Image Dimension Comment
Default.png 320 x 480 (iPhone) 768 x 1004 (iPad) This is the fallback file. If no other suitable launch image were found, iOS will use this.
Default∼iphone.png 320 x 480 iPhone apps are always launched in portrait mode
Default@2x∼iphone.png 640 x 960 Retina (iPhone 4 & 4S)
[email protected] 640 x 1136 iPhone 5
Default-Portrait∼ipad.png 768 x 1004
Default-Portrait@2x∼ipad.png 1536 x 2008 Retina (iPad 3)
Default-Landscape∼ipad.png 1024 x 748
Default-Landscape@2x∼ipad.png 2048 x 1496 Retina (iPad 3)

There is a difference between iPhone and iPad launch images that you should be aware of when designing your launch images. iPhone app launch images covers the entire screen while iPad launch images don’t include the space for the status bar. This is why iPad launch images are 20 pixels shorter (40 pixels for retina displays) in one dimension.

Note  The filesystem of iOS is case sensitive. Therefore it’s important that you pay attention to the casing when you name your launch files. Otherwise your app will not recognize them and show them at startup.

There are more options than we’ve put in the table, like upside-down portrait and left- or right-oriented landscape. However, it is recommended that you keep the number of launch images to a minimum as they add to the total size of your app.

Note  Because iPhone apps, regardless of actual orientation, always launch in a portrait mode you only need one launch image for those. However, iPad apps usually need two images: one for portrait orientation and one for landscape. Universal apps therefore normally need three launch images: portrait for iPhone, and portrait and landscape for iPad.

After you’ve created the launch images, you can add them to your project. This is done by Ctrl-clicking the Supporting Files folder of your project and choosing Add Files to <your project> from the context menu (see Figure 1-70).

9781430245995_Fig01-70.jpg

Figure 1-70.  Add launch images to the Supporting Files folder of your project

Be sure to check the Copy items into destination group’s folder (if needed) option as in Figure 1-71, so that the images are copied into the app’s bundle.

9781430245995_Fig01-71.jpg

Figure 1-71.  Make sure Copy items into destination group’s folder (if needed) is checked

Now, provided that you named your launch image(s) correctly, it/they will be shown during application launch and replaced with the main view once the app is ready for user interaction.

Designing Launch Images

Apple’s philosophy regarding launch images is that they should resemble the main view of the app as much as possible. This makes the loading time seem a little bit quicker and therefore more responsive than if the launch image has a more splash-like design, which contains, say, branding information.

So, for instance, if your app’s main view consists of a table view, your launch image could look something like Figure 1-72.

9781430245995_Fig01-72.jpg

Figure 1-72.  A launch image should resemble the main view of your app

Also, it’s recommended that launch images don’t contain elements that may need to be translated or changed during localization. For example, the launch image in Figure 1-67 doesn’t contain any navigation bar buttons. These have been edited out as well as the elements in the status bar.

Even though there is support for language specific versions of the launch images, it’s suggested you don’t use it due to the increased amount of space the additional launch images will require.

Summary

In this chapter you’ve acquired (or refreshed) some fundamental knowledge you need to follow the remaining recipes of this book. You’ve seen how to set up a basic application, how to build user interfaces, and how to control them from your code. You’ve also seen an extensive example of how to use Storyboards, the new way in which Apple wants you to build and structure user interfaces. Additionally, you’ve learned the difference between errors and exceptions in iOS, and seen how you can implement a strategy to handle them.

By now you should be more than ready to take on the rest of this book.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset