O'Reilly logo

iOS 7 Programming Cookbook by Vandad Nahavandipoor

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 1. Implementing Controllers and Views

1.0. Introduction

iOS 7 has introduced a lot of new features to users, as well as tons of new APIs for us programmers to use and play with. You probably already know that the user interface has drastically changed in iOS 7. This user interface had stayed intact all the way from the first version of iOS till now and because of this, many apps were coded on the assumption that this user interface would not ever change. Graphic designers are now faced with the challenge of creating the user interface and thinking about the user experience in a way that makes it great for both pre- and post-iOS 7 UIs.

In order to write apps for iOS 7, you need to know some of the basics of the Objective-C programming language that we will use throughout this book. Objective-C, as its name implies, is based on C with extensions that allow it to make use of objects. Objects and classes are fundamental in object-oriented programming (OOP) languages such as Objective-C, Java, C++, and many others. In Objective-C, like any other object-oriented language (OOL), you have not only access to objects, but also to primitives. For instance, the number –20 (minus twenty) can be expressed simply as a primitive in this way:

NSInteger myNumber = -20;

This simple line of code will define a variable named myNumber with the data type of NSInteger and sets its value to 10. This is how we define variables in Objective-C. A variable is a simple assignment of a name to a location in memory. In this case, when we set 10 as the value of the myNumber variable, we are telling the machine that will eventually run this piece of code to put the aforementioned value in a memory location that belongs to the variable myNumber.

All iOS applications essentially use the Model-View-Controller (MVC) architecture. Model, view, and controller are the three main components of an iOS application from an architectural perspective.

The model is the brain of the application. It does the calculations and creates a virtual world for itself that can live without the views and controllers. In other words, think of a model as a virtual copy of your application, without a face!

A view is the window through which your users interact with your application. It displays what’s inside the model most of the time, but in addition to that, it accepts users’ interactions. Any interaction between the user and your application is sent to a view, which then can be captured by a view controller and sent to the model.

The controller in iOS programming usually refers to the view controllers I just mentioned. Think of a view controller as a bridge between the model and your views. This controllers interprets what is happening on one side and uses that information to alter the other side as needed. For instance, if the user changes some field in a view, the controller makes sure the model changes in response. And if the model gets new data, the controller tells the view to reflect it.

In this chapter, you will learn how to create the structure of an iOS application and how to use views and view controllers to create intuitive applications.

Note

In this chapter, for most of the User Interface (UI) components that we create, we are using a Single View Application template in Xcode. To reproduce the examples, follow the instructions in Creating and Running Our First iOS App. Make sure that your app is Universal, as opposed to an iPhone or iPad app. A Universal app can run on both iPhone and iPad.

Creating and Running Our First iOS App

Before we dive any deeper into the features of Objective-C, we should have a brief look at how to create a simple iOS app in Xcode. Xcode is Apple’s IDE (Integrated Development Environment) that allows you to create, build, and run your apps on the iOS Simulator and even on real iOS devices. We will talk more about Xcode and its features as we go along, but for now let’s focus on creating and running a simple iOS app. I assume that you’ve already downloaded Xcode into your computer from the Mac App Store. Once that step is taken care of, please follow these steps to create and run a simple iOS app:

  1. Open Xcode if it’s not already open.

  2. From the File menu, choose New Project...

  3. In the New Project window that appears, on the lefthand side under the iOS category, choose Application and then on the righthand side, choose Single View Application. Then press the Next button.

  4. On the next screen, for the Product Name, enter a name that makes sense for you. For instance, you can set the name of your product as My First iOS App. In the Organization Name section, enter your company’s name, or if you don’t have a company, enter anything else that makes sense to you. The organization name is quite an important piece of information that you can enter here, but for now, you don’t have to worry about it too much. For the Company Identifier field, enter com.mycompany. If you really do own a company or you are creating this app for a company that you work with, replace mycompany with the actual name of the company in question. If you are just experimenting with development on your own, invent a name. For the Devices section, choose Universal and ensure that the Use Storyboards and the Include Unit Tests checks are not enabled, and that the Use Automatic Reference Counting check is enabled.

  5. Once you are done setting the aforementioned values, simply press the Next button.

  6. You are now being asked by Xcode to save your project to a suitable place. Choose a suitable folder for your project and press the Create button.

  7. As soon as your project is created, you are ready to build and run it. However, before you begin, make sure that you’ve unplugged all your iOS devices from your computer. The reason behind this is that once an iOS device is plugged in, by default, Xcode will attempt to build and run your project on the device, causing some issues with provisioning profiles (which we haven’t talked about yet). So unplug your iOS devices and then press the big Run button on the top-lefthand corner of Xcode. If you cannot find the Run button, go to the Product menu and select the Run menu item.

Voilà! Your first iOS app is running in the iOS Simulator now. Even though the app is not exactly impressive, simply displaying a white screen in the simulator, this is just the first step toward our bigger goal of mastering the iOS SDK, so hold on tight as we embark on this journey together.

Defining and Understanding Variables

All modern programming languages, including Objective-C, have the concept of variables. Variables are simple aliases to locations in the memory. Every variable can have the following properties:

  1. A data type, which is either a primitive such as an integer, or an object.

  2. A name.

  3. A value

You don’t always have to set a value for a variable, but you need to specify its type and its name. Here are a few data types that you will need to know about when writing any typical iOS app:

Mutable Versus Immutable

If a data type is mutable, you can change if after it is initialized. For instance, you can change one of the values in a mutable array, or add or remove values. In contrast, you must provide the values to an immutable data type when you initialize it, and cannot add to them, remove them, or change them later. Immutable types are useful because they are more efficient, and because they can prevent errors when the values are meant to stay the same throughout the life of the data.

NSInteger and NSUInteger

Variables of this type can hold integral values such as 10, 20, etc. The NSInteger type allows negative values as well as positive ones, but the NSUInteger data type is the Unsigned type, hence the U in its name. Remember, the phrase unsigned in programming languages in the context of numbers always means that the number must not be negative. Only a signed data type can hold negative numbers.

CGFloat

Holds floating point variables with decimal points, such as 1.31 or 2.40.

NSString

Allows you to store strings of characters. We will see examples of this later.

NSNumber

Allows you to store numbers as objects.

id

Variables of type id can point to any object of any type. These are called untyped objects. Whenever you want to pass an object from one place to another but do not wish to specify its type for whatever reason, you can take advantage of this data type.

NSDictionary and NSMutableDictionary

These are immutable and mutable variants of hash tables. A hash table allows you to store a key and to associate a value to that key, such as a key named phone_num that has the value 05552487700. Read the values by referring to the keys associated with them.

NSArray and NSMutableArray

Immutable and mutable arrays of objects. An array is an ordered collection of items. For instance, you may have ten string objects that you want to store in memory. An array could be a good place for that.

NSSet, NSMutableSet, NSOrderedSet, NSMutableOrderedSet

Sets are like arrays, in that they can hold series of objects, but they differ from arrays in that they contain only unique objects. Arrays can hold the same object multiple times, but a set can contain only one instance of an object. I encourage you to learn the difference between arrays and sets and use them properly.

NSData and NSMutableData

Immutable and mutable containers for any data. These data types are perfect when you want to read the contents of a file, for instance, into memory.

Some of the data types that we talked about are primitive and some are classes. You’ll just have to memorize which is which. For instance, NSInteger is a primitive data type, but NSString is a class, so objects can be instantiated of it. Objective-C, like C and C++, has the concept of pointers. A pointer is data type that stores the memory address where the real data is stored. You should know by now that pointers to classes are denoted using an asterisk sign:

NSString *myString = @"Objective-C is great!";

Thus, when you want to assign a string to a variable of type NSString in Objective-C, you simply have to store the data into a pointer of type NSString *. However, if you are about to store a floating point value into a variable, you wouldn’t specify it as a pointer since the data type for that variable is not a class:

/* Set the myFloat variable to PI */
CGFloat myFloat = M_PI;

If you wanted to have a pointer to that floating point variable, you could do so as follows:

/* Set the myFloat variable to PI */
CGFloat myFloat = M_PI;

/* Create a pointer variable that points to the myFloat variable */
CGFloat *pointerFloat = &myFloat;

Getting data from the original float is a simple dereference (myFloat) whereas getting the value of through the pointer requires the use of the asterisk (*pointerFloat). The pointer can be useful in some situations, such as when you call a function that sets the value of a floating-point argument and you want to retrieve the new value after the function returns.

Going back to classes, we probably have to talk a bit more about classes before things get lost in translation, so let’s do that next.

Creating and Taking Advantage of Classes

A class is a data structure that can have methods, instance variables, and properties along with many other features, but for now we are just going to talk about the basics. Every class has to follow these rules:

  • The class has to be derived from a super class, apart from a few exceptions such as NSObject and NSProxy classes, which are root classes. Root classes do not have a super class.

  • It has to have a name that conforms to Cocoa’s naming convention for methods.

  • It has to have an interface file that defines the interface of the class.

  • It has to have an implementation where you implement the features that you have promised to deliver in the interface of the class.

NSObject is the root class from which almost every other class is inherited. For this example, we are going to add a class, named Person, to the project that we created in Creating and Running Our First iOS App. We are going to then add two properties to this class, named firstName and lastName, of type NSString. Follow these steps to create and add the Person class to your project:

  1. In Xcode, while your project is open and in front of you, from the File menu, choose New → File...

  2. On the lefthand side, ensure that under the iOS main section, you have chosen the Cocoa Touch category. Once done, select the Objective-C Class item and press the Next button.

  3. In the Class section, enter Person.

  4. In the “Subclass of” section, enter NSObject.

  5. Once done, press the Next button, at which point Xcode will ask where you would like to save this file. Simply save the new class into the folder where you have placed your project and its files. This is the default selection. Then press the Create button and you are done.

You now have two files added to your project: Person.h and Person.m. The former is the interface and the latter is the implementation file for your Person class. In Objective-C, .h files are headers, where you define the interface of each class, and .m files are implementation files where you write the actual implementation of the class.

Now let’s go into the header file of our Person class and define two properties for the class, of type NSString:

@interface Person : NSObject

@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;

@end

Just like a variable, definition of properties has its own format, in this particular order:

  1. The definition of the property has to start with the @property keyword.

  2. You then need to specify the qualifiers of the property. nonatomic properties are not thread-safe. We will talk about thread safety in Chapter 16. You can also specify assign, copy, weak, strong, or unsafe_unretained as the property qualifiers. We will read more about these soon too.

  3. You then have to specify the data type of the property, such as NSInteger or NSString.

  4. Last but not least, you have to specify a name for the property. The name of the property has to follow the Apple guidelines.

We said that properties can have various qualifiers. Here are the important qualifiers that you need to know about:

strong

Properties of this type will be retained by the runtime. These can only be instances of classes. In other words, you cannot retain a value into a property of type strong if the value is a primitive. You can retain objects, but not primitives.

copy

The same as strong, but when you assign to properties of this type, the runtime will make a copy of the object on the right side of the assignment. The object on the righthand side of the assignment must conform to the NSCopying or NSMutableCopying protocol.

assign

Objects or primitive values that are set as the value of a property of type assign will not be copied or retained by that property. For primitive properties, this qualifier will create a memory address where you can put the primitive data. For objects, properties of this type will simply point to the object on the righthand side of the equation.

unsafe_unretained

The same as the assign qualifier.

weak

The same as the assign qualifier with one big difference. In case of objects, when the object that is assigned to a property of this type is released from memory, the runtime will automatically set the value of this property to nil.

We now have a Person class with two properties: firstName and lastName. Let’s go back to our app delegate’s implementation (AppDelegate.m) file and instantiate an object of type Person:

#import "AppDelegate.h"
#import "Person.h"

@implementation AppDelegate

- (BOOL)            application:(UIApplication *)application
  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

    Person *person = [[Person alloc] init];

    person.firstName = @"Steve";
    person.lastName = @"Jobs";

    self.window = [[UIWindow alloc]
                   initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

We are allocating and initializing our instance of the Person class in this example. You may not know what that means for yet, but continue to the Adding Functionality to Classes with Methods section and you will find out.

Adding Functionality to Classes with Methods

Methods are building blocks of classes. For instance, a class named Person can have logical functionalities such as walk, breathe, eat, and drink. These functionalities are usually encapsulated in methods.

A method can take parameters, which are variables that the caller passes when calling the method and are visible only to the method. For instance, in a simple world, we would have a walk method for our Person class. However, if you want, you can add a parameter or argument to the method and name it walkingSpeed of type CGFloat, so that when another programmer calls that method on your class, she can specify the speed by which the person has to walk. You, as the programmer of that class, would then add write the appropriate code for your class to handle different speeds of walking. Don’t worry if this all sounds like too much, but have a look at the following example, where I have added a method to the implementation file we created in Creating and Taking Advantage of Classes for our Person class:

#import "Person.h"

@implementation Person

- (void) walkAtKilometersPerHour:(CGFloat)paramSpeedKilometersPerHour{
    /* Write the code for this method here */
}

- (void) runAt10KilometersPerHour{
    /* Call the walk method in our own class and pass the value of 10 */
    [self walkAtKilometersPerHour:10.0f];
}

@end

A typical method has the following qualities in Objective-C:

  1. Tell the compiler that your method is an instance method by prefixing the method with the - character, or with + if your method has to be a class method. An instance method can be accessed only after the programmer allocates and initializes an instance of your class. A class method can be accessed by calling it directly from the class itself. Don’t worry if this all sounds complicated. We will see examples of these methods in this book, so don’t get hung up on this for now.

  2. A data type for the method, if the method returns any value. In our example, we have specified void, telling the compiler that we are not returning anything.

  3. The first part of the method name followed by the first parameter. You don’t necessarily have to have any parameters for a method. You can have methods that take no parameters.

  4. The list of subsequent parameters following the first parameter.

Let me show you an example of a method with two parameters:

- (void) singSong:(NSData *)paramSongData loudly:(BOOL)paramLoudly{
    /* The parameters that we can access here in this method are:

     paramSongData (to access the song's data)
     paramLoudly will tell us if we have to sing the song loudly or not
     */
}

It’s important to bear in mind that every parameter in every method has an external and an internal name. The external name is part of the method, whereas the internal part is the actual name or alias of the parameter that can be used inside the method’s implementation. In the previous example, the external name of the first parameter is singSong whereas its internal name is paramSongData. The external name of the second parameter is loudly, but its internal name is paramLoudly. The method’s name and the external names of its parameters combine to form what is known as the selector for the method. The selector for the aforementioned method in this case would be singSong:loudly:. A selector, as you will later see in this book, is the runtime identifier of every method. No two methods inside a single class can have the same selector.

In our example, we have defined three methods for our Person class, inside its implementation file (Person.m):

  • walkAtKilometersPerHour:

  • runAt10KilometersPerHour

  • singSong:loudly:

If we want to be able to use any of these methods from the outside world—for instance, from the app delegate—we should expose those methods in our interface file (Person.h):

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;

- (void) walkAtKilometersPerHour:(CGFloat)paramSpeedKilometersPerHour;
- (void) runAt10KilometersPerHour;

/* Do not expose the singSong:loudly: method to the outside world.
 That method is internal to our class. So why should we expose it? */

@end

Given this interface file, a programmer can call the walkAtKilometersPerHour: and the runAt10KilometersPerHour methods from outside the Person class, but not the singSong:loudly: method because it has not been exposed in the file. So let’s go ahead and try to call all three of these methods from our app delegate to see what happens!

- (BOOL)            application:(UIApplication *)application
  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

    Person *person = [[Person alloc] init];

    [person walkAtKilometersPerHour:3.0f];
    [person runAt10KilometersPerHour];

    /* If you uncomment this line of code, the compiler will give
     you an error telling you this method doesn't exist on the Person class */
    //[person singSong:nil loudly:YES];

    self.window = [[UIWindow alloc]
                   initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

Now we know how to define and call instance methods, but what about class methods? Let’s first find out what class methods are and how they differ from instance methods.

An instance method is a method that relates to an instance of a class. For instance, in our Person class, you can instantiate this class twice to create two distinct persons in a hypothetical game that you are working on and have one of those persons walk at the speed of 3 kilometers an hour while the other person walks at 2 kilometers an hour. Even though you have written the code for the walking instance method only once, when two separate instances of the Person class are created at runtime, the calls to the instance methods will be routed to the appropriate instance of this class.

In contrast, class methods work on the class itself. For instance, in a game where you have instances of a class named Light that light the scenery of your game, you may have a dimAllLights class method on this class that a programmer can call to dim all lights in the game, no matter where they are placed. Let’s have a look at an example of a class method on our Person class:

#import "Person.h"

@implementation Person

+ (CGFloat) maximumHeightInCentimeters{
    return 250.0f;
}

+ (CGFloat) minimumHeightInCentimeters{
    return 40.0f;
}

@end

The maximumHeightInCentimeters method is a class method that returns the hypothetical maximum height of any person in centimeters. The minimumHeightInCentimeters class method returns the minimum height of any person. Here is how we would then expose these methods in the interface of our class:

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, assign) CGFloat currentHeight;

+ (CGFloat) maximumHeightInCentimeters;
+ (CGFloat) minimumHeightInCentimeters;

@end

Note

We have also added a new floating point property to our Person class named currentHeight. This allows instances of this class to be able to store their height in memory for later reference, just like their first or their last names.

And in our app delegate, we would proceed to use these new methods like so:

- (BOOL)            application:(UIApplication *)application
  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

    Person *steveJobs = [[Person alloc] init];
    steveJobs.firstName = @"Steve";
    steveJobs.lastName = @"Jobs";
    steveJobs.currentHeight = 175.0f; /* Centimeters */

    if (steveJobs.currentHeight >= [Person minimumHeightInCentimeters] &&
        steveJobs.currentHeight <= [Person maximumHeightInCentimeters]){
        /* The height of this particular person is in the acceptable range */
    } else {
        /* This person's height is not in the acceptable range */
    }

    self.window = [[UIWindow alloc]
                   initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

Conforming to Requirements of Other Classes with Protocols

Objective-C has the concept of a protocol. This is a concept found in many other languages (always under a different term, it seems); for instance, it is called an interface in Java. A protocol, as its name implies, is a set of rules that classes can abide by in order to be used in certain ways. A class that follows the rules is said to conform to the protocol. Protocols are different from actual classes in that they do not have an implementation. They are just rules. For instance, every car has wheels, doors, and a main body color, among many other things. Let’s define these properties in a protocol named Car. Simply follow these steps to create a header file that can contain our Car protocol:

  1. In Xcode, while your project is open, from the File menu, select New → File...

  2. In the new dialog, on the lefthandside, make sure that you’ve selected Cocoa Touch under the iOS main category. Once done, on the righthand side of the dialog, choose “Objective-C protocol” and then press the Next button.

  3. On the next screen, under the Protocol section, enter Car as the protocol’s name and then press the Next button.

  4. You will now be asked to save your protocol on disk. Simply choose a location, usually in your project’s folder, and press the Create button.

Xcode will now create a file for you named Car.h with content like this:

#import <Foundation/Foundation.h>

@protocol Car <NSObject>

@end

So let’s go ahead and define the properties for the Car protocol, as we discussed earlier in this section:

#import <Foundation/Foundation.h>

@protocol Car <NSObject>

@property (nonatomic, copy) NSArray *wheels;
@property (nonatomic, strong) UIColor *bodyColor;
@property (nonatomic, copy) NSArray *doors;

@end

Now that our protocol has been defined, let’s create a class for a car, such as Jaguar, and then make that class conform to our protocol. Simply follow the steps provided in Creating and Taking Advantage of Classes to create a class named Jaguar and then make it conform to the Car protocol like so:

#import <Foundation/Foundation.h>
#import "Car.h"

@interface Jaguar : NSObject <Car>

@end

If you build your project now, you will notice that the compiler will give you a few warnings such as this:

Auto property synthesis will not synthesize property declared in a protocol

This is simply telling you that your Jaguar class is attempting to conform to the Car protocol but is not really implementing the required properties and/or methods in that protocol. So you should now know that a protocol can have required or optional items, and that you denote them by the @optional or the @required keywords. The default qualifier is @required, and since in our Car protocol we didn’t specify the qualifier explicitly, the compiler has chosen @required for us implicitly. Therefore, the Jaguar class now has to implement everything that is required from it by the Car protocol, like so:

#import <Foundation/Foundation.h>
#import "Car.h"

@interface Jaguar : NSObject <Car>

@property (nonatomic, copy) NSArray *wheels;
@property (nonatomic, strong) UIColor *bodyColor;
@property (nonatomic, copy) NSArray *doors;

@end

Perfect. Now you have an understanding of the basics of protocols and how they work and how you can define them. We will read more about them later in this book so this what you know right now about protocols is quite sufficient.

Storing Items in and Retrieving Them from Collections

Collections are instances of objects and can hold other objects. One of the primary collections is an array, which instantiates either NSArray or NSMutableArray. You can store any object in an array and an array can contain more than one instance of the same object. Here is an example where we create an array of three strings:

NSArray *stringsArray = @[
                          @"String 1",
                          @"String 2",
                          @"String 3"
                          ];

__unused NSString *firstString = stringsArray[0];
__unused NSString *secondString = stringsArray[1];
__unused NSString *thirdString = stringsArray[2];

Note

The __unused macro tells the compiler not to complain when a variable, such as the firstString variable in our example, is declared but never used. The default behavior of the compiler is that it throws a warning to the console saying a variable is not used. Our brief example has declared the variables but not used them, so adding the aforementioned macro to the beginning of the variable declaration keeps the compiler and ourselves happy.

A mutable array is an array that can be mutated and changed after it has been created. An immutable array, like we saw, cannot be tampered with after it is created. Here is an example of an immutable array:

NSString *string1 = @"String 1";
NSString *string2 = @"String 2";
NSString *string3 = @"String 3";

NSArray *immutableArray = @[string1, string2, string3];

NSMutableArray *mutableArray = [[NSMutableArray alloc]
                                initWithArray:immutableArray];

[mutableArray exchangeObjectAtIndex:0 withObjectAtIndex:1];
[mutableArray removeObjectAtIndex:1];
[mutableArray setObject:string1 atIndexedSubscript:0];

NSLog(@"Immutable array = %@", immutableArray);
NSLog(@"Mutable Array = %@", mutableArray);

The output of this program is as follows:

Immutable array = (
    "String 1",
    "String 2",
    "String 3"
)
Mutable Array = (
    "String 1",
    "String 3"
)

Another very common collection found throughout iOS programs is a dictionary. Dictionaries are like arrays, but every object in a dictionary is assigned to a key so that later you can retrieve the same object using the key. Here is an example:

NSDictionary *personInformation =
@{
  @"firstName" : @"Mark",
  @"lastName" : @"Tremonti",
  @"age" : @30,
  @"sex" : @"Male"
  };

NSString *firstName = personInformation[@"firstName"];
NSString *lastName = personInformation[@"lastName"];
NSNumber *age = personInformation[@"age"];
NSString *sex = personInformation[@"sex"];

NSLog(@"Full name = %@ %@", firstName, lastName);
NSLog(@"Age = %@, Sex = %@", age, sex);

The output of this program is:

Full name = Mark Tremonti
Age = 30, Sex = Male

You can also have mutable dictionaries, just as you can have mutable arrays. Mutable dictionaries’ contents can be changed after they are instantiated. Here is an example:

NSDictionary *personInformation =
@{
  @"firstName" : @"Mark",
  @"lastName" : @"Tremonti",
  @"age" : @30,
  @"sex" : @"Male"
  };

NSMutableDictionary *mutablePersonInformation =
    [[NSMutableDictionary alloc] initWithDictionary:personInformation];

mutablePersonInformation[@"age"] = @32;

NSLog(@"Information = %@", mutablePersonInformation);

The output of this program is:

Information = {
    age = 32;
    firstName = Mark;
    lastName = Tremonti;
    sex = Male;
}

You can also take advantage of sets. Sets are like arrays, but must contain a unique set of objects. You cannot add the same instance of an object twice to the same set. Here is an example:

NSSet *shoppingList = [[NSSet alloc] initWithObjects:
                       @"Milk",
                       @"Bananas",
                       @"Bread",
                       @"Milk", nil];

NSLog(@"Shopping list = %@", shoppingList);

If you run this program, the output will be:

Shopping list = {(
    Milk,
    Bananas,
    Bread
)}

Note how Milk was mentioned twice in our program but added to the set only once. That’s the magic behind sets. You can also use mutable sets like so:

NSSet *shoppingList = [[NSSet alloc] initWithObjects:
                       @"Milk",
                       @"Bananas",
                       @"Bread",
                       @"Milk", nil];

NSMutableSet *mutableList = [NSMutableSet setWithSet:shoppingList];

[mutableList addObject:@"Yogurt"];
[mutableList removeObject:@"Bread"];

NSLog(@"Original list = %@", shoppingList);
NSLog(@"Mutable list = %@", mutableList);

And the output is:

Original list = {(
    Milk,
    Bananas,
    Bread
)}
Mutable list = {(
    Milk,
    Bananas,
    Yogurt
)}

There are two other important classes that you need to know about, now that we are talking about sets and collections:

NSOrderedSet

An immutable set that keeps the order in which objects were added to it.

NSMutableOrderedSet

The mutable version of the ordered set.

By default, sets do not keep the order in which objects were added to them. Take the following as an example:

NSSet *setOfNumbers = [NSSet setWithArray:@[@3, @4, @1, @5, @10]];
NSLog(@"Set of numbers = %@", setOfNumbers);

What gets printed to the screen after you run this program is:

Set of numbers = {(
    5,
    10,
    3,
    4,
    1
)}

But that is not the order in which we created the set. If you want to keep the order intact, simply use the NSOrderedSet class instead:

NSOrderedSet *setOfNumbers = [NSOrderedSet orderedSetWithArray
                              :@[@3, @4, @1, @5, @10]];

NSLog(@"Ordered set of numbers = %@", setOfNumbers);

And of course, you can use the mutable version of an ordered set:

NSMutableOrderedSet *setOfNumbers =
    [NSMutableOrderedSet orderedSetWithArray:@[@3, @4, @1, @5, @10]];

[setOfNumbers removeObject:@5];
[setOfNumbers addObject:@0];
[setOfNumbers exchangeObjectAtIndex:1 withObjectAtIndex:2];

NSLog(@"Set of numbers = %@", setOfNumbers);

The results are shown here:

Set of numbers = {(
    3,
    1,
    4,
    10,
    0
)}

Before we move off the topic of sets, there is one other handy class that you may need to know about. The NSCountedSet class can hold a unique instance of an object multiple times. However, the way this is done is different from the way arrays perform the same task. In an array, the same object can appear multiple times. But in a counted set, the object will appear only once, but the set keeps a count of how many times the object was added to the set and will decrement that counter each time you remove an instance of the object. Here is an example:

NSCountedSet *setOfNumbers = [NSCountedSet setWithObjects:
                              @10, @20, @10, @10, @30, nil];

[setOfNumbers addObject:@20];
[setOfNumbers removeObject:@10];

NSLog(@"Count for object @10 = %lu",
      (unsigned long)[setOfNumbers countForObject:@10]);

NSLog(@"Count for object @20 = %lu",
      (unsigned long)[setOfNumbers countForObject:@20]);

The output is:

Count for object @10 = 2
Count for object @20 = 2

Note

The NSCountedSet class is mutable, despite what its name may lead you to think.

Adding Object Subscripting Support to Your Classes

Traditionally, when accessing objects in collections such as arrays and dictionaries, programmers had to access a method on the array or the dictionary to get or set that object. For instance, this is the traditional way of creating a mutable dictionary, adding two keys and values to it, and retrieving those values back:

NSString *const kFirstNameKey = @"firstName";
NSString *const kLastNameKey = @"lastName";

NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];

[dictionary setValue:@"Tim" forKey:kFirstNameKey];
[dictionary setValue:@"Cook" forKey:kLastNameKey];

__unused NSString *firstName = [dictionary valueForKey:kFirstNameKey];
__unused NSString *lastName = [dictionary valueForKey:kLastNameKey];

But with all the advances in the LLVM compiler, this code can now be shortened to this:

NSString *const kFirstNameKey = @"firstName";
NSString *const kLastNameKey = @"lastName";

NSDictionary *dictionary = @{
                             kFirstNameKey : @"Tim",
                             kLastNameKey : @"Cook",
                             };

__unused NSString *firstName = dictionary[kFirstNameKey];
__unused NSString *lastName = dictionary[kLastNameKey];

You can see that we are initializing the dictionary by providing the keys in curly brackets. The same thing for arrays. Here is how we used to create and use arrays traditionally:

NSArray *array = [[NSArray alloc] initWithObjects:@"Tim", @"Cook", nil];
__unused NSString *firstItem = [array objectAtIndex:0];
__unused NSString *secondObject = [array objectAtIndex:1];

And now with object subscripting, we can shorten this code, as follows:

NSArray *array = @[@"Tim", @"Cook"];
__unused NSString *firstItem = array[0];
__unused NSString *secondObject = array[0];

LLVM didn’t even stop there. You can add subscripting to your own classes as well. There are two types of subscripting:

Subscripting by key

With this, you can set the value for a specific key inside an object, just like you would in a dictionary. You can also access/read-from values inside the object by providing the key.

Subscripting by index

As with arrays, you can set/get values inside the object by providing an index to that object. This makes sense for array-like classes where the elements lie in a natural order that can be represented by an index.

For the first example, we are going to look at subscripting by key. To do this, we are going to create a class named Person with a firstName and a lastName. Then we are going to allow the programmer to change the first and last names by simply providing the key to those properties.

The reason you may want to add subscripting by key to a class like this is if your property names are volatile and you want to allow the programmer to set the value of those properties without having to worry whether the names of those properties will change later; otherwise, the programmer is better off using the properties directly. The other reason for implementing subscripting by key is if you want to hide the exact implementation/declaration of your properties from the programmer and not let her access them directly.

In order to support subscripting by key on your own classes, you must implement the following two methods on your class and put the method signatures in your class’s header file; otherwise, the compiler won’t know that your class supports subscripting by key.

#import <Foundation/Foundation.h>

/* We will use these as the keys to our firstName and lastName
 properties so that if our firstName and lastName properties' name
 changes in the future in the implementation, we won't break anything
 and our class will still work as we can simply change the value of
 these constants inside our implementation file */
extern NSString *const kFirstNameKey;
extern NSString *const kLastNameKey;

@interface Person : NSObject

@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;

- (id) objectForKeyedSubscript:(id<NSCopying>)paramKey;
- (void) setObject:(id)paramObject forKeyedSubscript:(id<NSCopying>)paramKey;

@end

The objectForKeyedSubscript: method will be called on your class whenever the programmer provides a key and wants to read the value of that key in your class. The parameter that will be given to you will obviously be the key from which the programmer wants to read the value. To complement this method, the setObject:forKeyedSubscript: method will be called on your class whenever the programmer wants to set the value for a specified key. So in our implementation, we want to check whether the given keys are the first name and the last name keys, and if yes, we will set/get the values of the first name and last name in our class:

#import "Person.h"

NSString *const kFirstNameKey = @"firstName";
NSString *const kLastNameKey = @"lastName";

@implementation Person

- (id) objectForKeyedSubscript:(id<NSCopying>)paramKey{

    NSObject<NSCopying> *keyAsObject = (NSObject<NSCopying> *)paramKey;
    if ([keyAsObject isKindOfClass:[NSString class]]){
        NSString *keyAsString = (NSString *)keyAsObject;
        if ([keyAsString isEqualToString:kFirstNameKey] ||
            [keyAsString isEqualToString:kLastNameKey]){
            return [self valueForKey:keyAsString];
        }
    }

    return nil;
}

- (void) setObject:(id)paramObject forKeyedSubscript:(id<NSCopying>)paramKey{
    NSObject<NSCopying> *keyAsObject = (NSObject<NSCopying> *)paramKey;
    if ([keyAsObject isKindOfClass:[NSString class]]){
        NSString *keyAsString = (NSString *)keyAsObject;
        if ([keyAsString isEqualToString:kFirstNameKey] ||
            [keyAsString isEqualToString:kLastNameKey]){
            [self setValue:paramObject forKey:keyAsString];
        }
    }
}

@end

So in this code, in the objectForKeyedSubscript: method, we are given a key and we are expected to return the object that is associated in our instance with that key. The key that is given to us is an object that conforms to the NSCopying protocol. In other words, it’s an object that we can make a copy of if we want to. We expect the key to be a string so that we can compare it with the predefined keys that we have declared on top of our class, and if it matches, we will set the value of that property in our class. We will then use the NSObject method named valueForKey: to return the value associated with the given key. But obviously, before we do so, we ensure that the given key is one of the keys that we expect. In the setObject:forKeyedSubscript: method we do the exact opposite. We set the values for a given key instead of returning them.

Now, elsewhere in your app, you can instantiate an object of type Person and use the predefined keys of kFirstNameKey and kLastNameKey to change the value of the firstName and lastName properties like so:

Person *person = [Person new];
person[kFirstNameKey] = @"Tim";
person[kLastNameKey] = @"Cook";
__unused NSString *firstName = person[kFirstNameKey];
__unused NSString *lastName = person[kLastNameKey];

This code will achieve exactly the same effect as the more direct approach of setting the properties of a class:

Person *person = [Person new];
person.firstName = @"Tim";
person.lastName = @"Cook";
__unused NSString *firstName = person.firstName;
__unused NSString *lastName = person.lastName;

You can also support subscripting by index, the same way arrays do. This is useful, as mentioned before, to allow programmers to access objects that have a natural order inside a class. But there are not many data structures besides arrays where it makes sense to order and number elements, unlike subscripting by key, which applies to a wide range of data structures. So the example I’ll use to illustrate subscripting by index is a bit contrived. In our previous example, we had the Person class with a first and last name. Now if you want to allow programmers to be able to read the first name by providing the index of 0 and the last name by providing the index of 1, all you have to do is declare the objectAtIndexedSubscript: and the setObject:atIndexedSubscript: methods in the header file of your class, and then write the implementation. Here is how we declare these methods in our Person class’s header file:

- (id) objectAtIndexedSubscript:(NSUInteger)paramIndex;
- (void) setObject:(id)paramObject atIndexedSubscript:(NSUInteger)paramIndex;

The implementation is also quite simple. We take the index and act upon it in a way that makes sense to our class. We decided that the first name has to have the index of 0 and the last name the index of 1. So if we get the index of 0 for setting a value, we set the value of the first name to the incoming object, and so on:

- (id) objectAtIndexedSubscript:(NSUInteger)paramIndex{

    switch (paramIndex){
        case 0:{
            return self.firstName;
            break;
        }
        case 1:{
            return self.lastName;
            break;
        }
        default:{
            [NSException raise:@"Invalid index" format:nil];
        }
    }

    return nil;
}

- (void) setObject:(id)paramObject atIndexedSubscript:(NSUInteger)paramIndex{
    switch (paramIndex){
        case 0:{
            self.firstName = paramObject;
            break;
        }
        case 1:{
            self.lastName = paramObject;
            break;
        }
        default:{
            [NSException raise:@"Invalid index" format:nil];
        }
    }
}

Now we can test out what we’ve written so far, like so:

Person *person = [Person new];
person[kFirstNameKey] = @"Tim";
person[kLastNameKey] = @"Cook";
NSString *firstNameByKey = person[kFirstNameKey];
NSString *lastNameByKey = person[kLastNameKey];

NSString *firstNameByIndex = person[0];
NSString *lastNameByIndex = person[1];

if ([firstNameByKey isEqualToString:firstNameByIndex] &&
    [lastNameByKey isEqualToString:lastNameByIndex]){
    NSLog(@"Success");
} else {
    NSLog(@"Something is not right");
}

If you’ve followed all the steps in this recipe, you should see the value Success printed to the console now.

1.1. Displaying Alerts with UIAlertView

Problem

You want to display a message to your users in the form of an alert. This could be used to ask them to confirm an action, ask for their username and password, or simply let them enter some simple text that you can use in your app.

Solution

Utilize the UIAlertView class.

Discussion

If you are an iOS user, you have most certainly already seen an alert view. Figure 1-1 depicts an example.

Example of an alert view in iOS
Figure 1-1. Example of an alert view in iOS

The best way to initialize an alert view is to use its designated initializer:

- (void) viewDidAppear:(BOOL)paramAnimated{

    [super viewDidAppear:paramAnimated];

    UIAlertView *alertView = [[UIAlertView alloc]
                              initWithTitle:@"Alert"
                              message:@"You've been delivered an alert"
                              delegate:nil
                              cancelButtonTitle:@"Cancel"
                              otherButtonTitles:@"Ok", nil];
    [alertView show];

}

When this alert view is displayed to the user, she will see something similar to that shown in Figure 1-2:

A simple alert view displayed to the user
Figure 1-2. A simple alert view displayed to the user

In order to display an alert view to the user, we use the alert view’s show method. Let’s have a look at the description for each of the parameters that we passed to the initializer of the alert view:

title

The string that the alert view will display on the top when it is shown to the user. This string is Title in Figure 1-2.

message

The actual message that gets displayed to the user. In Figure 1-2, this message is set to Message.

delegate

The optional delegate object that we pass to the alert view. This object will get notified whenever the alert’s state changes; for instance, when the user taps on a button on the alert view. The object passed to this parameter must conform to the UIAlertViewDelegate protocol.

cancelButtonTitle

A string that will get assigned to the cancel button on an alert view. An alert view that has a cancel button usually asks the user for an action. If the user isn’t comfortable with performing that action, he or she will press the cancel button. This button’s title does not necessarily have to say Cancel. It is up to you to specify a title for this button. This parameter is optional; you could put up a dialog box with no cancel button..

otherButtonTitles

Titles of any other buttons that you want to have appear on the alert view. Separate the titles with commas and make sure you terminate the list of titles with a nil, which is called a sentinel. This parameter is optional.

Warning

It is possible to create an alert view without any buttons. But the view cannot be dismissed by the user. If you create such a view, you, as the programmer, need to make sure this alert view will get dismissed automatically; for instance, three seconds after it is displayed. An alert view without any buttons that does not dismiss itself automatically gives a really poor user experience. Not only will your app get low ratings on the App Store for blocking the UI from user access, but chances are that your app will get rejected by Apple.

Alert views can take various styles. The UIAlertView class has a property called alertViewStyle of type UIAlertViewStyle:

typedef NS_ENUM(NSInteger, UIAlertViewStyle) {
    UIAlertViewStyleDefault = 0,
    UIAlertViewStyleSecureTextInput,
    UIAlertViewStylePlainTextInput,
    UIAlertViewStyleLoginAndPasswordInput
};

Here is what each of these styles will do:

UIAlertViewStyleDefault

This is the default style of an alert view, as we saw in Figure 1-2.

UIAlertViewStyleSecureTextInput

With this style, the alert view will contain a secure text field, which hides the actual characters typed by the user. For instance, if you are asking the user for her online banking credentials, you might choose this style of alert view.

UIAlertViewStylePlainTextInput

Under this style, the alert view will display a nonsecure text field to the user. This style is great if you simply want to ask the user for plain-text entry, such as her phone number.

UIAlertViewStyleLoginAndPasswordInput

With this style, the alert view will display two text fields: a nonsecure one for a username and a secure one for a password.

If you need to get notified when the user interacts with the alert view, specify a delegate object to your alert view. This delegate must conform to the UIAlertViewDelegate protocol. The most important method defined in this protocol is the alertView:clickedButtonAtIndex: method, which is called as soon as the user taps on one of the buttons in the alert view. The button index is passed to you through the clickedButtonAtIndex parameter.

As an example, let’s display an alert view to the user and ask whether she would like to visit a website in Safari after having pressed a link to that website available in our UI. We will display two buttons on our alert view: Yes and No. In our alert view delegate, we will detect which button she tapped on and will take action accordingly.

Let’s first implement two very simple methods that return the title of our two buttons:

- (NSString *) yesButtonTitle{
  return @"Yes";
}

- (NSString *) noButtonTitle{
  return @"No";
}

Now we need to make sure that we are conforming to the UIAlertViewDelegate protocol in our view controller:

#import "ViewController.h"

@interface ViewController () <UIAlertViewDelegate>

@end

@implementation ViewController

...

The next step is to create and display our alert view to the user:

- (void)viewDidAppear:(BOOL)animated{
  [super viewDidAppear:animated];

  self.view.backgroundColor = [UIColor whiteColor];

  NSString *message = @"Are you sure you want to open this link in Safari?";
  UIAlertView *alertView = [[UIAlertView alloc]
                            initWithTitle:@"Open Link"
                            message:message
                            delegate:self
                            cancelButtonTitle:[self noButtonTitle]
                            otherButtonTitles:[self yesButtonTitle], nil];
  [alertView show];

}

So now, our alert view will look similar to that shown in Figure 1-3.

An alert view with Yes and No buttons
Figure 1-3. An alert view with Yes and No buttons

Now we need a way to know whether the user selected the Yes or the No option in our alert view. For this, we will need to implement the alertView:clickedButtonAtIndex: method of our alert view delegate:

- (void)      alertView:(UIAlertView *)alertView
   clickedButtonAtIndex:(NSInteger)buttonIndex{

  NSString *buttonTitle = [alertView buttonTitleAtIndex:buttonIndex];

  if ([buttonTitle isEqualToString:[self yesButtonTitle]]){
    NSLog(@"User pressed the Yes button.");
  }
  else if ([buttonTitle isEqualToString:[self noButtonTitle]]){
    NSLog(@"User pressed the No button.");
  }

}

Note

Please bear in mind that in big projects where multiple developers work on the same source code, it is usually easier to compare the titles of buttons of alert views to respective strings, rather than picking which button the user selected on an alert view based on the index of that button. For the index solution to work, the programmer has to find out the code that constructed the alert view and, based on the code, find out which button has what index. In our solution, any developer, even without any knowledge as to how the alert view was constructed, can tell which if statement does what.

As you can see, we are using the buttonTitleAtIndex: method of UIAlertView. We pass the zero-based index of a button inside that alert view to this method and will get back the string that represents the title of that button, if any. Using this method, we can determine which button the user has tapped on. The index of that button will be passed to us as the buttonIndex parameter of the alertView:clickedButtonAtIndex: method, but if you need the title of that button, you will then need to use the buttonTitleAtIndex: method of UIAlertView. That is it; job done!

You can also use an alert view for text entry, such as to ask the user for his credit card number or address. For this, as mentioned before, we need to use the UIAlertViewStylePlainTextInput alert view style. Here is an example:

- (void) viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];

    UIAlertView *alertView = [[UIAlertView alloc]
                              initWithTitle:@"Credit Card Number"
                              message:@"Please enter your credit card number:"
                              delegate:self
                              cancelButtonTitle:@"Cancel"
                              otherButtonTitles:@"Ok", nil];
    [alertView setAlertViewStyle:UIAlertViewStylePlainTextInput];

    /* Display a numerical keypad for this text field */
    UITextField *textField = [alertView textFieldAtIndex:0];
    textField.keyboardType = UIKeyboardTypeNumberPad;

    [alertView show];

}

If you run your app on the simulator now, you will get a result similar to Figure 1-4.

An alert view with plain text input
Figure 1-4. An alert view with plain text input

We changed the alert view’s style to UIAlertViewStylePlainTextInput in this code, but we did something else as well. We retrieved the reference to the first and the only text field that we knew we would have on the alert view, and used that text field’s reference to change the keyboard type of the text field. For more information about text fields, please refer to Recipe 1.19.

In addition to a plain text entry, you can ask the user for secure text. You would normally use this if the text that the user is entering is sensitive, such as a password (see Figure 1-5). Here is an example:

- (void) viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];

    UIAlertView *alertView = [[UIAlertView alloc]
                              initWithTitle:@"Password"
                              message:@"Please enter your password:"
                              delegate:self
                              cancelButtonTitle:@"Cancel"
                              otherButtonTitles:@"Ok", nil];

    [alertView setAlertViewStyle:UIAlertViewStyleSecureTextInput];
    [alertView show];

}
Secure text entry in an alert view
Figure 1-5. Secure text entry in an alert view

The UIAlertViewStyleSecureTextInput style is very similar to UIAlertViewStylePlainTextInput, except that the text field is set to substitute some neutral character for each character of the entered text.

The next style, which is quite useful, displays two text fields, one for a username and the other for a password. The first is a plain text entry field and the other one is secure:

- (void) viewDidAppear:(BOOL)animated{
  [super viewDidAppear:animated];

  UIAlertView *alertView = [[UIAlertView alloc]
                            initWithTitle:@"Password"
                            message:@"Please enter your credentials:"
                            delegate:self
                            cancelButtonTitle:@"Cancel"
                            otherButtonTitles:@"Ok", nil];

  [alertView setAlertViewStyle:UIAlertViewStyleLoginAndPasswordInput];
  [alertView show];

}

The results will look similar to that shown in Figure 1-6.

Login and password style of alert view
Figure 1-6. Login and password style of alert view

See Also

Recipe 1.19

1.2. Creating and Using Switches with UISwitch

Problem

You would like to give your users the ability to turn an option on or off.

Solution

Use the UISwitch class.

Discussion

The UISwitch class provides an On/Off control like the one shown in Figure 1-7 for Auto-Capitalization, Auto-Correction, and so on.

UISwitch used in the Settings app on an iPhone
Figure 1-7. UISwitch used in the Settings app on an iPhone

In order to create a switch, you can either use Interface Builder or simply create your instance in code. Let’s do it through code. So next the challenge is to determine which class to place your code in. It needs to be in a View Controller class, which we haven’t discussed yet, but for the a Single View Application type of app we’re creating in this chapter, you can find the view controller’s .m (implementation) file as ViewController.m. Open that file now.

Let’s create a property of type UISwitch and call it mySwitch:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) UISwitch *mySwitch;
@end

@implementation ViewController

...

We can go ahead now and create our switch. Find the viewDidLoad method in your view controller’s implementation file:

- (void)viewDidLoad{
  [super viewDidLoad];
}

Let’s create our switch and place it on our view controller’s view:

- (void)viewDidLoad{
  [super viewDidLoad];

  /* Create the switch */
  self.mySwitch = [[UISwitch alloc] initWithFrame:
                   CGRectMake(100, 100, 0, 0)];

  [self.view addSubview:self.mySwitch];

}

So we are allocating an object of type UISwitch and using the initWithFrame: initializer to initialize our switch. Note that the parameter that we have to pass to this method is of type CGRect. A CGRect denotes the boundaries of a rectangle using the (x,y) position of the top-left corner of the rectangle and its width and height. We can construct a CGRect using the CGRectMake inline method, where the first two parameters passed to this method are the (x,y) positions and the next two are the width and height of the rectangle.

After we’ve created the switch, we simply add it to our view controller’s view.

Now let’s run our app on iOS Simulator. Figure 1-8 shows what happens.

A switch placed on a view
Figure 1-8. A switch placed on a view

As you can see, the switch’s default state is off. We can change this by changing the value of the on property of the instance of UISwitch. Alternatively, you can call the setOn: method on the switch, as shown here:

[self.mySwitch setOn:YES];

You can prettify the user interaction by using the setOn:animated: method of the switch. The animated parameter accepts a boolean value. If this boolean value is set to YES, the change in the switch’s state (from on to off or off to on) will be animated, just as if the user was interacting with it.

Obviously, you can read from the on property of the switch to find out whether the switch is on or off at the moment. Alternatively, you can use the isOn method of the switch, as shown here:

if ([self.mySwitch isOn]){
  NSLog(@"The switch is on.");
} else {
  NSLog(@"The switch is off.");
}

If you want to get notified when the switch gets turned on or off, you will need to add your class as the target for the switch, using the addTarget:action:forControlEvents: method of UISwitch, as shown here:

  [self.mySwitch addTarget:self
                    action:@selector(switchIsChanged:)
          forControlEvents:UIControlEventValueChanged];

Then implement the switchIsChanged: method. When the runtime calls this method for the UIControlEventValueChanged event of the switch, it will pass the switch as the parameter to this method, so you can find out which switch has fired this event:

- (void) switchIsChanged:(UISwitch *)paramSender{

  NSLog(@"Sender is = %@", paramSender);

  if ([paramSender isOn]){
    NSLog(@"The switch is turned on.");
  } else {
    NSLog(@"The switch is turned off.");
  }

}

Now go ahead and run the app on iOS Simulator. You will see messages similar to this in the console window:

Sender is = <UISwitch: 0x6e13500;
            frame = (100 100; 79 27);
            layer = <CALayer: 0x6e13700>>
The switch is turned off.
Sender is = <UISwitch: 0x6e13500;
            frame = (100 100; 79 27);
            layer = <CALayer: 0x6e13700>>
The switch is turned on.

1.3. Customizing the UISwitch

Problem

You have placed UISwitch instances on your UI and would now like to customize them to match your UI.

Solution

Use one of the tint/image customization properties of the UISwitch such as the tintColor or the onTintColor.

Discussion

Apple has done a fantastic job of bringing customization to UI components such as the UISwitch. In previous SDKs, developers were going as far as subclassing UISwitch just to change its appearance and color. Now, iOS SDK makes this much simpler.

There are two main ways of customizing a switch:

Tint Colors

Tint colors are colors that you can apply to a UI component such as a UISwitch. The tint color will be applied on top of the current color of the component. For instance, in a normal UISwitch, you will be able to see different colors. When you apply the tint color on top, the normal color of the control will be mixed with the tint color, giving a flavor of the tint color on the UI control.

Images

A switch has two images:

On Image

The image that represents the on state of the switch. The width of this image is 77 points and its height is 22.

Off Image

The image that represents the switch in its off state. This image, like the on state of the switch, is 77 points in width and 22 points in height.

Figure 1-9 shows an example of the on and off image of a switch.

The on and off images on a UISwitch
Figure 1-9. The on and off images on a UISwitch

Now that we know the two states (on and off) of a switch, let’s get started by learning how we can change the tint color of the switch UI component. This can be achieved by the use of three important properties of the UISwitch class. Each this property is of type UIColor.

tintColor

This is the tint color that will be applied to the off state of the switch. Unfortunately, Apple has not taken the time to name this property offTintColor instead of tintColor to make it more explicit.

thumbTintColor

This is the tint color that will be applied to the little knob on the switch.

onTintColor

This tint color will be applied to the switch in its on state.

Here is a simple code snippet that will change the on-mode tint color of the switch to red, the off-mode tint color to brown, and the knob’s tint color to green. It is not the best combination of colors, but will demonstrate what this recipe is trying to explain:

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.

    /* Create the switch */
    self.mainSwitch = [[UISwitch alloc] initWithFrame:CGRectZero];
    self.mainSwitch.center = self.view.center;
    [self.view addSubview:self.mainSwitch];

    /* Customize the switch */

    /* Adjust the off-mode tint color */
    self.mainSwitch.tintColor = [UIColor redColor];
    /* Adjust the on-mode tint color */
    self.mainSwitch.onTintColor = [UIColor brownColor];
    /* Also change the knob's tint color */
    self.mainSwitch.thumbTintColor = [UIColor greenColor];

}

Now that we are done with the tint colors on a switch, let’s move on to customizing the appearance of the switch using its on and off images, bearing in mind that custom on and off images are only for iOS 6 or older. iOS 7 ignores on and off images and uses only tint colors to customize its appearance. As mentioned before, both the on and the off images in a switch should be 77 points wide and 22 points tall. For this, I have prepared a new set of on and off images (in both normal and retina resolutions). I have added them to my Xcode project under the (retina) names of On@2x.png and Off@2x.png and I’ve also placed the non-retina flavor of the same images in the project. Now what we have to construct our switch but assign our custom on and off images to the switch, using the following properties on UISwitch:

onImage

As explained before, this will be the image that is displayed when the switch is in its on mode.

offImage

The image that represents the switch when it is in off mode.

And here is our code snippet to achieve this new look:

- (void)viewDidLoad
{
    [super viewDidLoad];

    /* Create the switch */
    self.mainSwitch = [[UISwitch alloc] initWithFrame:CGRectZero];
    self.mainSwitch.center = self.view.center;
    /* Make sure the switch won't appear blurry on iOS Simulator */
    self.mainSwitch.frame = [self roundedValuesInRect:self.mainSwitch.frame];
    [self.view addSubview:self.mainSwitch];

    /* Customize the switch */
    self.mainSwitch.onImage = [UIImage imageNamed:@"On"];
    self.mainSwitch.offImage = [UIImage imageNamed:@"Off"];

}

See Also

Recipe 1.2

1.4. Picking Values with the UIPickerView

Problem

You want to allow the users of your app to select from a list of values.

Solution

Use the UIPickerView class.

Discussion

A picker view is a graphical element that allows you to display series of values to your users and allow them to pick one. The Timer section of the Clock app on the iPhone is a great example of this (Figure 1-10).

A picker view on top of the screen
Figure 1-10. A picker view on top of the screen

As you can see, this specific picker view has two separate and independent visual elements. One is on the left and one is on the right. The element on the left is displaying hours (such as 0 hours, 1, 2, etc.) and the one on the right is displaying minutes (such as 10, 11, 12 mins, etc.). These two items are called components. Each component has rows. Any item in any of the components is in fact represented by a row, as we will soon see. For instance, in the left component, “0 hours” is a row, “1” is a row, etc.

Let’s go ahead and create a picker view on our view controller’s view. If you don’t know where your view controller’s source code is, please have a look at Recipe 1.2, where this subject is discussed.

First let’s go to the top of the .m (implementation) file of our view controller and define our picker view:

@interface ViewController ()
@property (nonatomic, strong) UIPickerView *myPicker;
@end

@implementation ViewController

...

Now let’s create the picker view in the viewDidLoad method of our view controller:

- (void)viewDidLoad{
  [super viewDidLoad];

  self.myPicker = [[UIPickerView alloc] init];
  self.myPicker.center = self.view.center;
  [self.view addSubview:self.myPicker];

}

It’s worth noting that in this example, we are centering our picker view at the center of our view. When you run this app on iOS 7 Simulator, you will see a blank screen because the picker on iOS 7 is white and so is the view controller’s background.

The reason this picker view is showing up as a plain white color is that we have not yet populated it with any values. Let’s do that. We do that by specifying a data source for the picker view and then making sure that our view controller sticks to the protocol that the data source requires. The data source of an instance of UIPickerView must conform to the UIPickerViewDataSource protocol, so let’s go ahead and make our view controller conform to this protocol in the .m file:

@interface ViewController () <UIPickerViewDataSource, UIPickerViewDelegate>
@property (nonatomic, strong) UIPickerView *myPicker;
@end

@implementation ViewController

...

Good. Let’s now change our code in the implementation file to make sure we select the current view controller as the data source of the picker view:

- (void)viewDidLoad{
  [super viewDidLoad];

  self.myPicker = [[UIPickerView alloc] init];
  self.myPicker.dataSource = self;
  self.myPicker.center = self.view.center;
  [self.view addSubview:self.myPicker];

}

After this, if you try to compile your application, you will get warnings from the compiler telling you that you have not yet implemented some of the methods that the UIPickerViewDataSource protocol wants you to implement. The way to fix this is to press Command+Shift+O, type in UIPickerViewDataSource, and press the Enter key on your keyboard. That will send you to the place in your code where this protocol is defined, where you will see something similar to this:

@protocol UIPickerViewDataSource<NSObject>
@required

// returns the number of 'columns' to display.
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView;

// returns the # of rows in each component..
- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component;
@end

Can you see the @required keyword there? That is telling us that whichever class wants to become the data source of a picker view must implement these methods. Good deal. Let’s go implement them in our view controller’s implementation file:

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{

    if ([pickerView isEqual:self.myPicker]){
        return 1;
    }

    return 0;

}

- (NSInteger)   pickerView:(UIPickerView *)pickerView
   numberOfRowsInComponent:(NSInteger)component{

    if ([pickerView isEqual:self.myPicker]){
        return 10;
    }

    return 0;
}

So what is happening here? Let’s have a look at what each one of these data source methods expects:

numberOfComponentsInPickerView:

This method passes you a picker view object as its parameter and expects you to return an integer, telling the runtime how many components you would like that picker view to render.

pickerView:numberOfRowsInComponent:

For each component that gets added to a picker view, you will need to tell the system how many rows you would like to render in that component. This method passes you an instance of picker view and you will need to return an integer indicating the number of rows to render for that component.

So in this case, we are asking the system to display 1 component with only 10 rows for a picker view that we have created before, called myPicker.

Compile and run your application on the iPhone Simulator (Figure 1-11). Ewww, what is that?

A picker view, not knowing what to render
Figure 1-11. A picker view, not knowing what to render

It looks like our picker view knows how many components it should have and how many rows it should render in that component but doesn’t know what text to display for each row. That is something we need to do now, and we do that by providing a delegate to the picker view. The delegate of an instance of UIPickerView has to conform to the UIPickerViewDelegate protocol and must implement all the @required methods of that protocol.

There is only one method in the UIPickerViewDelegate we are interested in: the pickerView:titleForRow:forComponent: method. This method will pass you the index of the current section and the index of the current row in that section for a picker view and it expects you to return an instance of NSString. This string will then get rendered for that specific row inside the component. In here, I would simply like to display the first row as Row 1, and then continue to Row 2, Row 3, etc., till the end. Remember, we also have to set the delegate property of our picker view:

self.myPicker.delegate = self;

And now we will handle the delegate method we just learned about:

- (NSString *)pickerView:(UIPickerView *)pickerView
             titleForRow:(NSInteger)row
            forComponent:(NSInteger)component{

    if ([pickerView isEqual:self.myPicker]){

        /* Row is zero-based and we want the first row (with index 0)
         to be rendered as Row 1 so we have to +1 every row index */
        return [NSString stringWithFormat:@"Row %ld", (long)row + 1];

    }

    return nil;

}

Now let’s run our app and see what happens (Figure 1-12).

A picker view with one section and a few rows
Figure 1-12. A picker view with one section and a few rows

Picker views in iOS 6 and older can highlight the current using a property called showsSelectionIndicator, which by default is set to NO. You can either directly set the value of this property to YES or use the setShowsSelectionIndicator: method of the picker view to turn this indicator on:

self.myPicker.showsSelectionIndicator = YES;

Now imagine that you have created this picker view in your final application. What is the use of a picker view if we cannot detect what the user has actually selected in each one of its components? Well, it’s good that Apple has already thought of that and given us the ability to ask the picker view what is selected. Call the selectedRowInComponent: method of a UIPickerView and pass the zero-based index of a component. The method will return an integer indicating zero-based index of the row that is currently selected in that component.

If you need to modify the values in your picker view at runtime, you need to make sure that your picker view reloads its data from its data source and delegate. To do that, you can either force all the components to reload their data, using the reloadAllComponents method, or you can ask a specific component to reload its data, using the reloadComponent: method and passing the index of the component that has to be reloaded.

See Also

Recipe 1.2

1.5. Picking the Date and Time with UIDatePicker

Problem

You want to allow the users of your app to select a date and time using an intuitive and ready-made user interface.

Solution

Use the UIDatePicker class.

Discussion

UIDatePicker is very similar to the UIPickerView class. The date picker is in fact a prepopulated picker view. A good example of the date picker control is in the Calendar app on the iPhone (Figure 1-13).

A date picker shown at the center of the screen
Figure 1-13. A date picker shown at the center of the screen

Let’s get started by first declaring a property of type UIDatePicker. Then we’ll allocate and initialize this property and add it to the view of our view controller:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) UIDatePicker *myDatePicker;
@end

@implementation ViewController

...

And now let’s instantiate the date picker, as planned:

- (void)viewDidLoad{
    [super viewDidLoad];
    self.myDatePicker = [[UIDatePicker alloc] init];
    self.myDatePicker.center = self.view.center;
    [self.view addSubview:self.myDatePicker];
}

Now let’s run the app and see how it looks in Figure 1-14.

A simple date picker
Figure 1-14. A simple date picker

You can see that the date picker, by default, has picked today’s date. The first thing that we need to know about date pickers is that they can have different styles or modes. This mode can be changed through the datePickerMode property, which is of type UIDatePickerMode:

typedef NS_ENUM(NSInteger, UIDatePickerMode) {
    UIDatePickerModeTime,
    UIDatePickerModeDate,
    UIDatePickerModeDateAndTime,
    UIDatePickerModeCountDownTimer,
};

Depending on what you need, you can set the mode of your date picker to any of the values listed in the UIDatePickerMode enumeration. I’ll show some of these as we go along.

Now that you have successfully displayed a date picker on the screen, you can attempt to retrieve its currently-selected date using its date property. Alternatively, you can call the date method on the date picker, like so:

NSDate *currentDate = self.myDatePicker.date;
NSLog(@"Date = %@", currentDate);

Just like the UISwitch class, a date picker sends action messages to its targets whenever the user has selected a different date. To respond to these messages, the receiver must add itself as the target of the date picker, using the addTarget:action:forControlEvents: method, like so:

- (void) datePickerDateChanged:(UIDatePicker *)paramDatePicker{

    if ([paramDatePicker isEqual:self.myDatePicker]){
        NSLog(@"Selected date = %@", paramDatePicker.date);
    }

}

- (void)viewDidLoad{
    [super viewDidLoad];
    self.myDatePicker = [[UIDatePicker alloc] init];
    self.myDatePicker.center = self.view.center;
    [self.view addSubview:self.myDatePicker];

    [self.myDatePicker addTarget:self
                          action:@selector(datePickerDateChanged:)
                forControlEvents:UIControlEventValueChanged];

}

Now, every time the user changes the date, you will get a message from the date picker.

A date picker also lets you set the minimum and the maximum dates that it can display. For this, let’s first switch our date picker mode to UIDatePickerModeDate and then, using the maximumDate and the minimumDate properties, adjust this range:

- (void)viewDidLoad{
    [super viewDidLoad];
    self.myDatePicker = [[UIDatePicker alloc] init];
    self.myDatePicker.center = self.view.center;
    self.myDatePicker.datePickerMode = UIDatePickerModeDate;
    [self.view addSubview:self.myDatePicker];


    NSTimeInterval oneYearTime = 365 * 24 * 60 * 60;
    NSDate *todayDate = [NSDate date];

    NSDate *oneYearFromToday = [todayDate
                                dateByAddingTimeInterval:oneYearTime];

    NSDate *twoYearsFromToday = [todayDate
                                 dateByAddingTimeInterval:2 * oneYearTime];

    self.myDatePicker.minimumDate = oneYearFromToday;
    self.myDatePicker.maximumDate = twoYearsFromToday;
}

With these two properties, we can then limit the user’s selection on the date to a specific range, as shown in Figure 1-15. In this example code, we have limited the user’s input of dates to the range of one year to two years from now.

Minimum and maximum dates applied to a date picker
Figure 1-15. Minimum and maximum dates applied to a date picker

If you want to use the date picker as a countdown timer, you must set your date picker mode to UIDatePickerModeCountDownTimer and use the countDownDuration property of the date picker to specify the default countdown duration. For instance, if you want to present a countdown picker to the user and set the default countdown duration to two minutes, write code like this:

- (void)viewDidLoad{
    [super viewDidLoad];
    self.myDatePicker = [[UIDatePicker alloc] init];
    self.myDatePicker.center = self.view.center;
    self.myDatePicker.datePickerMode = UIDatePickerModeCountDownTimer;
    [self.view addSubview:self.myDatePicker];

    NSTimeInterval twoMinutes = 2 * 60;
    [self.myDatePicker setCountDownDuration:twoMinutes];
}

The results are shown in Figure 1-16.

A two-minute countdown duration set on a date picker
Figure 1-16. A two-minute countdown duration set on a date picker

1.6. Implementing Range Pickers with UISlider

Problem

You would like to allow your users to specify a value within a range, using an easy-to-use and intuitive UI.

Solution

Use the UISlider class.

Discussion

You’ve certainly seen sliders before. Figure 1-17 shows an example.

The volume slider in control center
Figure 1-17. The volume slider in control center

To create a slider, instantiate an object of type UISlider. Let’s dive right in and create a slider and place it on our view controller’s view. We’ll start with our view controller’s implementation file:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) UISlider *mySlider;
@end

@implementation ViewController

...

And now let’s go to the viewDidLoad method and create our slider component. In this code, we are going to give our slider a range between 0 and 100 and set its default position to be halfway between start and end.

Note

The range of a slider has nothing to do with its appearance. We use the range specifiers of a slider to tell the slider to calculate its value based on the relative position within the range. For instance, if the range of a slider is provided as 0 to 100, when the knob on the slider is on the leftmost part, the value property of the slider is 0, and if the knob is to the rightmost side of the slider, the value property would be 100.

- (void)viewDidLoad{
    [super viewDidLoad];

    self.mySlider = [[UISlider alloc] initWithFrame:CGRectMake(0.0f,
                                                               0.0f,
                                                               200.0f,
                                                               23.0f)];
    self.mySlider.center = self.view.center;
    self.mySlider.minimumValue = 0.0f;
    self.mySlider.maximumValue = 100.0f;
    self.mySlider.value = self.mySlider.maximumValue / 2.0;
    [self.view addSubview:self.mySlider];
}

What do the results look like? You can now run the app on the simulator and you’ll get results like those shown in Figure 1-18.

A simple slider at the center of the screen
Figure 1-18. A simple slider at the center of the screen

We used a few properties of the slider to get the results we wanted. What were they?

minimumValue

Specifies the minimum value of the slider’s range.

maximumValue

Specifies the maximum value of the slider’s range.

value

The current value of the slider. This is a read/write property, meaning that you can both read from it and write to it. If you want the slider’s knob to be moved to this value in an animated mode, you can call the setValue:animated: method of the slider and pass YES as the animated parameter.

The little knob on a slider is called the thumb. If you wish to receive an event whenever the slider’s thumb has moved, you must add your object as the target of the slider, using the slider’s addTarget:action:forControlEvents: method:

- (void) sliderValueChanged:(UISlider *)paramSender{

    if ([paramSender isEqual:self.mySlider]){
        NSLog(@"New value = %f", paramSender.value);
    }

}

- (void)viewDidLoad{
    [super viewDidLoad];
    self.mySlider = [[UISlider alloc] initWithFrame:CGRectMake(0.0f,
                                                               0.0f,
                                                               200.0f,
                                                               23.0f)];
    self.mySlider.center = self.view.center;
    self.mySlider.minimumValue = 0.0f;
    self.mySlider.maximumValue = 100.0f;
    self.mySlider.value = self.mySlider.maximumValue / 2.0;
    [self.view addSubview:self.mySlider];

    [self.mySlider addTarget:self
                      action:@selector(sliderValueChanged:)
            forControlEvents:UIControlEventValueChanged];
}

If you run the application on the simulator now, you will notice that the sliderValueChanged: target method gets called whenever and as soon as the slider’s thumb moves. This might be what you want, but in some cases, you might need to get notified only after the user has let go of the thumb on the slider and let it settle. If you want to wait to be notified, set the continuous property of the slider to NO. This property, when set to YES (its default value), will call the slider’s targets continuously while the thumb moves.

The iOS SDK also gives you the ability to modify how a slider looks. For instance, the thumb on the slider can have a different image. To change the image of the thumb, simply use the setThumbImage:forState: method and pass an image along with a second parameter that can take any of these values:

UIControlStateNormal

The normal state of the thumb, with no user finger on this component.

UIControlStateHighlighted

The image to display for the thumb while the user is moving her finger on this component.

I have prepared two images: one for the normal state of the thumb and the other one for the highlighted (touched) state of the thumb. Let’s go ahead and add them to the slider:

[self.mySlider setThumbImage:[UIImage imageNamed:@"ThumbNormal.png"]
                    forState:UIControlStateNormal];
[self.mySlider setThumbImage:[UIImage imageNamed:@"ThumbHighlighted.png"]
                    forState:UIControlStateHighlighted];

And now let’s have a look and see how our normal thumb image looks in the simulator (Figure 1-19).

A slider with a custom thumb image
Figure 1-19. A slider with a custom thumb image

1.7. Customizing the UISlider

Problem

You are using the default appearance of the UISlider UI component and now you want to customize this look and feel.

Solution

Either modify the tint colors of the different parts of the slider, or provide your own images for the parts.

Discussion

Apple has done a great job giving us methods to customize UI components in iOS SDK. One customization is to modify the tint colors of various parts of the UI component. Let’s take a simple UISlider as an example. I have broken it down into its different UI components in Figure 1-20.

Different components of a UISlider
Figure 1-20. Different components of a UISlider

For each of these components in UISlider, a method and property exists that allow you to change the appearance of the slider. The easiest of these properties to use are the ones that modify the tint color of these components. The properties are:

minimumTrackTintColor

This property sets the tint color of the minimum value track view.

thumbTintColor

This property, as its name shows, sets the tint color of the thumb view.

maximumTrackTintColor

This property sets the tint color of the maximum value track view.

All these properties are of type UIColor.

The following sample code instantiates a UISlider and places it at the center of the view of the view controller. It also sets the tint color of the minimum value tracking view of the slider to red, the tint color of the thumb view of the slider to black, and the tint color of the maximum value tracking view of the slider to green:

- (void)viewDidLoad{
    [super viewDidLoad];

    /* Create  the slider */
    self.slider = [[UISlider alloc] initWithFrame:CGRectMake(0.0f,
                                                             0.0f,
                                                             118.0f,
                                                             23.0f)];
    self.slider.value = 0.5;
    self.slider.minimumValue = 0.0f;
    self.slider.maximumValue = 1.0f;
    self.slider.center = self.view.center;
    [self.view addSubview:self.slider];

    /* Set the tint color of the minimum value */
    self.slider.minimumTrackTintColor = [UIColor redColor];

    /* Set the tint color of the thumb */
    self.slider.maximumTrackTintColor = [UIColor greenColor];

    /* Set the tint color of the maximum value */
    self.slider.thumbTintColor = [UIColor blackColor];

}

If you run the app now, you will see something similar to Figure 1-21.

The tint colors of all the different components of a slider are modified
Figure 1-21. The tint colors of all the different components of a slider are modified

Sometimes you may want to have more control over how a slider looks on the screen. For this, tint colors may not be sufficient. That’s why Apple has provided other ways of modifying the look and feel of a slider, allowing you to provide images for different components in the slider. These images are:

Minimum value image

This is the image that will be displayed to the outer-left side of the slider. By default, no image is provided for the minimum value image, so you cannot really see this if you create a new slider on a view. You can use this image to give your users an indication of what the minimum value in your slider may mean in the context of your app. For instance, in an app where the user is allowed to increase or decrease the brightness of the screen, the minimum value image may display a dim lightbulb, suggesting to users that moving the thumb in the slider to the left (toward the minimum value) will reduce the brightness of the screen further. To change this image, use the setMinimumValueImage: instance method of the slider. The image needs to be 23 points wide and 23 points tall. Obviously, for retina displays, simply provide the same image but twice as big.

Minimum track image

This is the image that will be displayed for the slider’s track on the left side of the thumb. To change this image, use the setMinimumTrackImage:forState: instance method of the slider. The image needs to be 11 points wide and 9 points tall, and be constructed as a resizable image (see Recipe 20.5). Obviously, you need to provide a 46x46 image for retina displays.

Thumb image

The image for the thumb, the only moving component in the slider. To change this image, use the setThumbImage:forState: instance method of the slider. The image needs to be 23 points wide and 23 points tall.

Maximum track image

The image for the track of the slider to the right of the thumb. To change this image, use the setMaximumTrackImage:forState: instance method of the slider. The image needs to be 11 points wide and 9 points tall, and be constructed as a resizable image (see Recipe 20.5).

Maximum value image

The maximum value image is the image that gets displayed on the outer-right side of the slider. This is similar to the minimum value image, but of course depicts the maximum value of the slider instead. To continue the earlier example of a brightness slider, the image for the maximum value can be a bright light with rays emitting from it, suggesting to the user that the further they move the slider to the right, the brighter the display gets. To change this image, use the setMaximumValueImage: instance method of the slider. The image needs to be 23 points wide and 23 points tall.

Note

The images that you provide for the minimum and the maximum track need to be resizable. For more information about resizable images, see Recipe 20.5.

For the sake of this exercise, I have created five unique images for each one of the components of the slider. I’ve made sure that the minimum and the maximum track images are resizable images. What I am trying to achieve with the customization of this slider component is to make the user think that they are changing the temperature settings of a room, where moving the slider to the left means less heat and moving to the right means more heat. So here is the code that creates a slider and skins its various components:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) UISlider *slider;
@end

@implementation ViewController

/*
 This method returns a resizable image for the
 minimum track component of the slider
 */
- (UIImage *) minimumTrackImage{
    UIImage *result = [UIImage imageNamed:@"MinimumTrack"];
    UIEdgeInsets edgeInsets;
    edgeInsets.left = 4.0f;
    edgeInsets.top = 0.0f;
    edgeInsets.right = 0.0f;
    edgeInsets.bottom = 0.0f;
    result = [result resizableImageWithCapInsets:edgeInsets];
    return result;
}

/*
 Similar to the previous method, this one returns the resizable maximum
 track image for the slider
 */
- (UIImage *) maximumTrackImage{
    UIImage *result = [UIImage imageNamed:@"MaximumTrack"];
    UIEdgeInsets edgeInsets;
    edgeInsets.left = 0.0f;
    edgeInsets.top = 0.0f;
    edgeInsets.right = 3.0f;
    edgeInsets.bottom = 0.0f;
    result = [result resizableImageWithCapInsets:edgeInsets];
    return result;
}

- (void)viewDidLoad{
    [super viewDidLoad];

    /* Create  the slider */
    self.slider = [[UISlider alloc] initWithFrame:CGRectMake(0.0f,
                                                             0.0f,
                                                             218.0f,
                                                             23.0f)];
    self.slider.value = 0.5;
    self.slider.minimumValue = 0.0f;
    self.slider.maximumValue = 1.0f;
    self.slider.center = self.view.center;
    [self.view addSubview:self.slider];

    /* Change the minimum value image */
    [self.slider setMinimumValueImage:[UIImage imageNamed:@"MinimumValue"]];

    /* Change the minimum track image */
    [self.slider setMinimumTrackImage:[self minimumTrackImage]
                             forState:UIControlStateNormal];

    /* Change the thumb image for both untouched and touched states */
    [self.slider setThumbImage:[UIImage imageNamed:@"Thumb"]
                      forState:UIControlStateNormal];
    [self.slider setThumbImage:[UIImage imageNamed:@"Thumb"]
                      forState:UIControlStateHighlighted];

    /* Change the maximum track image */
    [self.slider setMaximumTrackImage:[self maximumTrackImage]
                             forState:UIControlStateNormal];

    /* Change the maximum value image */
    [self.slider setMaximumValueImage:[UIImage imageNamed:@"MaximumValue"]];

}

Note

The slider in iOS 7 has a completely new look, as you can guess, very streamlined and slim and thin. The height of minimum and maximum track images in iOS 7 is only 1 point wide, so setting an image for these components is absolutely useless and won’t look very good anyway. Therefore, to skin these components of a UISlider in iOS 7, it is recommended that you use the tint colors instead of assigning custom images to it.

See Also

Recipe 1.6

1.8. Grouping Compact Options with UISegmentedControl

Problem

You would like to present a few options to your users from which they can pick an option, through a UI that is compact, simple, and easy to understand.

Solution

Use the UISegmentedControl class, an example of which is shown in Figure 1-22.

A segmented control displaying four options
Figure 1-22. A segmented control displaying four options

Discussion

A segmented control is a UI component that allows you to display, in a compact UI, series of options for the user to choose from. To show a segmented control, create an instance of UISegmentedControl. Let’s start with our view controller’s .m file:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) UISegmentedControl *mySegmentedControl;
@end

@implementation ViewController

...

And create the segmented control in the viewDidLoad method of your view controller:

- (void)viewDidLoad{
    [super viewDidLoad];

    NSArray *segments = [[NSArray alloc] initWithObjects:
                         @"iPhone",
                         @"iPad",
                         @"iPod",
                         @"iMac", nil];

    self.mySegmentedControl = [[UISegmentedControl alloc]
                               initWithItems:segments];
    self.mySegmentedControl.center = self.view.center;
    [self.view addSubview:self.mySegmentedControl];

}

We are simply using an array of strings to provide the different options that our segmented control has to display. We initialize our segmented control using the initWithObjects: initializer and pass the array of strings and images to the segmented control. The results will look like what we saw in Figure 1-22.

Now the user can pick one of the options in the segmented control. Let’s say she has picked iPad. The segmented control will then change its user interface to show the user what option she has selected, as depicted in Figure 1-23.

User has selected one of the items in a segmented control
Figure 1-23. User has selected one of the items in a segmented control

Now the question is, how do you recognize when the user selects a new option in a segmented control? The answer is simple. Just as with a UISwitch or a UISlider, use the addTarget:action:forControlEvents: method of the segmented control to add a target to it. Provide the value of UIControlEventValueChanged for the forControlEvents parameter, because that is the event that gets fired when the user selects a new option in a segmented control:

- (void) segmentChanged:(UISegmentedControl *)paramSender{

    if ([paramSender isEqual:self.mySegmentedControl]){
        NSInteger selectedSegmentIndex = [paramSender selectedSegmentIndex];

        NSString  *selectedSegmentText =
        [paramSender titleForSegmentAtIndex:selectedSegmentIndex];

        NSLog(@"Segment %ld with %@ text is selected",
              (long)selectedSegmentIndex,
              selectedSegmentText);
    }
}

- (void)viewDidLoad{
    [super viewDidLoad];

    NSArray *segments = @[
                          @"iPhone",
                          @"iPad",
                          @"iPod",
                          @"iMac"
                          ];

    self.mySegmentedControl = [[UISegmentedControl alloc]
                               initWithItems:segments];
    self.mySegmentedControl.center = self.view.center;
    [self.view addSubview:self.mySegmentedControl];

    [self.mySegmentedControl addTarget:self
                                action:@selector(segmentChanged:)
                      forControlEvents:UIControlEventValueChanged];
}

If the user starts from the left side and selects each of the options in Figure 1-22, all the way to the right side of the control, the following text will print out to the console:

Segment 0 with iPhone text is selected
Segment 1 with iPad text is selected
Segment 2 with iPod text is selected
Segment 3 with iMac text is selected

As you can see, we used the selectedSegmentIndex method of the segmented control to find the index of the currently selected item. If no item is selected, this method returns the value –1. We also used the titleForSegmentAtIndex: method. Simply pass the index of an option in the segmented control to this method, and the segmented control will return the text for that item. Simple, isn’t it?

As you might have noticed, once the user selects an option in a segmented control, that option will get selected and will remain selected, as shown in Figure 1-23. If you want the user to be able to select an option but you would like the button for that option to bounce back to its original shape once it has been selected (just like a normal button that bounces back up once it is tapped), you need to set the momentary property of the segmented control to YES:

self.mySegmentedControl.momentary = YES;

One of the really neat features of segmented controls is that they can contain images instead of text. To do this, simply use the initWithObjects: initializer method of the UISegmentedControl class and pass the strings and images that will be used to initialize the segmented UI control:

- (void)viewDidLoad{
    [super viewDidLoad];

    NSArray *segments = @[
                          @"iPhone",
                          [UIImage imageNamed:@"iPad"],
                          @"iPod",
                          @"iMac",
                         ];

    self.mySegmentedControl = [[UISegmentedControl alloc]
                               initWithItems:segments];

    CGRect segmentedFrame = self.mySegmentedControl.frame;
    segmentedFrame.size.height = 128.0f;
    segmentedFrame.size.width = 300.0f;
    self.mySegmentedControl.frame = segmentedFrame;

    self.mySegmentedControl.center = self.view.center;

    [self.view addSubview:self.mySegmentedControl];
}

Note

In this example, the iPad file is simply an image of an iPad that’s been added to our project.

In iOS 7, Apple has deprecated the segmentedControlStyle property of the UISegmentedControl class, so segmented controls have only a single default style. We can no longer modify this style.

1.9. Presenting and Managing Views with UIViewController

Problem

You want to switch between different views in your application.

Solution

Use the UIViewController class.

Discussion

Apple’s strategy for iOS development was to use the Model-View-Controller (MVC) division of labor. Views are what get displayed to users, while the model is the data that the app manages, or the engine of the app. The controller is the bridge between the model and the view. The controller, or in this case, view controller, manages the relationship between the view and the model. Why doesn’t the view do that instead? Well, the answer is quite simple: the view’s code would get messy and that design choice would tightly couple our views with the model, which is not a good practice.

View controllers can be loaded from .xib files (for use with Interface Builder), or simply be created programmatically. We will first have a look at creating a view controller without a .xib file.

Xcode helps us create view controllers. Now that you have created an application using the Empty Application template in Xcode, follow these steps to create a new view controller for your app:

  1. In Xcode, select the File menu and then choose New New File...

  2. In the New File dialog, make sure iOS is the selected category on the left and that Cocoa Touch is the chosen subcategory. Once you’ve done that, select the New Objective-C class item on the righthand side and press Next, as shown in Figure 1-24.

New Objective-C subclass
Figure 1-24. New Objective-C subclass
  1. On the next screen, make sure that the Subclass of the text field says UIViewController. Also make sure that neither the “Targeted for iPad” nor “With XIB for user interface” checkboxes are selected, as shown in Figure 1-25. Press Next.

A custom view controller with no .xib file
Figure 1-25. A custom view controller with no .xib file
  1. On the next screen (Save As), give your view controller’s file the name of ViewController and press the Create button, as shown in Figure 1-26.

Saving a view controller without a .xib file
Figure 1-26. Saving a view controller without a .xib file
  1. Now find your application delegate’s .m file, which is usually called AppDelegate.m. In this file, declare a property of type ViewController:

#import "AppDelegate.h"
#import "ViewController.h"

@interface AppDelegate ()
@property (nonatomic, strong) ViewController *viewController;
@end

@implementation AppDelegate

...
  1. Now find the application:didFinishLaunchingWithOptions: method of the app delegate, and instantiate the view controller and set it as the root view controller of your window:

- (BOOL)            application:(UIApplication *)application
  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

    self.viewController = [[ViewController alloc] initWithNibName:nil
                                                           bundle:nil];

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

    /* Make our view controller the root view controller */
    self.window.rootViewController = self.viewController;

    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

Go ahead and run the app on the simulator. You will now see a plain white view on the screen. Congratulations! You just created a view controller and now you have access to the view controller and its view object.

While creating the view controller (Figure 1-25), if you had selected the “With XIB for user interface” checkbox, Xcode would have also generated a .xib file for you. In that case, you can load your view controller from that .xib file by passing the .xib file’s name (without the extension) to the initWithNibName parameter of the initWithNibName:bundle: method of the view controller, like so:

- (BOOL)            application:(UIApplication *)application
  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

    self.viewController = [[ViewController alloc]
                           initWithNibName:@"ViewController"
                           bundle:nil];

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

    /* Make our view controller the root view controller */
    self.window.rootViewController = self.viewController;

    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

If you did create a .xib file while creating your view controller, you can now select that file in Xcode and design your user interface with Interface Builder.

1.10. Presenting Sharing Options with UIActivityViewController

Problem

You want to be able to allow your users to share content inside your apps with their friends, through an interface similar to that shown in Figure 1-27 that provides different sharing options available in iOS, such as Facebook and Twitter.

The activity view controller displayed on an iOS device
Figure 1-27. The activity view controller displayed on an iOS device

Solution

Create an instance of the UIActivityViewController class and share your content through this class, as we will see in the Discussion section of this recipe.

Note

The instances of UIActivityViewController must be presented modally on the iPhone and inside a popover on an iPad. For more information about popovers, refer to Recipe 1.29.

Discussion

There are many sharing options inside iOS, all built into the core of the OS. For instance, Facebook and Twitter integration is now an integral part of the core of iOS and you can share pretty much any content from anywhere you want. Third-party apps like ours can also use all the sharing functionalities available in iOS without having to think about the low-level details of these services and how iOS provides these sharing options. The beauty of this whole thing is that you mention what you want to share and iOS will pick the sharing options that are capable of handling the items you want to share. For instance, if you want to share images and text, iOS will display many more items to you than if you want to share an audio file.

Sharing data is very easy in iOS. All you have to do is instantiate the UIActivityViewController class using its initWithActivityItems:applicationActivities: initializer. The parameters to this method are:

initWithActivityItems

The array of items that you want to share. These can be instances of NSString, UIImage, or instances of any of your custom classes that conform to the UIActivityItemSource protocol. We will talk about this protocol later in detail.

applicationActivities

An array of instances of UIActivity that represent the activities that your own application supports. For instance, you can indicate here whether your application can handle its own sharing of images and strings. We will not go into detail about this parameter, for now, and will simply pass nil as its value, telling iOS that we want to stick to the system sharing options.

So let’s say that you have a text field where the user can enter text to be shared, and a Share button right near it. When the user presses the Share button, you will simply pass the text of the text field to your instance of the UIActivityViewController class. Here is our code. We are writing this code for iPhone, so we will present our activity view controller as a modal view controller.

Because we are putting a text field on our view controller, we need to make sure that we are handling its delegate messages, especially the textFieldShouldReturn: method of the UITextFieldDelegate protocol. Therefore, we are going to elect our view controller as the delegate of the text field. Also, we are going to attach an action method to our Share button. Once the button is tapped, we want to make sure there is something in the text field to share. If there isn’t, we will simply display an alert to the user telling them why we cannot share the content of the text field. If there is some text in the text field, we will pop up an instance of the UIActivityViewController class. So let’s begin with the implementation file of our view controller and define our UI components:

@interface ViewController () <UITextFieldDelegate>
@property (nonatomic, strong) UITextField *textField;
@property (nonatomic, strong) UIButton *buttonShare;
@property (nonatomic, strong) UIActivityViewController *activityViewController;
@end

...

After this, we will write two methods for our view controller, each of which is able to create one of our UI components and place it on our view controller’s view. One will create the text field, and the other will create the button next to it:

- (void) createTextField{
    self.textField = [[UITextField alloc] initWithFrame:CGRectMake(20.0f,
                                                                   35.0f,
                                                                   280.0f,
                                                                   30.0f)];
    self.textField.translatesAutoresizingMaskIntoConstraints = NO;
    self.textField.borderStyle = UITextBorderStyleRoundedRect;
    self.textField.placeholder = @"Enter text to share...";
    self.textField.delegate = self;
    [self.view addSubview:self.textField];
}

- (void) createButton{
    self.buttonShare = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    self.buttonShare.translatesAutoresizingMaskIntoConstraints = NO;
    self.buttonShare.frame = CGRectMake(20.0f, 80.0f, 280.0f, 44.0f);
    [self.buttonShare setTitle:@"Share" forState:UIControlStateNormal];

    [self.buttonShare addTarget:self
                         action:@selector(handleShare:)
               forControlEvents:UIControlEventTouchUpInside];

    [self.view addSubview:self.buttonShare];
}

Once we are done with that, we just have to call these two methods in the viewDidLoad method of our view controller. This will allow the UI components to be placed on the view of our view controller:

- (void)viewDidLoad{

    [super viewDidLoad];
    [self createTextField];
    [self createButton];

}

In the textFieldShouldReturn: method, all we do is dismiss the keyboard in order to resign the text field’s active state. This simply means that when a user has been editing the text field and then presses the Return or Enter button on the keyboard, the keyboard should be dismissed. Bear in mind that the createTextField method that we just coded has set our view controller as the delegate of the text field. So we have to implement the aforementioned method as follows:

- (BOOL) textFieldShouldReturn:(UITextField *)textField{
    [textField resignFirstResponder];
    return YES;
}

Last but not least is the handler method of our button. As you saw, the createButton method creates the button for us and elects the handleShare: method to handle the touch down inside action of the button. So let’s code this method:

- (void) handleShare:(id)paramSender{

    if ([self.textField.text length] == 0){
        NSString *message = @"Please enter a text and then press Share";
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil
                                                            message:message
                                                           delegate:nil
                                                  cancelButtonTitle:@"OK"
                                                  otherButtonTitles:nil];
        [alertView show];
        return;
    }

    self.activityViewController = [[UIActivityViewController alloc]
                                   initWithActivityItems:@[self.textField.text]
                                   applicationActivities:nil];
    [self presentViewController:self.activityViewController
                       animated:YES
                     completion:^{
                         /* Nothing for now */
                     }];

}

Now if you run the app, enter some text in the text field, and then press the Share button, you will see something similar to Figure 1-28.

Sharing options displayed for the instance of string that we are trying to share
Figure 1-28. Sharing options displayed for the instance of string that we are trying to share

You can also have sharing options displayed as soon as your view controller is displayed on the screen. The viewDidAppear method of your view controller will be called when the view of your view controller is displayed on the screen and is guaranteed to be in the view hierarchy of your app, meaning that you can now display other views on top of your view controller’s view.

Warning

Do not attempt to present the activity view controller in the viewDidLoad method of your view controller. At that stage in the app, your view controller’s view is still not attached to the view hierarchy of the application, so attempting to present a view controller on the view will not work. Your view must be present in the hierarchy of the views for your modal views to work. For this reason, you need to present the sharing view controller in the viewDidAppear method of your view controller.

See Also

Recipe 1.29

1.11. Presenting Custom Sharing Options with UIActivityViewController

Problem

You want your app to participate in the list of apps that can handle sharing in iOS and appear in the list of available activities displayed in the activity view controller (see Figure 1-27).

You may need something like this, for example, when you have a text-editing app and when the user presses the Share button, you want a custom item that says “Archive” to appear in the activity view controller. When the user presses the Archive button, the text inside your app’s editing area will get passed to your custom activity and your activity can then archive that text into the filesystem on the iOS device.

Solution

Create a class of type UIActivity. In other words, subclass the aforementioned class and give a name (whatever you like) to your new class. Instances of the subclasses of this class can be passed to the initWithActivityItems:applicationActivities: initializer of the UIActivityViewController class, and if they implement all the required methods of the UIActivity class, iOS will display them in the activity view controller.

Discussion

The initWithActivityItems:applicationActivities: method’s first parameter accepts values of different types. These values can be strings, numbers, images, etc.—any object, really. When you present an activity controller with an array of arbitrary objects passed to the initWithActivityItems parameter, iOS will go through all the available system activities, like Facebook and Twitter, and will ask the user to pick an activity that suits her needs best. After the user picks an activity, iOS will pass the type of the objects in your array to the registered system activity that the user picked. Those activities can then check the type of the objects you are trying to share and decide whether they can handle those objects or not. They communicate this to iOS through a specific method that they will implement in their classes.

So let’s say that we want to create an activity that can reverse any number of strings that are handed to it. Remember that when your app initializes the activity view controller through the initWithActivityItems:applicationActivities: method, it can pass an array of arbitrary objects to the first parameter of this method. So our activity is going to peek at all these objects in this arbitrary array, and if they are all strings, it is going to reverse them and then display all the reversed strings in an alert view.

  1. Subclass UIActivity as shown here:

#import <UIKit/UIKit.h>

@interface StringReverserActivity : UIActivity

@end
  1. Since our activity is going to be responsible for displaying an alert view to the user when an array of strings is passed to us, we need to ensure that our activity conforms to the UIAlertViewDelegate protocol and marks our activity as “finished” when the user dismisses the alert view, like so:

#import "StringReverserActivity.h"

@interface StringReverserActivity () <UIAlertViewDelegate>
@property (nonatomic, strong) NSArray *activityItems;
@end

@implementation StringReverserActivity

- (void)            alertView:(UIAlertView *)alertView
    didDismissWithButtonIndex:(NSInteger)buttonIndex{
    [self activityDidFinish:YES];
}
  1. Next, override the activityType method of your activity. The return value of this method is an object of type NSString that is a unique identifier of your activity. This value will not be displayed to the user and is just for iOS to keep track of your activity’s identifier. There are no specific values that you are asked to return from this method and no guidelines available from Apple, but we will follow the reverse-domain string format and use our app’s bundle identifier and append the name of our class to the end of it. So if our bundle identifier is equal to com.pixolity.ios.cookbook.myapp and our class name is StringReverserActivity, we will return com.pixolity.ios.cookbook.myapp.StringReverserActivity from this method, like so:

- (NSString *) activityType{
    return [[NSBundle mainBundle].bundleIdentifier
            stringByAppendingFormat:@".%@", NSStringFromClass([self class])];
}
  1. The next method to override is the activityTitle method, which should return a string to be displayed to the user in the activity view controller. Make sure this string is short enough to fit into the activity view controller:

- (NSString *) activityTitle{
    return @"Reverse String";
}
  1. The next method is activityImage, which has to return an instance of UIImage that gets displayed in the activity view controller. Make sure that you provide both retina and non-retina versions of the image for both iPad and iPhone/iPod. The iPad retina image has to be 110×110 pixels and the iPhone retina image has to be 86×86 pixels. Obviously, divide these dimensions by 2 to get the width and the height of the non-retina images. iOS uses only the alpha channel in this image, so make sure your image’s background is transparent and that you illustrate your image with the color white or the color black. I have already created an image in my app’s image assets section and I’ve named the image “Reverse”, as you can see in Figure 1-29. Here is our code then:

- (UIImage *) activityImage{
    return [UIImage imageNamed:@"Reverse"];
}
Our asset category contains images for our custom activity
Figure 1-29. Our asset category contains images for our custom activity
  1. Implement the canPerformWithActivityItems: method of your activity. This method’s parameter is an array that will be set when an array of activity items is passed to the initializer of the activity view controller. Remember, these are objects of arbitrary type. The return value of your method will be a boolean indicating whether you can perform your actions on any of the given items or not. For instance, our activity can reverse any number of strings that it is given. So if we find one string in the array, that is good enough for us because we know we will later be able to reverse that string. If we are given an array of 1,000 objects that contains only 2 strings, we will still accept it. But if we are given an array of 1,000 objects, none of which are of our acceptable type, we will reject this request by returning NO from this method:

- (BOOL) canPerformWithActivityItems:(NSArray *)activityItems{

    for (id object in activityItems){
        if ([object isKindOfClass:[NSString class]]){
            return YES;
        }
    }

    return NO;

}
  1. Now implement the prepareWithActivityItems: method of your activity, whose parameter is of type NSArray. This method gets called if you returned YES from the canPerformWithActivityItems: method. You have to retain the given array for later use. You don’t really actually have to retain the whole array. You may choose to retain only the objects that you need in this array, such as the string objects.

- (void) prepareWithActivityItems:(NSArray *)activityItems{

    NSMutableArray *stringObjects = [[NSMutableArray alloc] init];
    for (id object in activityItems){
        if ([object isKindOfClass:[NSString class]]){
            [stringObjects addObject:object];
        }
    }

    self.activityItems = [stringObjects copy];
}
  1. Last but not least, you need to implement the performActivity method of your activity, which gets called when iOS wants you to actually perform your actions on the list of previously-provided arbitrary objects. In this method, basically, you have to perform your work. In our activity, we are going to go through the array of string objects that we extracted from this arbitrary array, reverse all of them, and display them to the user using an alert view:

- (NSString *) reverseOfString:(NSString *)paramString{

    NSMutableString *reversed = [[NSMutableString alloc]
                                 initWithCapacity:paramString.length];

    for (NSInteger counter = paramString.length - 1;
         counter >= 0;
         counter--){
        [reversed appendFormat:@"%c", [paramString characterAtIndex:counter]];
    }

    return [reversed copy];

}

- (void) performActivity{

    NSMutableString *reversedStrings = [[NSMutableString alloc] init];

    for (NSString *string in self.activityItems){
        [reversedStrings appendString:[self reverseOfString:string]];
        [reversedStrings appendString:@"\n"];
    }

    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Reversed"
                                                        message:reversedStrings
                                                       delegate:self
                                              cancelButtonTitle:@"OK"
                                              otherButtonTitles:nil];

    [alertView show];

}

We are done with the implementation of our activity class. Now let’s go to our view controller’s implementation file and display the activity view controller with our custom activity in the list:

#import "ViewController.h"
#import "StringReverserActivity.h"

@implementation ViewController

- (void) viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];

    NSArray *itemsToShare = @[
                              @"Item 1",
                              @"Item 2",
                              @"Item 3",
                              ];

    UIActivityViewController *activity =
        [[UIActivityViewController alloc]
         initWithActivityItems:itemsToShare
         applicationActivities:@[[StringReverserActivity new]]];

    [self presentViewController:activity animated:YES completion:nil];
}

@end

When the app runs for the first time, you will see something similar to Figure 1-30 on the screen:

Our custom Reverse String activity is showing in the list of available activities
Figure 1-30. Our custom Reverse String activity is showing in the list of available activities

If you now tap on the Reverse String item in the list, you should see something similar to that shown in Figure 1-31:

Our string reverser activity in action
Figure 1-31. Our string reverser activity in action

See Also

Recipe 1.10

1.12. Implementing Navigation with UINavigationController

Problem

You would like to allow your users to move from one view controller to the other with a smooth and built-in animation.

Solution

Use an instance of UINavigationController.

Discussion

If you’ve used an iPhone, iPod touch, or iPad before, chances are that you have already seen a navigation controller in action. For instance, if you go to the Settings app on your phone and then press an option such as Wallpaper (Figure 1-32), you will see the Settings’ main screen get pulled out of the screen from the left and the Wallpaper screen pushing its way into the screen from the right. That is the magic of navigation controllers. They allow you to push view controllers onto a stack and pop them from the stack. The view controller on top of the stack is the top view controller and is the one seen by the user at that moment. So only the top view controller gets displayed to the user, and is changed either by popping (removing) it or by pushing another view controller onto the stack.

Settings view controller pushing the Wallpaper view controller
Figure 1-32. Settings view controller pushing the Wallpaper view controller

Now we are going to add a navigation controller to our project, but we need a project first. Please follow the instructions in Recipe 1.9 to create an empty application with a simple view controller. In this recipe, we will expand on Recipe 1.9. Let’s start with the .m file of our app delegate:

#import "AppDelegate.h"
#import "FirstViewController.h"

@interface AppDelegate ()
@property (nonatomic, strong) UINavigationController *navigationController;
@end

@implementation AppDelegate

...

Now we have to initialize our navigation controller using its initWithRootViewController: method and pass our root view controller as its parameter. Then we will set the navigation controller as the root view controller of our window. Don’t get confused here. UINavigationController is actually a subclass of UIViewController and our window’s rootViewController property accepts any object of type UIViewController, so if we want the root view controller of our window to be a navigation controller, we simply set our navigation controller as the root view controller of the window:

- (BOOL)            application:(UIApplication *)application
  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

    FirstViewController *viewController = [[FirstViewController alloc]
                                           initWithNibName:nil
                                           bundle:nil];

    self.navigationController = [[UINavigationController alloc]
                                 initWithRootViewController:viewController];

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

    self.window.rootViewController = self.navigationController;

    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

Now let’s run our app in the simulator, as shown in Figure 1-33.

Our root view controller displayed inside a navigation controller
Figure 1-33. Our root view controller displayed inside a navigation controller

Note

The root view controller’s implementation file is creating the button in the center of the screen (shown in Figure 1-33). We will get to the implementation of that file soon.

The first thing you might notice in Figure 1-33 is the bar on top of the screen. The screen isn’t plain white anymore. What’s the new widget? A navigation bar. We will be using that bar a lot for navigation, placing buttons there, and so forth. That bar is also capable of displaying a title. Each view controller specifies a title for itself, and the navigation controller will automatically display that title once the view controller is pushed into the stack.

Let’s go to our root view controller’s implementation file, inside the viewDidLoad method, and set the title property of our view controller to First Controller. We’ll also create our button there. When the user presses this button, we want to display the second view controller on the screen:

#import "FirstViewController.h"
#import "SecondViewController.h"

@interface FirstViewController ()
    @property (nonatomic, strong) UIButton *displaySecondViewController;
@end

@implementation FirstViewController

- (void) performDisplaySecondViewController:(id)paramSender{
    SecondViewController *secondController = [[SecondViewController alloc]
                                              initWithNibName:nil
                                              bundle:NULL];
    [self.navigationController pushViewController:secondController
                                         animated:YES];
}

- (void)viewDidLoad{
    [super viewDidLoad];
    self.title = @"First Controller";

    self.displaySecondViewController = [UIButton buttonWithType:UIButtonTypeSystem];

    [self.displaySecondViewController setTitle:@"Display Second View Controller"
                                      forState:UIControlStateNormal];
    [self.displaySecondViewController sizeToFit];
    self.displaySecondViewController.center = self.view.center;

    [self.displaySecondViewController addTarget:self
                                         action:@selector(performDisplaySecondViewController:)
                               forControlEvents:UIControlEventTouchUpInside];


    [self.view addSubview:self.displaySecondViewController];
}

@end

Now let’s go and create this second view controller, without a .xib file, and call it SecondViewController. Follow the same process that you learned in Recipe 1.9. Once you are done creating this view controller, give it a title of Second Controller.

#import "SecondViewController.h"

@implementation SecondViewController

- (void)viewDidLoad{
    [super viewDidLoad];
    self.title = @"Second Controller";
}

Now what we want to do is “pop” from the second view controller back to the first view controller, 5 seconds after the second view controller is displayed to the screen. For that we are using the performSelector:withObject:afterDelay: method of NSObject to call our new method, goBack, five seconds after our second view controller successfully displays its view. In the goBack method, we are simply using the navigationController property of our view controller (this is built into UIViewController and is not something that we coded) to pop back to the instance of FirstViewController, using the popViewControllerAnimated: method of our navigation controller that takes a boolean as a parameter. If this boolean value is set to YES, the transition back to the previous view controller will be animated, and if NO, it won’t be. When the second view controller is displayed on the screen, you will see something similar to that shown in Figure 1-34.

A view controller is pushed on top of another one
Figure 1-34. A view controller is pushed on top of another one
#import "SecondViewController.h"

@implementation SecondViewController

- (void)viewDidLoad{
    [super viewDidLoad];
    self.title = @"Second Controller";
}

- (void) goBack{
    [self.navigationController popViewControllerAnimated:YES];
}

- (void) viewDidAppear:(BOOL)paramAnimated{
    [super viewDidAppear:paramAnimated];
    [self performSelector:@selector(goBack)
               withObject:nil
               afterDelay:5.0f];
}

@end

You can see that the navigation bar is displaying the title of the top view controller and even sports a back button that will take the user back to the previous view controller. You can push as many view controllers as you like into the stack and the navigation controller will work the navigation bar to display the relevant back buttons that allow the user to back through your application’s UI, all the way to the first screen.

So if you open the app in the simulator now and press the button on the first view controller, you will see that the second view controller will automatically get displayed on the screen. Wait five seconds now on the second view controller and it will automatically go back to the first view controller.

See Also

Recipe 1.9

1.13. Manipulating a Navigation Controller’s Array of View Controllers

Problem

You would like to directly manipulate the array of view controllers associated with a specific navigation controller.

Solution

Use the viewControllers property of the UINavigationController class to access and modify the array of view controllers associated with a navigation controller:

- (void) goBack{
    /* Get the current array of View Controllers */
    NSArray *currentControllers = self.navigationController.viewControllers;

    /* Create a mutable array out of this array */
    NSMutableArray *newControllers = [NSMutableArray
                                      arrayWithArray:currentControllers];

    /* Remove the last object from the array */
    [newControllers removeLastObject];

    /* Assign this array to the Navigation Controller */
    self.navigationController.viewControllers = newControllers;
}

You can call this method inside any view controller in order to pop the last view controller from the hierarchy of the navigation controller associated with the current view controller.

Discussion

An instance of the UINavigationController class holds an array of UIViewController objects. After retrieving this array, you can manipulate it in any way you wish. For instance, you can remove a view controller from an arbitrary place in the array.

Manipulating the view controllers of a navigation controller directly by assigning an array to the viewControllers property of the navigation controller will commit the operation without a transition/animation. If you wish this operation to be animated, use the setViewControllers:animated: method of the UINavigationController class, as shown in the following snippet:

- (void) goBack{
    /* Get the current array of View Controllers */
    NSArray *currentControllers = self.navigationController.viewControllers;

    /* Create a mutable array out of this array */
    NSMutableArray *newControllers = [NSMutableArray
                                      arrayWithArray:currentControllers];

    /* Remove the last object from the array */
    [newControllers removeLastObject];

    /* Assign this array to the Navigation Controller with animation */
    [self.navigationController setViewControllers:newControllers
                                         animated:YES];
}

1.14. Displaying an Image on a Navigation Bar

Problem

You want to display an image instead of text as the title of the current view controller on the navigation controller.

Solution

Use the titleView property of the view controller’s navigation item:

- (void)viewDidLoad{
    [super viewDidLoad];

    /* Create an Image View to replace the Title View */
    UIImageView *imageView =
    [[UIImageView alloc]
     initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, 40.0f)];

    imageView.contentMode = UIViewContentModeScaleAspectFit;

    /* Load an image. Be careful, this image will be cached */
    UIImage *image = [UIImage imageNamed:@"Logo"];

    /* Set the image of the Image View */
    [imageView setImage:image];

    /* Set the Title View */
    self.navigationItem.titleView = imageView;

}

Note

The preceding code must be executed in a view controller that is placed inside a navigation controller.

I have already loaded an image into my project’s assets group and I’ve called this image “Logo”. Once you run this app with the given code snippet, you’ll see something similar to that shown in Figure 1-35.

An image view in our navigation bar
Figure 1-35. An image view in our navigation bar

Discussion

The navigation item of every view controller can display two different types of content in the title area of the view controller to which it is assigned:

  • Simple text

  • A view

If you want to use text, you can use the title property of the navigation item. However, if you want more control over the title or if you simply want to display an image or any other view up on the navigation bar, you can use the titleView property of the navigation item of a view controller. You can assign any object that is a subclass of the UIView class. In our example, we created an image view and assigned an image to it. Then we displayed it as the title of the current view controller on the navigation controller.

The titleView property of the navigation bar is just a simple view, but Apple recommends that you limit the height of this view to no more than 128 points. So think about it in terms of the image. If you are loading an image that is 128 pixels in height, that will translate to 64 points on a retina display, so in that case you are fine. But if you are loading an image that is 300 pixels in height, on a retina display, that will translate to 150 points in height, so you’ll be clearly over the 128 points limit that Apple recommends for the title bar view height. To remedy this situation, you need to ensure that your title view never is taller than 128 points height-wise and set the view’s content mode to fill the view, instead of stretching the view to fit the content. This can be done by setting the contentMode property of your title bar view to UIViewContentModeScaleAspectFit.

1.15. Adding Buttons to Navigation Bars Using UIBarButtonItem

Problem

You want to add buttons to a navigation bar.

Solution

Use the UIBarButtonItem class.

Discussion

A navigation bar can contain different items. Buttons are often displayed on the left and the right sides. These buttons are of class UIBarButtonItem and can take many different shapes and forms. Let’s have a look at an example in Figure 1-36.

Different buttons displayed on a navigation bar
Figure 1-36. Different buttons displayed on a navigation bar

Navigation bars are of class UINavigationBar and can be created at any time and added to any view. Just look at all the different buttons with different shapes that have been added to the navigation bar in Figure 1-36. The ones on the top right have up and down arrows and the one on the top left has an arrow pointing to the left. We will have a look at creating some of these buttons in this recipe.

Note

For this recipe, you must follow the instructions in Creating and Running Our First iOS App to create an Empty application. Then follow the instructions in Recipe 1.12 to add a navigation controller to your app delegate.

In order to create a navigation button, we must:

  1. Create an instance of UIBarButtonItem.

  2. Add that button to the navigation bar of a view controller using the view controller’s navigationItem property. The navigationItem property allows us to interact with the navigation bar. This property has two others on itself: rightBarButtonItem and leftBarButtonItem. Both these properties are of type UIBarButtonItem.

Let’s then have a look at an example where we add a button to the right side of our navigation bar. In this button, we will display the text Add:

- (void) performAdd:(id)paramSender{
    NSLog(@"Action method got called.");
}

- (void)viewDidLoad{
    [super viewDidLoad];

    self.title = @"First Controller";

    self.navigationItem.rightBarButtonItem =
    [[UIBarButtonItem alloc] initWithTitle:@"Add"
                                     style:UIBarButtonItemStylePlain
                                    target:self
                                    action:@selector(performAdd:)];
}

When we run our app now, we will see something similar to Figure 1-37.

A navigation button added to a navigation bar
Figure 1-37. A navigation button added to a navigation bar

That was easy. But if you are an iOS user, you probably have noticed that the system apps that come preconfigured on iOS have a different Add button. Figure 1-38 shows an example in the Alarm section of the Clock app on the iPhone (notice the + button on the top right of the navigation bar).

The proper way of creating an Add button
Figure 1-38. The proper way of creating an Add button

It turns out that the iOS SDK allows us to create system buttons on the navigation bar. We do that by using the initWithBarButtonSystemItem:target:action: initializer of the UIBarButtonItem class:

- (void) performAdd:(id)paramSender{
    NSLog(@"Action method got called.");
}

- (void)viewDidLoad{
    [super viewDidLoad];

    self.title = @"First Controller";

    self.navigationItem.rightBarButtonItem =
    [[UIBarButtonItem alloc]
     initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
     target:self
     action:@selector(performAdd:)];
}

And the results are exactly what we were looking for (Figure 1-39).

A system Add button
Figure 1-39. A system Add button

The first parameter of the initWithBarButtonSystemItem:target:action: initializer method of the navigation button can have any of the values listed in the UIBarButtonSystemItem enumeration:

typedef NS_ENUM(NSInteger, UIBarButtonSystemItem) {
    UIBarButtonSystemItemDone,
    UIBarButtonSystemItemCancel,
    UIBarButtonSystemItemEdit,
    UIBarButtonSystemItemSave,
    UIBarButtonSystemItemAdd,
    UIBarButtonSystemItemFlexibleSpace,
    UIBarButtonSystemItemFixedSpace,
    UIBarButtonSystemItemCompose,
    UIBarButtonSystemItemReply,
    UIBarButtonSystemItemAction,
    UIBarButtonSystemItemOrganize,
    UIBarButtonSystemItemBookmarks,
    UIBarButtonSystemItemSearch,
    UIBarButtonSystemItemRefresh,
    UIBarButtonSystemItemStop,
    UIBarButtonSystemItemCamera,
    UIBarButtonSystemItemTrash,
    UIBarButtonSystemItemPlay,
    UIBarButtonSystemItemPause,
    UIBarButtonSystemItemRewind,
    UIBarButtonSystemItemFastForward,
#if __IPHONE_3_0 <= __IPHONE_OS_VERSION_MAX_ALLOWED
    UIBarButtonSystemItemUndo,
    UIBarButtonSystemItemRedo,
#endif
#if __IPHONE_4_0 <= __IPHONE_OS_VERSION_MAX_ALLOWED
    UIBarButtonSystemItemPageCurl,
#endif
};

One of the really great initializers of the UIBarButtonItem class is the initWithCustomView: method. As its parameter, this method accepts any view. This means we can even add a UISwitch (see Recipe 1.2) as a button on the navigation bar. This won’t look very good, but let’s give it a try:

- (void) switchIsChanged:(UISwitch *)paramSender{
    if ([paramSender isOn]){
        NSLog(@"Switch is on.");
    } else {
        NSLog(@"Switch is off.");
    }
}

- (void)viewDidLoad{
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    self.title = @"First Controller";

    UISwitch *simpleSwitch = [[UISwitch alloc] init];
    simpleSwitch.on = YES;
    [simpleSwitch addTarget:self
                     action:@selector(switchIsChanged:)
           forControlEvents:UIControlEventValueChanged];

    self.navigationItem.rightBarButtonItem =
    [[UIBarButtonItem alloc] initWithCustomView:simpleSwitch];
}

And Figure 1-40 shows the results.

A switch added to a navigation bar
Figure 1-40. A switch added to a navigation bar

You can create pretty amazing navigation bar buttons. Just take a look at what Apple has done with the up and down arrows on the top-right corner of Figure 1-36. Let’s do the same thing, shall we? Well, it looks like the button actually contains a segmented control (see Recipe 1.8). So we should create a segmented control with two segments, add it to a navigation button, and finally place the navigation button on the navigation bar. Let’s get started:

- (void) segmentedControlTapped:(UISegmentedControl *)paramSender{

    switch (paramSender.selectedSegmentIndex){
        case 0:{
            NSLog(@"Up");
            break;
        }
        case 1:{
            NSLog(@"Down");
            break;
        }
    }

}

- (void)viewDidLoad{
    [super viewDidLoad];

    self.title = @"First Controller";

    NSArray *items = @[
                       @"Up",
                       @"Down"
                       ];

    UISegmentedControl *segmentedControl = [[UISegmentedControl alloc]
                                            initWithItems:items];

    segmentedControl.momentary = YES;

    [segmentedControl addTarget:self
                         action:@selector(segmentedControlTapped:)
               forControlEvents:UIControlEventValueChanged];

    self.navigationItem.rightBarButtonItem =
    [[UIBarButtonItem alloc] initWithCustomView:segmentedControl];

}

And Figure 1-41 shows what the output looks like.

A segmented control inside a navigation button
Figure 1-41. A segmented control inside a navigation button

The navigationItem of every view controller also has two very interesting methods:

setRightBarButtonItem:animated:

Sets the navigation bar’s right button.

setLeftBarButtonItem:animated:

Sets the navigation bar’s left button.

Both methods allow you to specify whether you want the placement to be animated. Pass the value of YES to the animated parameter if you want the placement to be animated. Here is an example:

UIBarButtonItem *rightBarButton =
[[UIBarButtonItem alloc] initWithCustomView:segmentedControl];

[self.navigationItem setRightBarButtonItem:rightBarButton
                                  animated:YES];

1.16. Presenting Multiple View Controllers with UITabBarController

Problem

You would like to give your users the option to switch from one section of your app to another, with ease.

Solution

Use the UITabBarController class.

Discussion

If you use your iPhone as an alarm clock, you have certainly seen a tab bar. Have a look at Figure 1-38. The bottom icons labeled World Clock, Alarm, Stopwatch, and Timer are parts of a tab bar. The whole black bar at the bottom of the screen is a tab bar and the aforementioned icons are tab bar items.

A tab bar is a container controller. In other words, we create instances of UITabBarController and add them to the window of our application. For each tab bar item, we add a navigation controller or a view controller to the tab bar, and those items will appear as tab bar items. A tab bar controller contains a tab bar of type UITabBar. We don’t create this object manually. We create the tab bar controller, and that will create the tab bar object for us. To make things simple, remember that we instantiate a tab bar controller and set the view controllers of that tab bar to instances of either UIViewController or UINavigationController if we intend to have navigation controllers for each of the tab bar items (aka, the view controllers set for the tab bar controller). Navigation controllers are of type UINavigationController that are subclasses of UIViewController. Therefore, a navigation controller is a view controller, but view controllers of type UIViewController are not navigation controllers.

So let’s assume we have two view controllers with class names FirstViewController and SecondViewController.

- (BOOL)            application:(UIApplication *)application
  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

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

    [self.window makeKeyAndVisible];

    FirstViewController *firstViewController = [[FirstViewController alloc]
                                                initWithNibName:nil
                                                bundle:NULL];
    SecondViewController *secondViewController = [[SecondViewController alloc]
                                                  initWithNibName:nil
                                                  bundle:NULL];

    UITabBarController *tabBarController = [[UITabBarController alloc] init];
    [tabBarController setViewControllers:@[firstViewController,
                                           secondViewController
                                           ]];

    self.window.rootViewController = tabBarController;

    return YES;

}

A tab bar, when displayed on the screen, will display tab bar items just like those we saw in Figure 1-38. The name of each of these tab bar items comes from the title of the view controller that is representing that tab bar item, so let’s go ahead and set the title for both our view controllers.

Warning

When a tab bar loads up, it loads only the view of the first view controller in its items. All other view controllers will be initialized, but their views won’t be loaded. This means that any code that you have written in the viewDidLoad of the second view controller will not get executed until after the user taps on the second tab bar item for the first time. So if you assign a title to the second view controller in its viewDidLoad and run your app, you will find that the title in the tab bar item is still empty.

For the first view controller, we choose the title First:

#import "FirstViewController.h"

@implementation FirstViewController

- (id)initWithNibName:(NSString *)nibNameOrNil
               bundle:(NSBundle *)nibBundleOrNil{

    self = [super initWithNibName:nibNameOrNil
                           bundle:nibBundleOrNil];
    if (self != nil) {
        self.title = @"First";
    }
    return self;

}

- (void)viewDidLoad{
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
}

And for the second view controller, we pick the title Second:

#import "SecondViewController.h"

@implementation SecondViewController

- (id)initWithNibName:(NSString *)nibNameOrNil
               bundle:(NSBundle *)nibBundleOrNil{

    self = [super initWithNibName:nibNameOrNil
                           bundle:nibBundleOrNil];
    if (self != nil) {
        self.title = @"Second";
    }
    return self;

}

- (void)viewDidLoad{
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
}

Now let’s run our app and see what happens (Figure 1-42).

A very simple tab bar populated with two view controllers
Figure 1-42. A very simple tab bar populated with two view controllers

You can see that our view controllers do not have a navigation bar. What should we do? It’s easy. Remember that a UINavigationController is actually a subclass of UIViewController. So, we can add instances of navigation controllers to a tab bar, and inside each navigation controller, we can load a view controller. What are we waiting for, then?

- (BOOL)            application:(UIApplication *)application
  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

    // Override point for customization after application launch.
    self.window = [[UIWindow alloc] initWithFrame:
                   [[UIScreen mainScreen] bounds]];

    [self.window makeKeyAndVisible];

    FirstViewController *firstViewController = [[FirstViewController alloc]
                                                initWithNibName:nil
                                                bundle:NULL];

    UINavigationController *firstNavigationController =
        [[UINavigationController alloc]
         initWithRootViewController:firstViewController];

    SecondViewController *secondViewController = [[SecondViewController alloc]
                                                  initWithNibName:nil
                                                  bundle:NULL];

    UINavigationController *secondNavigationController =
        [[UINavigationController alloc]
         initWithRootViewController:secondViewController];

    UITabBarController *tabBarController = [[UITabBarController alloc] init];

    [tabBarController setViewControllers:
        @[firstNavigationController, secondNavigationController]];

    self.window.rootViewController = tabBarController;

    return YES;

}

And the results? Exactly what we wanted (Figure 1-43).

A tab bar displaying view controllers inside navigation controllers
Figure 1-43. A tab bar displaying view controllers inside navigation controllers

As we can see in Figure 1-38, each tab bar item can have text and an image. We’ve learned that, using the title property of a view controller, we can specify this text, but what about the image? It turns out that every view controller has a property called tabItem. This property is the tab item for the current view controller, and you can use this property to set the image of the tab bar item through the image property of the tab item. I’ve already designed two images, a rectangle and a circle. I’m going to display them as the tab bar item image for each of my view controllers. Here is code for the first view controller:

- (id)initWithNibName:(NSString *)nibNameOrNil
               bundle:(NSBundle *)nibBundleOrNil{

    self = [super initWithNibName:nibNameOrNil
                           bundle:nibBundleOrNil];
    if (self != nil) {
        self.title = @"First";
        self.tabBarItem.image = [UIImage imageNamed:@"FirstTab"];
    }
    return self;

}

- (void)viewDidLoad{
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
}

And here it is for the second view controller:

- (id)initWithNibName:(NSString *)nibNameOrNil
               bundle:(NSBundle *)nibBundleOrNil{

    self = [super initWithNibName:nibNameOrNil
                           bundle:nibBundleOrNil];
    if (self != nil) {
        self.title = @"Second";
        self.tabBarItem.image = [UIImage imageNamed:@"SecondTab"];
    }
    return self;

}

- (void)viewDidLoad{
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
}

Running the app in the simulator, we will see that the images are displayed properly (Figure 1-44).

Tab bar items with images
Figure 1-44. Tab bar items with images

1.17. Displaying Static Text with UILabel

Problem

You want to display text to your users. You would also like to control the text’s font and color.

Note

A static text is text that is not directly changeable by the user at runtime.

Solution

Use the UILabel class.

Discussion

Labels are everywhere in iOS. You can see them in practically every application, except for games where the content is usually rendered with OpenGL ES instead of the core drawing frameworks in iOS. Figure 1-45 shows several labels in the Settings app on the iPhone.

Labels as titles of each one of the settings
Figure 1-45. Labels as titles of each one of the settings

You can see that the labels are displaying text in the Settings app, such as iCloud, Phone, FaceTime, Safari, etc.

To create a label, instantiate an object of type UILabel. Setting or getting the text of a label can be done through its text property. So let’s first define a label in our view controller’s implementation file:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) UILabel *myLabel;
@end

@implementation ViewController

...

Now in the viewDidLoad method, instantiate the label and tell the runtime where the label has to be positioned (through its frame property) on the view to which it will be added (in this case, our view controller’s view):

- (void)viewDidLoad{
    [super viewDidLoad];

    CGRect labelFrame = CGRectMake(0.0f,
                                   0.0f,
                                   100.0f,
                                   23.0f);
    self.myLabel = [[UILabel alloc] initWithFrame:labelFrame];
    self.myLabel.text = @"iOS 7 Programming Cookbook";
    self.myLabel.font = [UIFont boldSystemFontOfSize:14.0f];
    self.myLabel.center = self.view.center;
    [self.view addSubview:self.myLabel];

}

Now let’s run our app and see what happens (see Figure 1-46).

A label that is too small in width to contain its contents
Figure 1-46. A label that is too small in width to contain its contents

You can see that the contents of the label are truncated, with trailing full stops, because the width of the label isn’t enough to contain the whole contents. One solution would be to make the width longer, but how about the height? What if we wanted the text to wrap to the next line? OK, go ahead and change the height from 23.0f to 50.0f:

CGRect labelFrame = CGRectMake(0.0f,
                               0.0f,
                               100.0f,
                               50.0f);

If you run your app now, you will get exactly the same results that you got in Figure 1-46. You might ask, “I increased the height, so why didn’t the content wrap to the next line?” It turns out that UILabel class has a property called numberOfLines that needs to be adjusted to the number of lines the label has to wrap the text to, in case it runs out of horizontal space. If you set this value to 3, it tells the label that you want the text to wrap to a maximum of three lines if it cannot fit the text into one line:

- (void)viewDidLoad{
    [super viewDidLoad];

    CGRect labelFrame = CGRectMake(0.0f,
                                   0.0f,
                                   100.0f,
                                   70.0f);
    self.myLabel = [[UILabel alloc] initWithFrame:labelFrame];
    self.myLabel.numberOfLines = 3;
    self.myLabel.lineBreakMode = NSLineBreakByWordWrapping;
    self.myLabel.text = @"iOS 7 Programming Cookbook";
    self.myLabel.font = [UIFont boldSystemFontOfSize:14.0f];
    self.myLabel.center = self.view.center;
    [self.view addSubview:self.myLabel];

}

If you run the app now, you will get the desired results (see Figure 1-47).

A label wrapping its contents to three lines
Figure 1-47. A label wrapping its contents to three lines

Note

In some situations, you might not know how many lines are required to display a certain text in a label. In those instances, you need to set the numberOfLines property of your label to 0.

If you want your label’s frame to stay static and you want the font inside your label to adjust itself to fit into the boundaries of the label, you need to set the adjustsFontSizeToFitWidth property of your label to YES. For instance, if the height of our label was 23.0f, as we see in Figure 1-46, we could adjust the font of the label to fit into the boundaries. Here is how it works:

- (void)viewDidLoad{
    [super viewDidLoad];

    CGRect labelFrame = CGRectMake(0.0f,
                                   0.0f,
                                   100.0f,
                                   23.0f);
    self.myLabel = [[UILabel alloc] initWithFrame:labelFrame];
    self.myLabel.adjustsFontSizeToFitWidth = YES;
    self.myLabel.text = @"iOS 7 Programming Cookbook";
    self.myLabel.font = [UIFont boldSystemFontOfSize:14.0f];
    self.myLabel.center = self.view.center;
    [self.view addSubview:self.myLabel];

}

1.18. Customizing the UILabel

Problem

You want to be able to customize the appearance of your labels, from shadow settings to alignment settings.

Solution

Use the following properties of the UILabel class, depending on your requirements:

shadowColor

This property is of type UIColor and, as its name shows, it specifies the color of the dropshadow to render for your label. If you are setting this property, you should also set the shadowOffset property.

shadowOffset

This property is of type CGSize and it specifies the offset of the dropshadow from the text. For instance, if you set this property to (1, 0), the dropshadow will appear 1 point to the right of the text. If you set this property to (1, 2), the dropshadow will appear 1 point to the right and 2 points down from the text. If you set this property to (–2, –10), the dropshadow will render 2 points to the left and 10 points above the text.

numberOfLines

This property is an integer that specifies how many lines of text the label is able to render. By default, this property’s value is set to 1, meaning any label that you create by default can handle 1 line of text. If you want 2 lines of text, for instance, set this property to 2. If you want unlimited lines of text to be rendered in your text field or you simply don’t know how many lines of text you will end up displaying, set this property to 0. (I know, it’s really strange. Instead of NSIntegerMax or something similar, Apple has decided that 0 means unlimited!)

lineBreakMode

This property is of type NSLineBreakMode and specifies how you want to line-wrap the text inside your text field. For instance, if you set this property to NSLineBreakByWordWrapping, words will be kept together, but the string will be wrapped to the next line if there is not enough space to display it. Alternatively, if you set this property to NSLineBreakByCharWrapping, words may be broken across lines when text is wrapped. You would probably use NSLineBreakByCharWrapping only if the space is very tight and you need to fit as much information as possible on the screen. I personally do not recommend using this option if you want to keep a consistent and clear user interface.

textAlignment

This property is of type NSTextAlignment and sets the horizontal alignment of the text in your label. For instance, you can set the value of this property to NSTextAlignmentCenter to horizontally center-align your text.

textColor

This property is of type UIColor and defines the color of the text inside the label.

font

This property of type UIFont specifies the font with which the text inside your label will get rendered.

adjustsFontSizeToFitWidth

This property is of type BOOL. When set to YES, it will change the size of the font to fit your label. For instance, if you have a small label and the text you are trying to set in it is too big to fit, if this property is set to YES, the runtime will automatically reduce the font size of your label to make sure the text will fit into the label. In contrast, if this property is set to NO, the current line/word/character wrapping option is taken into account and your text will be rendered in an incomplete manner with just a few words being displayed.

Discussion

Labels are one of the easiest UI components we can utilize in our applications. Although labels are simple, they are really powerful. Customization of labels is therefore very important in order to deliver the best user experience. For this reason, Apple has given us plenty of ways to customize the instances of UILabel. Let us have a look at an example. We’ll create a simple Single View Application with one view controller, place a simple label at the center of the screen with a huge font, and write “iOS SDK” in it. We will set the background color of our view to white, the text color of our label to black, and the shadow color of our label to light gray. We will make sure a dropshadow appears at the bottom-right side of our label. Figure 1-48 shows the effect our app should produce.

How our label is customized and rendered on the screen
Figure 1-48. How our label is customized and rendered on the screen

And here is our code to achieve this:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) UILabel *label;
@end

@implementation ViewController

- (void)viewDidLoad{
    [super viewDidLoad];

    self.label = [[UILabel alloc] init];
    self.label.backgroundColor = [UIColor clearColor];
    self.label.text = @"iOS SDK";
    self.label.font = [UIFont boldSystemFontOfSize:70.0f];
    self.label.textColor = [UIColor blackColor];
    self.label.shadowColor = [UIColor lightGrayColor];
    self.label.shadowOffset = CGSizeMake(2.0f, 2.0f);
    [self.label sizeToFit];
    self.label.center = self.view.center;
    [self.view addSubview:self.label];

}

@end

1.19. Accepting User Text Input with UITextField

Problem

You want to accept text input in your user interface.

Solution

Use the UITextField class.

Discussion

A text field is very much like a label in that it can display text, but a text field can also accept text entry at runtime. Figure 1-49 shows two text fields in the Twitter section of the Settings app on an iPhone.

Text fields allowing text entry
Figure 1-49. Text fields allowing text entry

Note

A text field allows only a single line of text to be input/displayed. As a result, the default height of a text field is only 31 points. In Interface Builder, this height cannot be modified, but if you are creating your text field in code, you can change the text field’s height. A change in height, though, will not change the number of lines you can render in a text field, which is always 1.

Let’s start with the implementation file of our view controller to define our text field:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) UITextField *myTextField;
@end

@implementation ViewController

...

And then let’s create the text field:

- (void)viewDidLoad{
    [super viewDidLoad];

    CGRect textFieldFrame = CGRectMake(0.0f,
                                       0.0f,
                                       200.0f,
                                       31.0f);

    self.myTextField = [[UITextField alloc]
                        initWithFrame:textFieldFrame];

    self.myTextField.borderStyle = UITextBorderStyleRoundedRect;

    self.myTextField.contentVerticalAlignment =
    UIControlContentVerticalAlignmentCenter;

    self.myTextField.textAlignment = NSTextAlignmentCenter;

    self.myTextField.text = @"Sir Richard Branson";
    self.myTextField.center = self.view.center;
    [self.view addSubview:self.myTextField];

}

Before looking at the details of the code, let’s first have a look at the results (Figure 1-50).

A simple text field with center aligned text
Figure 1-50. A simple text field with center aligned text

In order to create this text field, we used various properties of UITextField. These are:

borderStyle

This property is of type UITextBorderStyle and specifies how the text field should render its borders.

contentVerticalAlignment

This value is of type UIControlContentVerticalAlignment and tells the text field how the text should appear, vertically, in the boundaries of the control. If we didn’t center the text vertically, it would appear on the top-left corner of the text field by default.

textAlignment

This property is of type NSTextAlignment and specifies the horizontal alignment of the text in a text field. In this example, we have centered the text horizontally.

text

This is a read/write property: you can both read from it and write to it. Reading from it will return the text field’s current text and writing to it will set the text field’s text to the value that you specify.

A text field sends delegate messages to its delegate object. These messages get sent, for instance, when the user starts editing the text inside a text field, when the user enters any character into the text field (changing its contents in any way), and when the user finishes editing the field (by leaving the field). To get notified of these events, set the delegate property of the text field to your object. The delegate of a text field must conform to the UITextFieldDelegate protocol, so let’s first take care of this:

@interface ViewController () <UITextFieldDelegate>
@property (nonatomic, strong) UITextField *myTextField;
@end

@implementation ViewController

Hold down the Command key on your computer and click on the UITextFieldDelegate protocol in Xcode. You will see all the methods that this protocol gives you control over. Here are those methods with description of when they get called:

textFieldShouldBeginEditing:

A method that returns a BOOL telling the text field (the parameter to this method) whether it should start getting edited by the user or not. Return NO if you don’t want the user to edit your text field. This method gets fired as soon as the user taps on the text field with the goal of editing its content (assuming the text field allows editing).

textFieldDidBeginEditing:

Gets called when the text field starts to get edited by the user. This method gets called when the user has already tapped on the text field and the textFieldShouldBeginEditing: delegate method of the text field returned YES, telling the text field it is OK for the user to edit the content of the text field.

textFieldShouldEndEditing:

Returns a BOOL telling the text field whether it should end its current editing session or not. This method gets called when the user is about to leave the text field or the first responder is switching to another data entry field. If you return NO from this method, the user will not be able to switch to another text entry field, and the keyboard will stay on the screen.

textFieldDidEndEditing:

Gets called when the editing session of the text field ends. This happens when the user decides to edit some other data entry field or uses a button provided by the supplier of the app to dismiss the keyboard shown for the text field.

textField:shouldChangeCharactersInRange:replacementString:

Gets called whenever the text inside the text field is modified. The return value of this method is a boolean. If you return YES, you say that you allow the text to be changed. If you return NO, the change in the text of the text field will not be confirmed and will not happen.

textFieldShouldClear:

Each text field has a clear button that is usually a circular X button. When the user presses this button, the contents of the text field will automatically get erased. We need to manually enable the clear button, though. If you have enabled the clear button and you return NO to this method, that gives the user the impression that your app isn’t working, so make sure you know what you are doing. It is a very poor user experience if the user sees a clear button and presses it, but doesn’t see the text in the text field get erased.

textFieldShouldReturn:

Gets called when the user has pressed the Return/Enter key on the keyboard, trying to dismiss the keyboard. You should assign the text field as the first responder in this method.

Let’s mix this recipe with Recipe 1.17 and create a dynamic text label under our text field. We’ll also display the total number of characters entered in our text field in the label. Let’s start with our implementation file:

@interface ViewController () <UITextFieldDelegate>
@property (nonatomic, strong) UITextField *myTextField;
@property (nonatomic, strong) UILabel *labelCounter;
@end

@implementation ViewController

Now for the creation of the text field along with the label and the text field delegate methods we require. We skip implementing many of the UITextFieldDelegate methods, because we don’t need all of them in this example:

- (void) calculateAndDisplayTextFieldLengthWithText:(NSString *)paramText{

    NSString *characterOrCharacters = @"Characters";
    if ([paramText length] == 1){
        characterOrCharacters = @"Character";
    }

    self.labelCounter.text = [NSString stringWithFormat:@"%lu %@",
                              (unsigned long)[paramText length],
                              characterOrCharacters];
}

- (BOOL)                textField:(UITextField *)textField
    shouldChangeCharactersInRange:(NSRange)range
                replacementString:(NSString *)string{

    if ([textField isEqual:self.myTextField]){
        NSString *wholeText =
        [textField.text stringByReplacingCharactersInRange:range
                                                withString:string];
        [self calculateAndDisplayTextFieldLengthWithText:wholeText];
    }

    return YES;

}

- (BOOL)textFieldShouldReturn:(UITextField *)textField{
    [textField resignFirstResponder];
    return YES;
}

- (void)viewDidLoad{
    [super viewDidLoad];

    CGRect textFieldFrame = CGRectMake(38.0f,
                                       30.0f,
                                       220.0f,
                                       31.0f);

    self.myTextField = [[UITextField alloc]
                        initWithFrame:textFieldFrame];

    self.myTextField.delegate = self;

    self.myTextField.borderStyle = UITextBorderStyleRoundedRect;

    self.myTextField.contentVerticalAlignment =
        UIControlContentVerticalAlignmentCenter;

    self.myTextField.textAlignment = NSTextAlignmentCenter;

    self.myTextField.text = @"Sir Richard Branson";

    [self.view addSubview:self.myTextField];

    CGRect labelCounterFrame = self.myTextField.frame;
    labelCounterFrame.origin.y += textFieldFrame.size.height + 10;
    self.labelCounter = [[UILabel alloc] initWithFrame:labelCounterFrame];
    [self.view addSubview:self.labelCounter];

    [self calculateAndDisplayTextFieldLengthWithText:self.myTextField.text];

}

One important calculation we are doing is in the textField:shouldChangeCharactersInRange:replacementString: method. There, we declare and use a variable called wholeText. When this method gets called, the replacementString parameter specifies the string that the user has entered into the text field. You might be thinking that the user can enter only one character at a time, so why can’t this field be a char? But don’t forget that the user can paste a whole chunk of text into a text field, so this parameter needs to be a string. The shouldChangeCharactersInRange parameter specifies where, in terms of location inside the text field’s text, the user is entering the text. So using these two parameters, we will create a string that first reads the whole text inside the text field and then uses the given range to place the new text inside the old text. With this, we will come up with the text that will appear in the text field after the textField:shouldChangeCharactersInRange:replacementString: method returns YES. Figure 1-51 shows how our app looks when it gets run on the simulator.

Responding to delegate messages of a text field
Figure 1-51. Responding to delegate messages of a text field

In addition to displaying text, a text field can also display a placeholder. A placeholder is the text displayed before the user has entered any text in the text field, while the text field’s text property is empty. This can be any string that you wish, and setting it will help give the user an indication as to what this text field is for. Many use this placeholder to tell the user what type of value she can enter in that text field. For instance, in Figure 1-49, the two text fields (password and description) have placeholders that say “Required”, etc. You can use the placeholder property of the text field to set or get the current placeholder. Here is an example:

CGRect textFieldFrame = CGRectMake(38.0f,
                                   30.0f,
                                   220.0f,
                                   31.0f);

self.myTextField = [[UITextField alloc]
                    initWithFrame:textFieldFrame];

self.myTextField.delegate = self;

self.myTextField.borderStyle = UITextBorderStyleRoundedRect;

self.myTextField.contentVerticalAlignment =
    UIControlContentVerticalAlignmentCenter;

self.myTextField.textAlignment = NSTextAlignmentCenter;

self.myTextField.placeholder = @"Enter text here...";
[self.view addSubview:self.myTextField];

The results are shown in Figure 1-52.

A placeholder is shown when the user has not entered any text in a text field
Figure 1-52. A placeholder is shown when the user has not entered any text in a text field

Text fields have two really neat properties called leftView and rightView. These two properties are of type UIView and are read/write. They appear, as their names imply, on the left and the right side of a text field if you assign a view to them. One place you might use a left view, for instance, is if you are displaying a currency text field where you would like to display the currency of the user’s current country in the left view, as a UILabel. Here is how we can accomplish that:

UILabel *currencyLabel = [[UILabel alloc] initWithFrame:CGRectZero];
currencyLabel.text = [[[NSNumberFormatter alloc] init] currencySymbol];
currencyLabel.font = self.myTextField.font;
[currencyLabel sizeToFit];
self.myTextField.leftView = currencyLabel;
self.myTextField.leftViewMode = UITextFieldViewModeAlways;

If we simply assign a view to the leftView or to the rightView properties of a text field, those views will not appear automatically by default. When they show up on the screen depends on the mode that governs their appearance, and you can control that mode using the leftViewMode and rightViewMode properties, respectively. These modes are of type UITextFieldViewMode:

typedef NS_ENUM(NSInteger, UITextFieldViewMode) {
    UITextFieldViewModeNever,
    UITextFieldViewModeWhileEditing,
    UITextFieldViewModeUnlessEditing,
    UITextFieldViewModeAlways
};

So if, for instance, you set the left view mode to UITextFieldViewModeWhileEditing and assign a value to it, it will appear only while the user is editing the text field. Conversely, if you set this value to UITextFieldViewModeUnlessEditing, the left view will appear only while the user is not editing the text field. As soon as editing starts, the left view will disappear. Let’s have a look at our code now in the simulator (Figure 1-53).

A text field with a left view
Figure 1-53. A text field with a left view

See Also

Recipe 1.17

1.20. Displaying Long Lines of Text with UITextView

Problem

You want to display multiple lines of text in your UI inside one scrollable view.

Solution

Use the UITextView class.

Discussion

The UITextView class can display multiple lines of text and contain scrollable content, meaning that if the contents run off the boundaries of the text view, the text view’s internal components allow the user to scroll the text up and down to see different parts of the text. An example of a text view in an iOS app is the Notes app on the iPhone (Figure 1-54).

Notes app on the iPhone uses a text view to render text
Figure 1-54. Notes app on the iPhone uses a text view to render text

Let’s create a text view and see how it works. We start off by declaring the text view in our view controller’s implementation file:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) UITextView *myTextView;
@end

@implementation ViewController

Now it’s time to create the text view itself. We will make the text view as big as the view controller’s view:

- (void)viewDidLoad{
    [super viewDidLoad];

    self.myTextView = [[UITextView alloc] initWithFrame:self.view.bounds];
    self.myTextView.text = @"Some text here...";
    self.myTextView.contentInset = UIEdgeInsetsMake(10.0f, 0.0f, 0.0f, 0.0f);
    self.myTextView.font = [UIFont systemFontOfSize:16.0f];
    [self.view addSubview:self.myTextView];

}

Now let’s run the app in iOS Simulator and see how it looks (Figure 1-55).

A text view consuming the entire boundary of the screen
Figure 1-55. A text view consuming the entire boundary of the screen

If you tap on the text field, you will notice a keyboard pop up from the bottom of the screen, concealing almost half the entire area of the text view. That means if the user starts typing text and gets to the middle of the text view, the rest of the text that she types will not be visible to her (Figure 1-56).

Keyboard concealing half the size of a text view
Figure 1-56. Keyboard concealing half the size of a text view

To remedy this, we have to listen for certain notifications:

UIKeyboardWillShowNotification

Gets sent by the system whenever the keyboard is brought up on the screen for any component, be it a text field, a text view, etc.

UIKeyboardDidShowNotification

Gets sent by the system when the keyboard has already been displayed.

UIKeyboardWillHideNotification

Gets sent by the system when the keyboard is about to hide.

UIKeyboardDidHideNotification

Gets sent by the system when the keyboard is now fully hidden.

Note

The keyboard notifications contain a dictionary, accessible through the userInfo property, that specifies the boundaries of the keyboard on the screen. This property is of type NSDictionary. One of the keys in this dictionary is UIKeyboardFrameEndUserInfoKey, which contains an object of type NSValue that itself contains the rectangular boundaries of the keyboard when it is fully shown. This rectangular area is denoted with a CGRect.

So our strategy is to find out when the keyboard is getting displayed and then somehow resize our text view. For this, we will use the contentInset property of UITextView to specify the margins of contents in the text view from top, left, bottom, and right:

- (void) handleKeyboardDidShow:(NSNotification *)paramNotification{

    /* Get the frame of the keyboard */
    NSValue *keyboardRectAsObject =
    [[paramNotification userInfo]
     objectForKey:UIKeyboardFrameEndUserInfoKey];

    /* Place it in a CGRect */
    CGRect keyboardRect = CGRectZero;

    [keyboardRectAsObject getValue:&keyboardRect];

    /* Give a bottom margin to our text view that makes it
     reach to the top of the keyboard */
    self.myTextView.contentInset =
    UIEdgeInsetsMake(0.0f,
                     0.0f,
                     keyboardRect.size.height,
                     0.0f);
}

- (void) handleKeyboardWillHide:(NSNotification *)paramNotification{
    /* Make the text view as big as the whole view again */
    self.myTextView.contentInset = UIEdgeInsetsZero;
}

- (void) viewWillAppear:(BOOL)paramAnimated{
    [super viewWillAppear:paramAnimated];

    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(handleKeyboardDidShow:)
     name:UIKeyboardDidShowNotification
     object:nil];

    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(handleKeyboardWillHide:)
     name:UIKeyboardWillHideNotification
     object:nil];

    self.myTextView = [[UITextView alloc] initWithFrame:self.view.bounds];
    self.myTextView.text = @"Some text here...";
    self.myTextView.font = [UIFont systemFontOfSize:16.0f];
    [self.view addSubview:self.myTextView];

}

- (void) viewWillDisappear:(BOOL)paramAnimated{
    [super viewWillDisappear:paramAnimated];

    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

In this code, we start looking for keyboard notifications in viewWillAppear: and we stop listening to keyboard notifications in viewWillDisappear:. Removing your view controller as the listener is important, because when your view controller is no longer displayed, you probably don’t want to receive keyboard notifications fired by any other view controller. There may be times when a view controller in the background needs to receive notifications, but these are rare and you must normally make sure to stop listening for notifications in viewWillDisappear:. I’ve seen many applications programmers break their apps by not taking care of this simple logic.

Note

If you intend to change your UI structure when the keyboard gets displayed and when the keyboard is dismissed, the only method that you can rely on is to use the keyboard notifications. Delegate messages of UITextField get fired when the text field is getting edited, whether there is a soft keyboard on the screen or not. Remember, a user can have a Bluetooth keyboard connected to his iOS device and use it to edit the content of text fields and any other data entry in your apps. In the case of a Bluetooth keyboard, no soft keyboard will be displayed on the screen—and if you change your UI when your text fields start to get edited, you might unnecessarily change the UI while the Bluetooth keyboard user is editing text.

Now, if the user tries to enter some text into the text view, the keyboard will pop up, and we take the height of the keyboard and assign that value as the bottom margin of the contents inside the text view. This makes our text view’s contents smaller in size and allows the user to enter as much text as she wishes without the keyboard blocking her view.

1.21. Adding Buttons to the User Interface with UIButton

Problem

You want to display a button on your UI and handle the touch events for that button.

Solution

Use the UIButton class.

Discussion

Buttons allow users to initiate an action in your apps. For instance, the iCloud Settings bundle in the Settings app presents a Delete Account button in Figure 1-57. If you press this button, the iCloud app will take action. The action depends on the app. Not all apps act the same when a Delete button is pressed by the user. Buttons can have images in them as well as text, as we will soon see.

A Delete Account button
Figure 1-57. A Delete Account button

A button can assign actions to different triggers. For instance, a button can fire one action when the user puts her finger down on the button and another action when she lifts her finger off the button. These become actions and the objects implementing the actions become targets. Let’s go ahead and define a button in our view controller’s implementation file:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) UIButton *myButton;
@end

@implementation ViewController

Note

The default height of UIButton is 44.0f points in iOS 7.

Next, we move on to the implementation of the button (Figure 1-58):

- (void) buttonIsPressed:(UIButton *)paramSender{
    NSLog(@"Button is pressed.");
}

- (void) buttonIsTapped:(UIButton *)paramSender{
    NSLog(@"Button is tapped.");
}

- (void)viewDidLoad{
    [super viewDidLoad];

    self.myButton = [UIButton buttonWithType:UIButtonTypeSystem];

    self.myButton.frame = CGRectMake(110.0f,
                                     200.0f,
                                     100.0f,
                                     44.0f);

    [self.myButton setTitle:@"Press Me"
                   forState:UIControlStateNormal];

    [self.myButton setTitle:@"I'm Pressed"
                   forState:UIControlStateHighlighted];

    [self.myButton addTarget:self
                      action:@selector(buttonIsPressed:)
            forControlEvents:UIControlEventTouchDown];

    [self.myButton addTarget:self
                      action:@selector(buttonIsTapped:)
            forControlEvents:UIControlEventTouchUpInside];

    [self.view addSubview:self.myButton];

}
A system button in the middle of the screen
Figure 1-58. A system button in the middle of the screen

In this example code, we are using the setTitle:forState: method of our button to set two different titles for the button. The title is the text that gets displayed on the button. A button can be in different states at different times—such as normal and highlighted (pressed down)—and can display a different title in each state. So in this case, when the user sees the button for the first time, he will read Press Me. Once he presses the button, the title of the button will change to I'm Pressed.

We did a similar thing with the actions that the button fires. We used the addTarget:action:forControlEvents: method to specify two actions for our button:

  1. An action to be fired when the user presses the button down.

  2. Another action to be fired when the user has pressed the button and has lifted his finger off the button. This completes a touch-up-inside action.

The other thing that you need to know about UIButton is that it must always be assigned a type, which you do by initializing it with a call to the class method buttonWithType, as shown in the example code. As the parameter to this method, pass a value of type UIButtonType:

typedef NS_ENUM(NSInteger, UIButtonType) {
    UIButtonTypeCustom = 0,
    UIButtonTypeSystem NS_ENUM_AVAILABLE_IOS(7_0),
    UIButtonTypeDetailDisclosure,
    UIButtonTypeInfoLight,
    UIButtonTypeInfoDark,
    UIButtonTypeContactAdd,
    UIButtonTypeRoundedRect = UIButtonTypeSystem,
};

A button can also render an image. An image will replace the default look and feel of the button. When you have an image or series of images that you want to assign to different states of a button, make sure your button is of type UIButtonTypeCustom. I have prepared two images here: one for the normal state of the button and the other for the highlighted (pressed) state. I will now create my custom button and assign two images to it. One image is for the normal state of the button, and the other for the highlighted state:

UIImage *normalImage = [UIImage imageNamed:@"NormalBlueButton"];
UIImage *highlightedImage = [UIImage imageNamed:@"HighlightedBlueButton"];

self.myButton = [UIButton buttonWithType:UIButtonTypeCustom];

self.myButton.frame = CGRectMake(110.0f,
                                 200.0f,
                                 100.0f,
                                 44.0f);

[self.myButton setBackgroundImage:normalImage
                         forState:UIControlStateNormal];
[self.myButton setTitle:@"Normal"
               forState:UIControlStateNormal];

[self.myButton setBackgroundImage:highlightedImage
                         forState:UIControlStateHighlighted];
[self.myButton setTitle:@"Pressed"
               forState:UIControlStateHighlighted];

Figure 1-59 shows what the app looks like when we run it in the iOS Simulator. We are using the setBackgroundImage:forState: method of the button to set a background image. With a background image, we can still use the setTitle:forState: methods to render text on top of the background image. If your images contain text and you don’t need the title for a button, you can instead use the setImage:forState: method or simply remove the titles from the button.

A button with a background image
Figure 1-59. A button with a background image

1.22. Displaying Images with UIImageView

Problem

You would like to display images to your users on your app’s UI.

Solution

Use the UIImageView class.

Discussion

The UIImageView is one of the least complicated classes in the iOS SDK. As you know, an image view is responsible for displaying images. There are no tips or tricks involved. All you have to do is instantiate an object of type UIImageView and add it to your views. Now, I have a picture of Apple MacBook Air and I would like to display it in an image view. Let’s start with our view controller’s implementation file:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) UIImageView *myImageView;
@end

@implementation ViewController

Go ahead and instantiate the image view and place the image in it:

- (void)viewDidLoad{
    [super viewDidLoad];

    UIImage *macBookAir = [UIImage imageNamed:@"MacBookAir"];
    self.myImageView = [[UIImageView alloc] initWithImage:macBookAir];
    self.myImageView.center = self.view.center;
    [self.view addSubview:self.myImageView];

}

Now if we run the app, we will see something similar to Figure 1-60.

An image view that is too big to fit on the screen
Figure 1-60. An image view that is too big to fit on the screen

I should mention that the MacBook Air image that I’m loading into this image view is 980×519 pixels and as you can see, it certainly doesn’t fit into the iPhone screen. So how do we solve this problem? First, we need to make sure that we are initializing our image view using the initWithFrame: method, instead of the initWithImage: method as the latter will set the width and height of the image view to the exact width and height of the image. So let’s remedy that first:

- (void)viewDidLoad{
    [super viewDidLoad];

    UIImage *macBookAir = [UIImage imageNamed:@"MacBookAir"];
    self.myImageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
    self.myImageView.image = macBookAir;
    self.myImageView.center = self.view.center;
    [self.view addSubview:self.myImageView];

}

So how does the app look now? See Figure 1-61.

An image whose width is squished to fit the width of the screen
Figure 1-61. An image whose width is squished to fit the width of the screen

This isn’t really what we wanted to do, is it? Of course, we got the frame of the image view right, but the way the image is rendered in the image view isn’t quite right. So what can we do? We can rectify this by setting the contentMode property of the image view. This property is of type UIContentMode:

typedef NS_ENUM(NSInteger, UIViewContentMode) {
    UIViewContentModeScaleToFill,
    UIViewContentModeScaleAspectFit,
    UIViewContentModeScaleAspectFill,
    UIViewContentModeRedraw,
    UIViewContentModeCenter,
    UIViewContentModeTop,
    UIViewContentModeBottom,
    UIViewContentModeLeft,
    UIViewContentModeRight,
    UIViewContentModeTopLeft,
    UIViewContentModeTopRight,
    UIViewContentModeBottomLeft,
    UIViewContentModeBottomRight,
};

Here is an explanation of some of the most useful values in the UIViewContentMode enumeration:

UIViewContentModeScaleToFill

This will scale the image inside the image view to fill the entire boundaries of the image view.

UIViewContentModeScaleAspectFit

This will make sure the image inside the image view will have the right aspect ratio and fits inside the image view’s boundaries.

UIViewContentModeScaleAspectFill

This will makes sure the image inside the image view will have the right aspect ratio and fills the entire boundaries of the image view. For this value to work properly, make sure that you have set the clipsToBounds property of the image view to YES.

Note

The clipsToBounds property of UIView denotes whether the subviews of that view should be clipped if they go outside the boundaries of the view. You use this property if you want to be absolutely certain that the subviews of a specific view will not get rendered outside the boundaries of that view (or that they do get rendered outside the boundaries, depending on your requirements).

So to make sure the image fits into the image view’s boundaries and that the aspect ratio of the image is right, we need to use the UIViewContentModeScaleAspectFit content mode:

- (void)viewDidLoad{
    [super viewDidLoad];

    UIImage *macBookAir = [UIImage imageNamed:@"MacBookAir"];
    self.myImageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
    self.myImageView.contentMode = UIViewContentModeScaleAspectFit;
    self.myImageView.image = macBookAir;
    self.myImageView.center = self.view.center;
    [self.view addSubview:self.myImageView];
}

And the results will be exactly what we expected (Figure 1-62).

The aspect ratio of image view is absolutely spot on
Figure 1-62. The aspect ratio of image view is absolutely spot on

1.23. Creating Scrollable Content with UIScrollView

Problem

You have content that needs to get displayed on the screen, but it requires more real estate than what the device’s screen allows for.

Solution

Use the UIScrollView class.

Discussion

Scroll views are one of the features that make iOS a really neat operating system. They are practically everywhere. You’ve been to the Clock or the Contacts apps, haven’t you? Have you seen how the content can be scrolled up and down? Well that’s the magic of scroll views.

There really is one basic concept you need to learn about scroll views: the content size, which lets the scroll view conform to the size of what it’s displaying The content size is a value of type CGSize that specifies the width and the height of the contents of a scroll view. A scroll view, as its name implies, is a subclass of UIView, so you can simply add your views to a scroll view using its addSubview: method. However, you need to make sure that the scroll view’s content size is set properly, otherwise, the contents inside the scroll view won’t scroll.

As an example, let’s find a big image and load it to an image view. I will add the same image that I used in Recipe 1.22: a MacBook Air image. I will add it to an image view and place it in a scroll view. Then I will use the contentSize of the scroll view to make sure this content size is equal to the size of the image (width and height). First, let’s start with the implementation file of our view controller:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) UIScrollView *myScrollView;
@property (nonatomic, strong) UIImageView *myImageView;
@end

@implementation ViewController

And let’s place the image view inside the scroll view:

- (void)viewDidLoad{
    [super viewDidLoad];

    UIImage *imageToLoad = [UIImage imageNamed:@"MacBookAir"];
    self.myImageView = [[UIImageView alloc] initWithImage:imageToLoad];
    self.myScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    [self.myScrollView addSubview:self.myImageView];
    self.myScrollView.contentSize = self.myImageView.bounds.size;
    [self.view addSubview:self.myScrollView];

}

If you now load up the app in iOS Simulator, you will see that you can scroll the image horizontally and vertically. The challenge here, of course, is to provide an image that is bigger than the screen’s boundaries. For example, if you provide an image that is 20×20 pixels, the scroll view won’t be of much use to you. In fact, it would be wrong to place such an image into a scroll view, as the scroll view would practically be useless in that scenario. There would be nothing to scroll because the image is smaller than the screen size.

One of the handy features of UIScrollView is support for delegation, so that it can report really important events to the app through a delegate. A delegate for a scroll view must conform to the UIScrollViewDelegate protocol. Some of the methods defined in this protocol are:

scrollViewDidScroll:

Gets called whenever the contents of a scroll view get scrolled.

scrollViewWillBeginDecelerating:

Gets called when the user scrolls the contents of a scroll view and lifts his finger off of the screen, as the scroll view scrolls.

scrollViewDidEndDecelerating:

Gets called when the scroll view has finished scrolling its contents.

scrollViewDidEndDragging:willDecelerate:

Gets called when the user finishes dragging the contents of the scroll view. This method is very similar to the scrollViewDidEndDecelerating: method but you need to bear in mind that the user can drag the contents of a scroll view without scrolling the contents. She can simply put her finger on the content, move her finger to any location on the screen and lift her finger without giving the contents any momentum to move. This is dragging as opposed to scrolling. Scrolling is similar to dragging, but the user will give momentum to the contents’ movement by lifting her finger off the screen while the content is being dragged around, and not waiting for the content to stop before lifting the finger off the screen. Dragging is comparable to holding down the accelerator in a car or pedaling on a bicycle, whereas scrolling is comparable to coasting in a car or on a bicycle.

So let’s add some fun to our previous app. Now the goal is to set the alpha level of the image inside our image view to 0.50f (half transparent) when the user starts to scroll the scroll view and set this alpha back to 1.0f (opaque) when the user finishes scrolling. Let’s begin by conforming to the UIScrollViewDelegate protocol:

#import "ViewController.h"

@interface ViewController () <UIScrollViewDelegate>
@property (nonatomic, strong) UIScrollView *myScrollView;
@property (nonatomic, strong) UIImageView *myImageView;
@end

@implementation ViewController

Then let’s implement this functionality:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    /* Gets called when user scrolls or drags */
    self.myScrollView.alpha = 0.50f;
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
    /* Gets called only after scrolling */
    self.myScrollView.alpha = 1.0f;
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
                  willDecelerate:(BOOL)decelerate{
    /* Make sure the alpha is reset even if the user is dragging */
    self.myScrollView.alpha = 1.0f;
}

- (void)viewDidLoad{
    [super viewDidLoad];

    UIImage *imageToLoad = [UIImage imageNamed:@"MacBookAir"];
    self.myImageView = [[UIImageView alloc] initWithImage:imageToLoad];
    self.myScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    [self.myScrollView addSubview:self.myImageView];
    self.myScrollView.contentSize = self.myImageView.bounds.size;
    self.myScrollView.delegate = self;
    [self.view addSubview:self.myScrollView];

}

As you might have noticed, scroll views have indicators. An indicator is the little tracking line that appears on the sides of a scroll view when its contents are getting scrolled and moved. Figure 1-63 shows an example.

Black indicators appearing on the right and bottom of a scroll view
Figure 1-63. Black indicators appearing on the right and bottom of a scroll view

Indicators simply show the user where the current view is in relation to the content (top, halfway down, etc.). You can control what the indicators look like by changing the value of the indicatorStyle property. For instance, here I have changed the indicator style of my scroll view to white:

self.myScrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;

One of the great features of scroll views is that they allow pagination. Pagination is the same as scrolling, but locks the scrolling when the user moves to the next page. You have perhaps already seen this if you’ve ever used the Photos app on the iPhone or iPad. When you are looking at photos, you can swipe between them. Each swipe brings the next or previous photo onto the screen. Your swiping never scrolls all the way to the end or all the way to the start. When the scrolling starts, the scroll view detects the next image to display, scrolls and bounces to that image, and stops the scrolling animation. That’s pagination. If you haven’t tried it already, I urge you to do so, because otherwise I could go on and on and none of this would make sense unless you looked at an app that supports pagination.

For this example code, I’ve prepared three images: an iPhone, iPad, and a MacBook Air. I’ve placed them in their individual image views and added them to a scroll view. Then we can enable pagination by setting the value of the pagingEnabled property of the scroll view to YES:

- (UIImageView *) newImageViewWithImage:(UIImage *)paramImage
                                  frame:(CGRect)paramFrame{

    UIImageView *result = [[UIImageView alloc] initWithFrame:paramFrame];
    result.contentMode = UIViewContentModeScaleAspectFit;
    result.image = paramImage;
    return result;

}

- (void)viewDidLoad{
    [super viewDidLoad];

    UIImage *iPhone = [UIImage imageNamed:@"iPhone"];
    UIImage *iPad = [UIImage imageNamed:@"iPad"];
    UIImage *macBookAir = [UIImage imageNamed:@"MacBookAir"];

    CGRect scrollViewRect = self.view.bounds;

    self.myScrollView = [[UIScrollView alloc] initWithFrame:scrollViewRect];
    self.myScrollView.pagingEnabled = YES;
    self.myScrollView.contentSize = CGSizeMake(scrollViewRect.size.width * 3.0f,
                                               scrollViewRect.size.height);
    [self.view addSubview:self.myScrollView];

    CGRect imageViewRect = self.view.bounds;
    UIImageView *iPhoneImageView = [self newImageViewWithImage:iPhone
                                                         frame:imageViewRect];
    [self.myScrollView addSubview:iPhoneImageView];

    /* Go to next page by moving the x position of the next image view */
    imageViewRect.origin.x += imageViewRect.size.width;
    UIImageView *iPadImageView = [self newImageViewWithImage:iPad
                                                       frame:imageViewRect];
    [self.myScrollView addSubview:iPadImageView];

    /* Go to next page by moving the x position of the next image view */
    imageViewRect.origin.x += imageViewRect.size.width;
    UIImageView *macBookAirImageView =
    [self newImageViewWithImage:macBookAir
                          frame:imageViewRect];
    [self.myScrollView addSubview:macBookAirImageView];

}

Now we have three pages of scrollable content (Figure 1-64).

Scrolling through pages in a page-enabled scroll view
Figure 1-64. Scrolling through pages in a page-enabled scroll view

1.24. Loading Web Pages with UIWebView

Problem

You want to load a web page dynamically right inside your iOS app.

Solution

Use the UIWebView class.

Discussion

A web view is what the Safari browser uses on iOS to load web content. You have the whole power of Safari in your iOS apps through the UIWebView class. All you have to do is place a web view on your UI and use one of its loading methods:

loadData:MIMEType:textEncodingName:baseURL:

Loads an instance of NSData into the web view.

loadHTMLString:baseURL:

Loads an instance of NSString into the web view. The string should be a valid HTML, or in other words, something that a web browser can render.

loadRequest:

Loads an instance of NSURLRequest. This is useful when you want to load the contents of a remote URL into a web view inside your application.

Let’s see an example. We’ll start with the implementation file of our view controller:

#import "ViewController.h"

@interface ViewController ()
@property(nonatomic, strong) UIWebView *myWebView;
@end

@implementation ViewController

Now I would like to load the string iOS 7 Programming Cookbook into the web view. To prove things are working as expected and that our web view is capable of rendering rich text, I will go ahead and make the Cookbook part bold while leaving the rest of the text intact (Figure 1-65):

- (void)viewDidLoad{
    [super viewDidLoad];

    self.myWebView = [[UIWebView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:self.myWebView];

    NSString *htmlString = @"<br/>iOS 7 Programming <strong>Cookbook</strong>";

    [self.myWebView loadHTMLString:htmlString
                           baseURL:nil];
}
Loading rich text into a web view
Figure 1-65. Loading rich text into a web view

Another way to use a web view is to load a remote URL into it. For this purpose, we can use the loadRequest: method. Let’s go ahead and look at an example where we will load Apple’s main page into a web view in our iOS app (Figure 1-66):

- (void)viewDidLoad{
    [super viewDidLoad];

    self.myWebView = [[UIWebView alloc] initWithFrame:self.view.bounds];
    self.myWebView.scalesPageToFit = YES;
    [self.view addSubview:self.myWebView];

    NSURL *url = [NSURL URLWithString:@"http://www.apple.com"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    [self.myWebView loadRequest:request];

}
Apple’s home page loaded into a web view
Figure 1-66. Apple’s home page loaded into a web view

It might take quite a while for a web view to load the contents that you pass to it. You might have noticed that when loading content in Safari, you get a little activity indicator in the top-left corner of the screen telling you that the device is busy loading the contents. Figure 1-67 shows an example.

A progress bar indicating a loading process
Figure 1-67. A progress bar indicating a loading process

iOS accomplishes this through delegation. We will subscribe as the delegate of a web view, and the web view will notify us when it starts to load content. When the content is fully loaded, we get a message from the web view informing us about this. We do this through the delegate property of the web view. A delegate of a web view must conform to the UIWebViewDelegate protocol.

Let’s go ahead and implement the little activity indicator in our view controller. Please bear in mind that the activity indicator is already a part of the application and we don’t have to create it. We can control it using the setNetworkActivityIndicatorVisible: method of UIApplication. So let’s start with the implementation file of our view controller:

@interface ViewController () <UIWebViewDelegate>
@property(nonatomic, strong) UIWebView *myWebView;
@end

@implementation ViewController

Then, do the implementation. Here we will use three of the methods declared in the UIWebViewDelegate protocol:

webViewDidStartLoad:

This method gets called as soon as the web view starts loading content.

webViewDidFinishLoad:

This method gets called as soon as the web view finishes loading content.

webView:didFailLoadWithError:

This method gets called when the web view stops loading content, for instance because of an error or a broken network connection.

- (void)webViewDidStartLoad:(UIWebView *)webView{
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
}

- (void)webViewDidFinishLoad:(UIWebView *)webView{
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}

- (void)viewDidLoad{
    [super viewDidLoad];

    self.myWebView = [[UIWebView alloc] initWithFrame:self.view.bounds];
    self.myWebView.delegate = self;
    self.myWebView.scalesPageToFit = YES;
    [self.view addSubview:self.myWebView];

    NSURL *url = [NSURL URLWithString:@"http://www.apple.com"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    [self.myWebView loadRequest:request];

}

1.25. Displaying Progress with UIProgressView

Problem

You want to display a progress bar on the screen, depicting the progress of a certain task; for instance, the progress of downloading a file from a URL.

Solution

Instantiate a view of type UIProgressView and place it on another view.

Discussion

A progress view is what programmers generally call a progress bar. An example of a progress view is depicted in Figure 1-68.

A simple progress view
Figure 1-68. A simple progress view

Progress views are generally displayed to users to show them the progress of a task that has a well-defined starting and ending point. For instance, downloading 30 files is a well-defined task with a specific starting and ending point. This task obviously finishes when all 30 files have been downloaded. A progress view is an instance of UIProgressView and is initialized using the designated initializer of this class, the initWithProgressViewStyle: method. This method takes in the style of the progress bar to be created as a parameter. This parameter is of type UIProgressViewStyle and can therefore be one of the following values:

UIProgressViewStyleDefault

This is the default style of the progress view. An example of this is the progress view shown in Figure 1-68.

UIProgressViewStyleBar

This is similar to the UIProgressViewStyleDefault but is meant to be used for progress views that are to be added to a toolbar.

An instance of UIProgressView defines a property called progress (of type float). This property tells iOS how the bar inside the progress view should be rendered. This value must be in the range +0 to +1.0. If the value of +0 is given, the progress bar won’t appear to have started yet. A value of +1.0 shows the progress of 100%. The progress depicted in Figure 1-68 is 0.5 (or 50%).

To get used to creating progress views, let’s create one similar to what we saw in Figure 1-68. First things first: define a property for your progress view:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) UIProgressView *progressView;
@end

@implementation ViewController

Then instantiate an object of type UIProgressView:

- (void)viewDidLoad{

    [super viewDidLoad];

    self.progressView = [[UIProgressView alloc]
                         initWithProgressViewStyle:UIProgressViewStyleBar];
    self.progressView.center = self.view.center;
    self.progressView.progress = 20.0f / 30.0f;
    [self.view addSubview:self.progressView];

}

Obviously, creating a progress view is very straightforward. All you really need to do is display your progress correctly, because the progress property of a progress view should be in the range +0 to +1.0, which is a normalized value. So if you have 30 tasks to take care of and you have completed 20 of them so far, you need to assign the result of the following equation to the progress property of your progress view:

self.progressView.progress = 20.0f / 30.0f;

Warning

The reason the values 20 and 30 are passed to the equation as floating-point values is to tell the compiler that the division has to happen on floating-point values, producing a value with decimal numbers. If you provided the integer division 20/30 to the compiler to place inside the progress property of your progress view, you would get the integral value of 0 out of the division, because the compiler will perform integer division that truncates the result to the next lower integer. In short, your progress view would show zero progress all the way to the end, when 30/30 produces the result of 1; not of much value to the user.

1.26. Constructing and Displaying Styled Texts

Problem

You want to be able to be able to display rich formatted text in your UI components without having to create a separate UI component per attribute. For instance, you may want to display one sentence that contains only one of its words written in bold, inside a UILabel.

Solution

Construct an instance of the NSAttributedString or the mutable variant of it, the NSMutableAttributedString, and either set it as the text of a UI component like the UILabel component through its special attributed string property, or simply use the attributed string’s built-in methods to draw the text on a canvas.

Discussion

Rich text is a thing of legend! A lot of us programmers have had the requirement to display mixed-style strings in one line of text on our UI. For instance, in one line of text you may have to display straight and italic text together, where one word is italic and the rest of the words are regular text. Or you may have had to underline a word inside a sentence. For this, some of us had to use Web Views, but that is not the optimal solution because Web Views are quite slow in rendering their content, and that will definitely impact the performance of your app. In iOS 7, we can start using attributed strings. I don’t know what took Apple so long to introduce this feature to iOS, as Mac developers have been using attributed strings for a long time now!

Before we begin, I want to clearly show you what I mean by attributed strings, using Figure 1-69. Then we will set out on the journey to write the program to achieve exactly this.

An attributed string is displayed on the screen inside a simple label
Figure 1-69. An attributed string is displayed on the screen inside a simple label

Note

Just to be explicit, this text is rendered inside a single instance of the UILabel class.

So what do we see in this example? I’ll list the pieces:

The text “iOS” with the following attributes:
  • Bold font with size of 60 points

  • Background color of black

  • Font color of red

The text “SDK” with the following attributes:
  • Bold font with size of 60 points

  • White text color

  • Light gray shadow

The best way to construct attributed strings is to use the initWithString: method of the mutable variant of the NSMutableAttributedString class and pass an instance of the NSString to this method. This will create our attributed string without any attributes. Then, to assign attributes to different parts of the string, we will use the setAttributes:range: method of the NSMutableAttributedString class. This method takes in two parameters:

setAttributes

A dictionary whose keys are character attributes and the value of each key depends on the key itself. Here are the most important keys that you can set in this dictionary:

NSFontAttributeName

The value of this key is an instance of UIFont and defines the font for the specific range of your string.

NSForegroundColorAttributeName

The value for this key is of type UIColor and defines the color for your text for the specific range of your string.

NSBackgroundColorAttributeName

The value of this key is of type UIColor and defines the background color on which the specific range of your string has to be drawn.

NSShadowAttributeName

The value of this key must be an instance of the NSShadow and defines the shadow that you want to use under the specific range of your string.

range

A value of type NSRange that defines the starting point and the length of characters to which you want to apply the attributes.

Note

To see all the different keys that you can pass to this method, simply browse the Apple documentation online for the NSMutableAttributedString class. I will not put the direct URL to this documentation here as Apple may change the URL at some point, but a simple search online will do the trick.

We’ll break our example down into two dictionaries of attributes. The dictionary of attributes for the word “iOS” can be constructed in this way in code:

NSDictionary *attributesForFirstWord = @{
                 NSFontAttributeName : [UIFont boldSystemFontOfSize:60.0f],
                 NSForegroundColorAttributeName : [UIColor redColor],
                 NSBackgroundColorAttributeName : [UIColor blackColor]
                 };

And the word “SDK” will be constructed using the following attributes:

NSShadow *shadow = [[NSShadow alloc] init];
shadow.shadowColor = [UIColor darkGrayColor];
shadow.shadowOffset = CGSizeMake(4.0f, 4.0f);

NSDictionary *attributesForSecondWord = @{
                  NSFontAttributeName : [UIFont boldSystemFontOfSize:60.0f],
                  NSForegroundColorAttributeName : [UIColor whiteColor],
                  NSBackgroundColorAttributeName : [UIColor redColor],
                  NSShadowAttributeName : shadow
                  };

Putting it together, we will get the following code that not only creates our label, but also sets its attributed text:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) UILabel *label;
@end

@implementation ViewController

- (NSAttributedString *) attributedText{

    NSString *string = @"iOS SDK";

    NSMutableAttributedString *result = [[NSMutableAttributedString alloc]
                                         initWithString:string];

    NSDictionary *attributesForFirstWord = @{
                     NSFontAttributeName : [UIFont boldSystemFontOfSize:60.0f],
                     NSForegroundColorAttributeName : [UIColor redColor],
                     NSBackgroundColorAttributeName : [UIColor blackColor]
                     };

    NSShadow *shadow = [[NSShadow alloc] init];
    shadow.shadowColor = [UIColor darkGrayColor];
    shadow.shadowOffset = CGSizeMake(4.0f, 4.0f);

    NSDictionary *attributesForSecondWord = @{
                      NSFontAttributeName : [UIFont boldSystemFontOfSize:60.0f],
                      NSForegroundColorAttributeName : [UIColor whiteColor],
                      NSBackgroundColorAttributeName : [UIColor redColor],
                      NSShadowAttributeName : shadow
                      };

    /* Find the string "iOS" in the whole string and sets its attribute */
    [result setAttributes:attributesForFirstWord
                    range:[string rangeOfString:@"iOS"]];

    /* Do the same thing for the string "SDK" */
    [result setAttributes:attributesForSecondWord
                    range:[string rangeOfString:@"SDK"]];

    return [[NSAttributedString alloc] initWithAttributedString:result];

}

- (void)viewDidLoad{
    [super viewDidLoad];

    self.label = [[UILabel alloc] init];
    self.label.backgroundColor = [UIColor clearColor];
    self.label.attributedText = [self attributedText];
    [self.label sizeToFit];
    self.label.center = self.view.center;
    [self.view addSubview:self.label];

}

@end

1.27. Presenting Master-Detail Views with UISplitViewController

Problem

You want to take maximum advantage of iPad’s relatively large screen by presenting two side-by-side view controllers.

Solution

Use the UISplitViewController class.

Discussion

Split view controllers are present only on the iPad. If you’ve used an iPad, you’ve probably already seen them. Just open the Settings app in landscape mode and have a look. Can you see the split view controller there in Figure 1-70?

Split view controller in the Settings app on the iPad
Figure 1-70. Split view controller in the Settings app on the iPad

A split view controller has left and right sides. The left side displays the main settings, and tapping on each one of those settings shows the details of that setting item on the right side of the split view controller.

Warning

Never attempt to instantiate an object of type UISplitViewController on a device other than an iPad. This will raise an exception.

Apple has made it extremely easy to create split view controller based applications. Simply follow these steps to create your app based on split view controllers:

  1. In Xcode, navigate to the File menu and choose New New Project...

  2. In the New Project screen, pick iOS Application on the left side and then pick Master-Detail Application (as shown in Figure 1-71) and press Next.

  1. In this screen, pick your Product Name and make sure your Device Family is Universal. We want to make sure our app runs both on the iPhone and the iPad. Once you are done, press Next (see Figure 1-72).

    Picking the Master-Detail Application project template in Xcode
    Figure 1-71. Picking the Master-Detail Application project template in Xcode
    Setting the master-detail project settings in Xcode
    Figure 1-72. Setting the master-detail project settings in Xcode
  2. Now pick where you would like to save your project. Once done, press the Create button.

Now the project is created. In the Scheme breadcrumb button on the top-left corner of Xcode, make sure your app is set to run on the iPad Simulator instead of the iPhone Simulator. If you create a universal master-detail app in Xcode, Xcode makes sure that your app runs on the iPhone as well, but when you run your app on the iPhone, the structure of the app will be different. It will have a navigation controller with a view controller inside it, whereas running the same app on the iPad will use a split view controller with two view controllers inside it.

There are two files that are very important to note in the split view controller project template:

MasterViewController

The master view controller that appears on the left side of the split view controller on the iPad. On the iPhone, it is the first view controller that the user sees.

DetailViewController

The detail view controller that appears on the right side of the split view controller on the iPad. On the iPhone, it is the view controller that gets pushed onto the stack once the user taps on any of the items on the root (first, master) view controller.

Now you need to think about communication between the master and the detail view controller. Do you want the communication to be done through the app delegate, or do you want the master view controller to send messages to the detail view controller directly? That’s really up to you.

If you run the app in iPad Simulator, you’ll notice that in landscape mode, you can see our master and detail view controllers in the split view controller, but as soon as you rotate the orientation to portrait, your master view controller is gone and is replaced with a Master navigation button on the top-left side of the navigation bar of the detail view controller. Although this is good, we weren’t expecting it, since we were comparing it with the Settings app on the iPad. If you rotate the settings app to portrait on an iPad, you can still see both the master and the detail view controllers. How can we accomplish this? It turns out Apple has exposed an API to us through which we can do it. Simply go to the DetailViewController.m file and implement this method:

- (BOOL) splitViewController:(UISplitViewController *)svc
    shouldHideViewController:(UIViewController *)vc
               inOrientation:(UIInterfaceOrientation)orientation{
    return NO;
}

If you return NO from this method, iOS will not hide the master view controller in either orientation and both the master and the detail view controllers will be visible in both landscape and portrait orientations. Now that we have implemented this method, we won’t need those two methods anymore:

- (void)splitViewController:(UISplitViewController *)splitController
     willHideViewController:(UIViewController *)viewController
          withBarButtonItem:(UIBarButtonItem *)barButtonItem
       forPopoverController:(UIPopoverController *)popoverController{
    barButtonItem.title = NSLocalizedString(@"Master", @"Master");
    [self.navigationItem setLeftBarButtonItem:barButtonItem animated:YES];
    self.masterPopoverController = popoverController;
}

- (void)splitViewController:(UISplitViewController *)splitController
     willShowViewController:(UIViewController *)viewController
  invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem{
    [self.navigationItem setLeftBarButtonItem:nil animated:YES];
    self.masterPopoverController = nil;
}

These methods were there simply to manage the navigation bar button for us, but now that we are not using that button any more, we can get rid of the methods. You can comment them out, or just remove them from the DetailViewController.m file.

If you look inside your master view controller’s header file, you’ll notice something similar to this:

#import <UIKit/UIKit.h>

@class DetailViewController;

@interface MasterViewController : UITableViewController

@property (strong, nonatomic) DetailViewController *detailViewController;

@end

As you can see, the master view controller has a reference to the detail view controller. Using this connection, we can communicate selections and other values to the detail view controller, as you will soon see.

By default, if you run your app now in the iPad Simulator, you will see a UI similar to that shown in Figure 1-73. The default implementation that Apple provides us with in the master view controller has a mutable array that gets populated with instances of NSDate every time you press the plus (+) button on the navigation bar of the master view controller. The default implementation is very simple and you can modify it by learning a bit more about table views. Please refer to Chapter 4 for more details about table views and populating them.

An empty split view controller running on the iPad Simulator
Figure 1-73. An empty split view controller running on the iPad Simulator

1.28. Enabling Paging with UIPageViewController

Problem

You want to create an app that works similarly to iBooks, where the user can flip through the pages of a book as if it were a real book, to provide an intuitive and real user experience.

Solution

Use UIPageViewController.

Discussion

Xcode has a template for page view controllers. It’s best to first see how they look before reading an explanation of what they actually are. So follow these steps to create your app to use page view controllers:

Note

Page view controllers work both on the iPhone and the iPad.

  1. In Xcode, go to the File menu and then choose New New Project...

  2. On the lefthand side of the New Project window, make sure you’ve selected iOS and then Application. Once that is done, pick the Page-Based Application template from the right side and press Next, as shown in Figure 1-74.

Creating a Page-Based Application in Xcode
Figure 1-74. Creating a Page-Based Application in Xcode
  1. Now, select a product name and make sure the Device Family that you’ve chosen is Universal, as you normally would want your app to run on both the iPhone and the iPad (see Figure 1-75). Once you are done, press Next.

  2. Select where you want to save your project. Once you are done, press the Create button. You have now successfully created your project.

Setting the project settings of a page-based app
Figure 1-75. Setting the project settings of a page-based app

You can now see that Xcode has created quite a few classes in your project. Let’s have a quick look at what each one of these classes does:

Delegate Class

The app delegate simply creates an instance of the RootViewController class and presents it to the user. There is one .xib for iPad and another one of iPhone, but both are using the aforementioned class.

RootViewController

Creates an instance of UIPageViewController and adds that view controller to itself. So the UI of this view controller is actually a mix of two view controllers: the RootViewController itself and a UIPageViewController.

DataViewController

For every page in the page view controller, an instance of this class gets presented to this user. This class is a subclass of UIViewController.

ModelController

This is simply a subclass of NSObject that conforms to the UIPageViewControllerDataSource protocol. This class is the data source of the page view controller.

So you can see that a page view controller has both a delegate and a data source. With Xcode’s default page-based application template, the root view controller becomes the delegate and the model controller becomes the data source of the page view controller. In order to understand how a page view controller really works, we need to understand its delegation and data source protocols. Let’s start with the delegate, UIPageViewControllerDelegate. This protocol has two important methods:

- (void)pageViewController:(UIPageViewController *)pageViewController
        didFinishAnimating:(BOOL)finished
   previousViewControllers:(NSArray *)previousViewControllers
       transitionCompleted:(BOOL)completed;

- (UIPageViewControllerSpineLocation)pageViewController
:(UIPageViewController *)pageViewController
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation;

The first method gets called when the user turns to the next or the previous page, or if the user initiates the movement from one page to the other but decides against it while the page is moving (in which case, the user gets sent back to the page she was in before). The transitionCompleted will get set to YES if this was a successful page animation, or set to NO if the user decided against the movement and cancelled it in the middle of the animation.

The second method gets called whenever the device orientation changes. You can use this method to specify the location of the spine for the pages by returning a value of type UIPageViewControllerSpineLocation:

typedef NS_ENUM(NSInteger, UIPageViewControllerSpineLocation) {
    UIPageViewControllerSpineLocationNone = 0,
    UIPageViewControllerSpineLocationMin = 1,
    UIPageViewControllerSpineLocationMid = 2,
    UIPageViewControllerSpineLocationMax = 3
};

This might be a bit confusing to you, but let me demonstrate. If we are using a UIPageViewControllerSpineLocationMin spine location, the page view controller will require only one view controller to present to the user, and when the user goes to the next page, a new view controller will be presented to him. However, if we set the spine location to UIPageViewControllerSpineLocationMid, we will be required to display 2 view controllers at the same time: one on the left and another on the right, with the spine sitting between them. Let me show you what I mean. In Figure 1-76 you can see an example of a page view controller in landscape mode, with the spine location set to UIPageViewControllerSpineLocationMin.

One view controller presented in a page view controller in landscape mode
Figure 1-76. One view controller presented in a page view controller in landscape mode

Now if we return the spine location of UIPageViewControllerSpineLocationMid, we will get results similar to Figure 1-77.

Two view controllers displayed in a page view controller in landscape mode
Figure 1-77. Two view controllers displayed in a page view controller in landscape mode

As you can see in that image, the spine is located exactly in the center of the screen between two view controllers. Once the user flips a page from right to the left, the page rests on the left and the page view controller reveals a new view controller on the right side. This whole logic is in this delegate method:

- (UIPageViewControllerSpineLocation)pageViewController
:(UIPageViewController *)pageViewController
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation;

We’ve now covered the delegate of the page view controller, but how about the data source? The data source of a page view controller must conform to the UIPageViewControllerDataSource. Two important methods that this protocol exposes are:

- (UIViewController *)pageViewController
:(UIPageViewController *)pageViewController
viewControllerBeforeViewController:(UIViewController *)viewController;

- (UIViewController *)pageViewController
:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController;

The first method gets called when the page view controller already has a view controller on the screen and needs to know which previous view controller to render. This happens when the user decides to flip to the next page. The second method is called when the page view controller needs to figure out which view controller to display after the view controller that is being flipped.

Xcode, as you’ve already seen, has greatly simplified setting up a page-based application. All you really need to do now is to provide content to the data model (ModelController) and off you go. If you need to customize the colors and images in your view controllers, do so by either using the Interface Builder to modify the storyboard files directly or write your own code in the implementation of each of the view controllers.

1.29. Displaying Popovers with UIPopoverController

Problem

You want to display content on an iPad without blocking the whole screen.

Solution

Use popovers.

Discussion

Popovers are used to display additional information on the iPad screen. An example can be seen in the Safari app on the iPad. When the user taps on the Bookmarks button, she will see a popover displaying the bookmarks content on the screen (see Figure 1-78).

The bookmarks popover in the Safari app on an iPad
Figure 1-78. The bookmarks popover in the Safari app on an iPad

The default behavior of popovers is that when the user taps somewhere outside the region of the popover, the popover will automatically get dismissed. You can ask the popover to not get dismissed if the user taps on specific parts of the screen, as we will see later. Popovers present their content by using a view controller. Note that you can also present navigation controllers inside popovers, because navigation controllers are a subclass of UIViewController.

Warning

Popovers can be used only on iPad devices. If you have a view controller whose code runs both on an iPad and on an iPhone, you need to make sure that you are not instantiating the popover on a device other than the iPad.

Popovers can be presented to the user in two ways:

  1. From inside a navigation button, an instance of UIBarButtonItem

  2. From inside a rectangular area in a view

When a device orientation is changed (the device is rotated), popovers are either dismissed or hidden temporarily. You need to make sure that you give your users a good experience by redisplaying the popover after the orientation change has settled, if possible. In certain cases, your popover might get dismissed automatically after an orientation change. For instance, if the user taps on a navigation button in landscape mode you might display a popover on the screen. Suppose your app is designed so that when the orientation changes to portrait, the navigation button is removed from the navigation bar for some reason. Now, the correct user experience would be to hide the popover associated with that navigation bar after the orientation of the device is changed to portrait. In some instances, though, you will need to play with popovers a bit to give your users a good experience, because handling device orientation is not always as straightforward as in the aforementioned scenario.

To create the demo popover app, we need to first come up with a strategy based on our requirements. For this example, we want to build an app with a view controller loaded inside a navigation controller. The root view controller will display a + button on the right corner of its navigation bar. When the + button is tapped on an iPad device, it will display a popover with two buttons on it. The first button will say “Photo” and the second button will say “Audio.” When the same navigation button is tapped on an iPhone device, we will display an alert view with three buttons: the two aforementioned buttons, and a cancel button so that the user can cancel the alert view if he wishes to. When these buttons are tapped (whether on the alert view on an iPhone or the popover on an iPad), we won’t really do anything. We will simply dismiss the alert view or the popover.

Go ahead and create a Single View universal project in Xcode and name the project “Displaying Popovers with UIPopoverController”. Then, using the technique that you learned in Recipe 6.1, add a navigation controller to your storyboard so that your view controllers will have a navigation bar.

After this, we need to go into the definition our root view controller and define a property of type UIPopoverController:

#import "ViewController.h"

@interface ViewController () <UIAlertViewDelegate>
@property (nonatomic, strong) UIPopoverController *myPopoverController;
@property (nonatomic, strong) UIBarButtonItem *barButtonAdd;
@end

@implementation ViewController

<# Rest of your code goes here #>

You can see that we are also defining a property called barButtonAdd in our view controller. This is the navigation button that we will add on our navigation bar. Our plan is to display our popover when the user taps on this button (you can read more about navigation buttons in Recipe 1.15). However, we need to make sure we instantiate the popover only if the device is an iPad. Before we go ahead and implement our root view controller with the navigation button, let’s go ahead and create a subclass of UIViewController and name it PopoverContentViewController. We will display the contents of this view controller inside our popover later. See Recipe 1.9 for information about view controllers and ways of creating them.

The content view controller displayed inside the popover will have two buttons (as per our requirements). However, this view controller will need to have a reference to the popover controller in order to dismiss the popover when the user taps on any of the buttons. For this, we need to define a property in our content view controller to refer to the popover:

#import <UIKit/UIKit.h>

@interface PopoverContentViewController : UIViewController

/* We shouldn't define this as strong. That will create a retain cycle
 between the popover controller and the content view controller since the
 popover controller retains the content view controller and the view controller will
 retain the popover controller */
@property (nonatomic, weak) UIPopoverController *myPopoverController;

@end

And, also inside the implementation file of our content view controller, we declare our bar buttons:

#import "PopoverContentViewController.h"

@interface PopoverContentViewController ()
@property (nonatomic, strong) UIButton *buttonPhoto;
@property (nonatomic, strong) UIButton *buttonAudio;
@end

@implementation PopoverContentViewController

<# Rest of your code goes here #>

After this, we’ll create our two buttons in the content view controller and link them to their action methods. These methods will take care of dismissing the popover that is displaying this view controller. Remember, the popover controller will be responsible for assigning itself to the popoverController property of the content view controller:

- (BOOL) isInPopover{

    Class popoverClass = NSClassFromString(@"UIPopoverController");

    if (popoverClass != nil &&
        UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad &&
        self.myPopoverController != nil){
        return YES;
    } else {
        return NO;
    }

}

- (void) gotoAppleWebsite:(id)paramSender{

    if ([self isInPopover]){
        /* Go to website and then dismiss popover */
        [self.myPopoverController dismissPopoverAnimated:YES];
    } else {
        /* Handle case for iPhone */
    }

}

- (void) gotoAppleStoreWebsite:(id)paramSender{

    if ([self isInPopover]){
        /* Go to website and then dismiss popover */
        [self.myPopoverController dismissPopoverAnimated:YES];
    } else {
        /* Handle case for iPhone */
    }

}

- (void)viewDidLoad{
    [super viewDidLoad];

    self.preferredContentSize = CGSizeMake(200.0f, 125.0f);

    CGRect buttonRect = CGRectMake(20.0f,
                                   20.0f,
                                   160.0f,
                                   37.0f);

    self.buttonPhoto = [UIButton buttonWithType:UIButtonTypeSystem];
    [self.buttonPhoto setTitle:@"Photo"
                      forState:UIControlStateNormal];
    [self.buttonPhoto addTarget:self
                         action:@selector(gotoAppleWebsite:)
               forControlEvents:UIControlEventTouchUpInside];

    self.buttonPhoto.frame = buttonRect;

    [self.view addSubview:self.buttonPhoto];


    buttonRect.origin.y += 50.0f;
    self.buttonAudio = [UIButton buttonWithType:UIButtonTypeSystem];

    [self.buttonAudio setTitle:@"Audio"
                      forState:UIControlStateNormal];
    [self.buttonAudio addTarget:self
                         action:@selector(gotoAppleStoreWebsite:)
               forControlEvents:UIControlEventTouchUpInside];

    self.buttonAudio.frame = buttonRect;

    [self.view addSubview:self.buttonAudio];

}

Now in the viewDidLoad method of our root view controller, we will create our navigation button. Based on the device type, when the navigation bar is tapped, we will display either a popover (on the iPad) or an alert view (on the iPhone):

- (void)viewDidLoad{
    [super viewDidLoad];

    /* See if this class exists on the iOS running the app */
    Class popoverClass = NSClassFromString(@"UIPopoverController");

    if (popoverClass != nil &&
        UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){

        PopoverContentViewController *content =
        [[PopoverContentViewController alloc] initWithNibName:nil
                                                       bundle:nil];

        self.myPopoverController = [[UIPopoverController alloc]
                                  initWithContentViewController:content];

        content.myPopoverController = self.myPopoverController;

        self.barButtonAdd =
        [[UIBarButtonItem alloc]
         initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
         target:self
         action:@selector(performAddWithPopover:)];

    } else {

        self.barButtonAdd =
        [[UIBarButtonItem alloc]
         initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
         target:self
         action:@selector(performAddWithAlertView:)];

    }

    [self.navigationItem setRightBarButtonItem:self.barButtonAdd
                                      animated:NO];

}

Note

The popover controller sets a reference to itself in the content view controller after its initialization. This is very important. A popover controller cannot be initialized without a content view controller. Once the popover is initialized with a content view controller, you can go ahead and change the content view controller in the popover controller, but not during the initialization.

We have elected the performAddWithPopover: method to be invoked when the + navigation bar button is tapped on an iPad device. If the device isn’t an iPad, we’ve asked the + navigation bar button to invoke the performAddWithAlertView: method. Let’s go ahead and implement these methods and also take care of the delegate methods of our alert view, so that we know what alert view button the user tapped on an iPhone:

- (NSString *) photoButtonTitle{
    return @"Photo";
}

- (NSString *) audioButtonTitle{
    return @"Audio";
}

- (void)          alertView:(UIAlertView *)alertView
  didDismissWithButtonIndex:(NSInteger)buttonIndex{

    NSString *buttonTitle = [alertView buttonTitleAtIndex:buttonIndex];

    if ([buttonTitle isEqualToString:[self photoButtonTitle]]){
        /* Adding a photo ... */
    }
    else if ([buttonTitle isEqualToString:[self audioButtonTitle]]){
        /* Adding an audio... */
    }

}

- (void) performAddWithAlertView:(id)paramSender{

    [[[UIAlertView alloc] initWithTitle:nil
                                message:@"Add..."
                               delegate:self
                      cancelButtonTitle:@"Cancel"
                      otherButtonTitles:
      [self photoButtonTitle],
      [self audioButtonTitle], nil] show];

}

- (void) performAddWithPopover:(id)paramSender{

    [self.myPopoverController
     presentPopoverFromBarButtonItem:self.barButtonAdd
     permittedArrowDirections:UIPopoverArrowDirectionAny
     animated:YES];

}

If you now run your app on iPad Simulator and tap the + button on the navigation bar, you will see an interface similar to Figure 1-79:

Our simple popover displayed when a navigation button was tapped
Figure 1-79. Our simple popover displayed when a navigation button was tapped

If you run the same universal app on the iPhone Simulator and tap the + button on the navigation bar, you will see results similar to Figure 1-80.

Popovers are replaced by alert view in a universal app
Figure 1-80. Popovers are replaced by alert view in a universal app

We used an important property of our content view controller: preferredContentSize. The popover, when displaying its content view controller, will read the value of this property automatically and will adjust its size (width and height) to this size. Also, we used the presentPopoverFromBarButtonItem:permittedArrowDirections:animated: method of our popover in our root view controller to display the popover over a navigation bar button. The first parameter to this method is the navigation bar button from which the popover controller has to be displayed. The second parameter specifies the direction of the popover when it appears, in relation to the object from which it appears. For example, in Figure 1-79, you can see that our popover’s arrow is pointing up toward the navigation bar button. The value that you pass to this parameter must be of type UIPopoverArrowDirection:

typedef NS_OPTIONS(NSUInteger, UIPopoverArrowDirection) {
    UIPopoverArrowDirectionUp = 1UL << 0,
    UIPopoverArrowDirectionDown = 1UL << 1,
    UIPopoverArrowDirectionLeft = 1UL << 2,
    UIPopoverArrowDirectionRight = 1UL << 3,
    UIPopoverArrowDirectionAny = UIPopoverArrowDirectionUp |
    UIPopoverArrowDirectionDown |
    UIPopoverArrowDirectionLeft |
    UIPopoverArrowDirectionRight,
    UIPopoverArrowDirectionUnknown = NSUIntegerMax
};

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required