iOS App Development For Dummies (2014)

Part IV. The Model and the App Structure

image

image Visit www.dummies.com/extras/iosappdevelopment for more on how to use frameworks in iOS app development.

In this part …

·        Implementing the Master View Controller

·        Working with split view controllers and the master view

·        Finishing the basic app structure

Chapter 11. The Trip Model

In This Chapter

arrow Recognizing the importance of models

arrow Working with property lists

arrow Starting the Trip class implementation

arrow Displaying variable content in the debugger

It’s time to add some content to RoadTrip. The owner of RoadTrip’s data is the model, so I start this chapter with a look at the data you’ll need and how the model will manage it. Then I show you how to add a Trip class that will serve as the interface to the model. Finally, you see how to add the Destination class — which is one of the objects that will be used by the model but is hidden behind the Trip interface.

What the Model Contains

In the Model-View-Controller (MVC) design pattern that is the basis for all iOS apps, the model owns the app content logic and data. Therefore, to design your model, the best place to start is with the app design I present in Chapter 4 and consider what will be displayed in the views.

In this first version of RoadTrip, the model needs to provide, for each destination, the following:

·        The destination name

·        A background image for the destination

·        The destination latitude and longitude, along with a title and subtitle to be displayed in the annotation on a map

·        Events that the user might be interested in

·        Titles and subtitles of sights or places of interest to be displayed in the annotation on a map

·        Weather information

I’ve made the point (several times) that the model is about the data, so where does the data come from? The easy answer is … any place you’d like. Given the iOS model, however, you can take several approaches:

·        Option 1: Download the data from a web service (or FTP site) and have the model control it in a file or have Core Data (an iOS object persistence mechanism outside the scope of this book) manage it.

·        Option 2: Have a web service manage your data and get what you need as you need it.

·        Option 3: Include the data as an app resource.

·        Option 4: Access the data on the web as an HTML page.

Although I really like Option 1 (and explain that in my book Sams Teach Yourself Core Data for Mac and iOS in 24 Hours), it's beyond the scope of this book, so the Trip model uses both Options 3 and 4.

Although the preceding answers most of the model’s “Show me the data” responsibility, yet another question still has to be answered: How does the model know where the data is? The answer to that question lies in a very useful structure that's used extensively by not only iOS but also Cocoa apps: property lists (more commonly known as plists). The RoadTrip plist that you are about to create will have both data used by the Trip model object (Option 3) as well as the URLs for the data you download as HTML pages (Option 4).

Adding the Model Data

For situations in which you need to store relatively small amounts of persistent data — say, less than a megabyte — a property list offers a uniform and convenient means of organizing, storing, and accessing the data.

Using property lists

A property list (or plist) is perfect for storing small amounts of data that consist primarily of strings and numbers. What adds to its appeal is the capability to easily read it into your program, use the data, and (although you won’t be doing it in the RoadTrip app) modify the data and then write the property list back out again (see the “Using plists to store data” sidebar, later in this chapter). That’s because iOS provides a small set of objects that have that behavior built right in.

Apps and other system software in OS X and iOS use property lists extensively. For example, the OS X Finder stores file and directory attributes in a property list, and iOS uses them for user defaults. You also get a Property List editor with Xcode, which makes property list files easy to create and maintain in your own programs.

Figure 11-1 shows a property list that I show you how to build — one that contains the data necessary for the RoadTrip app.

After you figure out how to work with property lists, it’s actually easy, but like most things, getting there is half the fun.

Property lists hold serializable objects. A serializable object can convert itself into a stream of bits so that it can be stored in a file; it can then reconstitute itself into the object it once was when it's read back in.

These objects, called property list objects, that you have to work with are as follows, and you find two types (which I explain in an upcoming paragraph):

Basic Classes:

·        NSData and NSMutableData

·        NSDate

·        NSNumber

·        NSString and NSMutableString

image

Figure 11-1: The RoadTrip plist.

Containers:

·        NSArray and NSMutableArray

·        NSDictionary and NSMutableDictionary

As previously shown in Figure 11-1, the RoadTrip plist is actually an NSDictionary named Root (this is true of all property list unless you change it). Root has one entry — DestinationData, which is an array of dictionaries — and the data for each one of the destinations is held in a dictionary in that array (Item 0 and Item 1).

Now for that explanation of two kinds of property list objects:

·        Basic classes: The term basic classes describes the simplest kind of object. They are what they are.

·        Containers: Containers can hold primitives as well as other containers.

One important feature of property list object containers (such as NSArray and NSDictionary), besides their ability to hold other objects, is that they both have a writeToFile: method that writes the object to a file, and a corresponding initWithContentsOfFile:, which initializes the object with the contents of a file. So if I create an array or dictionary and fill it chock-full of objects of the property list type, all I have to do to save that array or dictionary to a file is tell it to go save itself — or create an array or dictionary and then tell it to initialize itself from a file.

Primitives NSString and NSData and their mutable counterparts also can write and read themselves to and from a file.

NSData and NSMutableData are wrappers (a wrapper is an object whose basic purpose is to turn something into an object) in which you can dump any kind of digital data and then have that data act as an object.

image The containers can contain other containers as well as the primitive types. Thus, you might have an array of dictionaries, and each dictionary might contain other arrays and dictionaries as well as the primitive types.

Adding a property list to your project

Given the versatility of property lists, you’re sure to turn to them time and time again. Follow these steps to incorporate a plist into your Xcode project:

1.     In the Project navigator, add a RoadTrip Resources group to Supporting Files. Right-click it, and then choose New File from the menu that appears to get the New File dialog.

Or select the RoadTrip Resources group and choose File⇒New⇒New File from the main menu (or press image+N).

2.     In the left column of the New File dialog, select Resource under the iOS heading, select the Property List template in the top-right pane, and then click Next.

A Save sheet appears.

3.     Enter Destinations in the Save As field.

4.     Click Create (and make sure that the Target field has the RoadTrip check box selected).

A property list will be created with a single element called Root; it is a dictionary, but you can change it to be an array if you want.

5.     Right-click in the Root row to show the context-sensitive menu; then choose Add Row, as shown in Figure 11-2.

You can also choose Editor⇒Add Item to add a row.

A new row appears, as shown in Figure 11-3.

image

Figure 11-2: Add a row to the new plist file.

image

Figure 11-3: A new row.

6.     Enter DestinationData in the Key field, replacing New Item (which should be highlighted).

7.     Click in the up and down arrows in the Type field and then choose Array from the pop-up menu that appears, as shown in Figure 11-4.

image

Figure 11-4: Change the New Item to Array.

8.     Click the disclosure triangle in front of the DestinationData key so it is pointing down and click the + (plus) button, as shown in Figure 11-5. A new entry appears.

DestinationData is an array of dictionaries that will hold all your destination-specific information, with Item 0 being the first one.

In Figure 11-5, I've added the row, and you can see the + (plus) button in the new row.

image

Figure 11-5: Add an entry.

9.     Make Item 0 a dictionary by selecting Dictionary in the Type pop-up menu (in the same way you select Array in Step 7).

Your new entry is made into a dictionary, as shown in Figure 11-6.

10.  Click the disclosure triangle in front of the Item 0 key so that it points down, and click the + (plus) button as you did in Step 7 to add a new entry to the dictionary.

You see a new entry under the dictionary like the one in Figure 11-7.

image These disclosure triangles work the same way as those in the Finder and the Xcode editor. The Property List editor interprets what you want to add based on the triangle. So if the items are revealed (that is, the triangle is pointing down), the editor assumes that you want to add a sub item or child. If the sub items are not revealed (that is, the triangle is pointing sideways), the editor assumes that you want to add an item at that level (sibling). In this case, with the arrow pointing down, you add a new entry — a sub item — to the dictionary. If the triangle were pointing sideways, you would be entering a new entry under the root.

Only arrays and dictionaries have children.

image

Figure 11-6: A dictionary entry.

image

Figure 11-7: A new entry in the dictionary.

11.  In the Key field of your newest entry, enter DestinationName, leave the Type as String, and then double-click (or tab to) the Value field and enter New York City, as shown in Figure 11-8.

12.  Click the + button in the row you just entered, and you get a new entry (a sibling of the previous row). In the Key field, enter DestinationImage, leave the Type as String, and then double-click (or tab to) the Value field and enter NYCImage.

image

Figure 11-8: A dictionary entry.

13.  Click the + button in the row you just entered, and you get a new entry (a sibling of the previous row). In the Key field, enter DestinationLocation and select Dictionary in the Type pop-up menu.

14.  Click the disclosure triangle in front of the DestinationLocation key so it's facing down, and click the + button.

You see a new entry under the dictionary, as you can see in Figure 11-9.

image

Figure 11-9: A new dictionary with another entry.

15.  In the Key field, enter Title, and enter New York City in the Value field.

16.  Add these three keys with their corresponding type and value:

Key

Type

Value

Subtitle

String

A great city

Latitude

Number

40.712756

Longitude

Number

−74.006047

17.  When you're done, your plist should look like Figure 11-10.

18.  Click the disclosure triangle to hide theDestinationLocation dictionary entries, and add a new array named Events as a sibling of theDestinationLocation, as shown in Figure 11-11.

As explained previously, when the disclosure triangle is closed, you add a sibling entry.

19.  Continue filling out the plist to make it match Figure 11-12 and Table 11-1.

image

Figure 11-10: The ­Desti-
nation
Loca­tion entries.

image

Figure 11-11: The Events dictionary entry is an array.

image

Figure 11-12: The New York City destination entry.

Table 11-1 New York City plist Keys and Values

Key

Type

Value

DestinationName

String

New York City

DestinationImage

String

NYCImage

DestinationLocation

Dictionary

 

 Title

String

New York City

 Subtitle

String

A great city

 Latitude

Number

40.712756

 Longitude

Number

-74.006047

Events

Array

 

 Item 0

String

http://jessefeiler.com/NYC01.html

 Item 1

String

http://jessefeiler.com/NYC02.html

POIs

Array

 

 Item 0

Dictionary

 

 Title

String

The Statue of Liberty

 Subtitle

String

Pro-democracy and anti-monarchy symbol of freedom

 Latitude

Number

40.689244

 Longitude

Number

-74.044514

Weather

String

http://forecast.weather.gov/MapClick.php?zoneid=NYZ072

After you finish entering these items, close Item 0 under DestinationData and go through Steps 8 through 18 again to add the San Francisco entry using the keys and values in Table11-2. If you want to save some typing, you can copy and paste Item 0 and then expand the new Item 1 and simply replace the values. (That’s what I did.)

Table 11-2 San Francisco plist Keys and Values

Key

Type

Value

DestinationName

String

San Francisco

DestinationImage

String

SFImage

DestinationLocation

Dictionary

 

 Title

String

San Francisco

 Subtitle

String

A great city

 Latitude

Number

37.7793

 Longitude

Number

−122.4192

Events

Array

 

 Item 0

String

http://jessefeiler.com/SF01.html

 Item 1

String

http://jessefeiler.com/SF02.html

POIs

Array

 

 Item 0

Dictionary

 

 Title

String

Golden Gate Bridge

 Subtitle

String

An internationally recognized symbol of San Francisco

 Latitude

Number

37.819722

 Longitude

Number

−122.478611

Weather

String

http://forecast.weather.gov/MapClick.php?site=mtr&textField1=37.76&textField2=-122.43&smap=1

image Make sure that you spell the entries exactly as specified or else you won’t be able to access them using the examples in this book. Be especially careful of trailing spaces.

When you’re done, refer to Figure 11-1 to see how your plist should look.

You may wonder why you’re using this specific data (title and subtitle, for example). You’ll understand that when you explore maps in Chapter 17.


Using plists to store data

Although you won’t do it here, you can also modify a plist to store data. The only restriction of note is that you can’t modify a plist you’ve created in your bundle. You need to save it in the file system instead. For example:

  NSArray *paths = NSSearchPathForDirectoriesInDomains 
                    (NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];  
NSString *filePath = [documentsDirectory      
                        stringByAppendingPathComponent:state];
[updatedDestinations writeToFile:filePath atomically:YES];


Now that you have the information needed by the model to locate the data it's responsible for, it’s time to start adding some model classes, which I cover in the following section.

Adding the First Two Model Classes

The first model class I have you add is the Trip class. This will become the only model object visible to the view controllers. Although the Trip object will use other objects to carry out its responsibilities, hiding them behind the Trip object results in the loose coupling I explain in the next section and in “The importance of loose coupling” sidebar, later in this chapter.

Here is how to create your first two model classes:

1.     Create a new group to keep your model classes in by going to the Project navigator, selecting the RoadTrip group (not the RoadTrip project, which is at the top), and right-clicking and choosing New Group from the menu that appears.

Or you can select the RoadTrip group and choose File⇒New⇒Group from the main menu.

You’ll see a new group. (It looks like a folder.)

2.     Name your new group Model Classes.

image To change a file’s group, select the file and drag it to the group you want it to occupy. The same goes for groups as well. (After all, they can go into other groups.)

3.     In the Project navigator, select the Model Classes group, right-click the selection, and then choose New File from the menu that appears to open the New File dialog.

Or you can select the Model Classes group and choose File⇒New⇒File from the main menu (or press image+N).

You’ll see a dialog that enables you to select a file type.

4.     In the left column of the dialog, select Cocoa Touch under the iOS heading. Next, select the Objective-C class template in the top-right pane; then click Next.

5.     In the Class field, enter Trip. In the Subclass Of drop-down menu, select NSObject. Click Next.

A Save sheet appears.

6.     In the Save sheet, click Create.

7.     Repeat Steps 36 to create theDestination class.

You'll also be using the MapKit and CoreLocation frameworks, so add them as well:

1.     In the Project navigator, select the RoadTrip Project at the top of the Project navigator area to display the Project editor.

2.     In the Targets section, select RoadTrip.

3.     On the General tab, scroll down to the Linked Frameworks and Libraries section.

4.     Expand the Linked Frameworks and Libraries section, if it isn't already expanded, by clicking the disclosure triangle.

5.     Click the + button underneath the list of current project frameworks.

A list of frameworks appears.

6.     Scroll down and select both MapKit.framework and CoreLocation.framework from the list of frameworks.

7.     Click the Add button.

You’ll see the frameworks added to the Linked Frameworks and Libraries section.

8.     Close the Linked Frameworks and Libraries section.

image If you make a mistake and want to delete a file, right-click and choose Delete from the menu that appears or select the file and press Delete. Whichever method you choose, you'll see the dialog in Figure 11-13.

image

Figure 11-13: What would you like to do?

Remove Reference only removes the file from the Project navigator but leaves it on disk. You can't copy a new file with that name to that project until you delete that file from your project on the disk as well.

When you use the template to create a class, it adds the methods it thinks you may need. As a result, there will be some methods you won’t need and that appear to sit around doing nothing. You can delete them or just leave them in case you do need them. This is especially true of initialization methods because initialization in this app will be (mostly) taken care of by the storyboard.

Understanding the Trip Interface

Following is what the Trip interface will eventually become:

  #import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
@class Annotation;

@interface Trip : NSObject

- (UIImage *) destinationImage;
- (NSString *) destinationName;
- (CLLocationCoordinate2D) destinationCoordinate;

- (id)initWithDestinationIndex:(int)destinationIndex;
- (NSString *)weather;
- (int)numberOfEvents;
- (NSString *)getEvent:(int)index;
- (NSArray *)createAnnotations;
- (NSString *)mapTitle;
- (void)addLocation:(NSString *)findLocation 
    completionHandler:(void (^)(Annotation *annotation,   
                              NSError* error)) completion;
@end

As you can see, this code contains a lot of stuff, and I explain it as you add functionality to the Trip class along the way.

Earlier in the chapter, I mention that Trip is the model interface, and I say this because in many cases more than one class will be involved in delivering the necessary functionality of the model. For example, in this chapter you just added a Destination class that will be responsible for the information about your destination. An Events class that you will add in Chapter 16 will be responsible for managing the list of things going on at your destination, and an Annotation class (coming in Chapter 17) will provide the information you need to plot annotations (places to see) on a map. Hiding additional model objects behind the Trip object is known as loose coupling and is an important object-oriented design principle (see the nearby “The importance of loose coupling” sidebar).

You might be tempted to have the view controllers create the model classes they’ll use (for example, a WeatherController would create the Weather object, and so on). The problem with that approach is that it makes the coupling between controller objects and the model much tighter.

One advantage of the Model-View-Controller (MVC) design pattern that I explain in Chapter 4 is that it allows you to assign (most) classes into one of three groups in your app and work on them individually. If each group has a well-defined interface, it encapsulates many of the kinds of changes that are often made so that they don’t affect the other groups. This little fact is especially true of the model and view controller relationship.

If the view controllers have minimal knowledge about the model, you can change the model objects with minor impact on the view controllers. So although the Trip class will provide this functionality to the various view controllers, as I said, it won’t be doing all the work on its own. What makes this possible is a well-defined interface, which I showed you at the start of this section. You create an interface between the model and the controllers by using a technique called composition, which is a useful way to create interfaces.

image I’m a big fan of composition because it's another way to hide what’s really going on behind the curtain. It keeps the objects that use the composite object (in this case, Trip is the composite object) ignorant of the objects that the composite object uses and actually makes the components ignorant of each other, allowing you to switch components in and out at will.

So you'll have Trip create the model objects, encapsulating the knowledge of what objects make up the model from the object that uses it. Trip hides all implementation knowledge from a view controller; it will know only about the Trip object. Again, this setup makes everything loosely coupled and makes your app more extensible and easier to change.


The importance of loose coupling

A loosely coupled system is one in which each of its components has little or no knowledge (or makes no use of the knowledge it may have) of other components. The term loose coupling refers to the degree of direct knowledge that one class has of another. This isn't about encapsulation or one class's knowledge of another class's attributes or implementation, but rather knowledge of that other class itself.

Applying loose coupling means presenting a minimum interface to other objects. The client deals with the fewest number of objects as possible. So although you may want to break down a function into smaller pieces (for example, by using composition), you never want the client to know that. Clients are happy dealing with one object, even if that object then turns around and redistributes that work to other objects.

There are many ways to implement loose coupling, and Cocoa uses a number of them. Messages can be sent to objects, the identity of which is only known dynamically at run-time. Similarly, notifications can be sent out to any observer that has registered to receive them. In these cases (and more), the tight coupling of a known sender and recipient does not exist.


Implementing the Trip Class

In this chapter, I show you how to implement the Trip model functionality that will enable you to choose between multiple destinations (although you won’t be doing the choosing until Chapter 20). You also implement the Trip functionality that will be needed by the Master View controller (you add that in Chapter 12) — the name of the destination and its background image.

Start by adding the bolded code in Listing 11-1 to Trip.h.

Listing 11-1: Updating the Trip Interface

  #import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>

@interface Trip : NSObject

-(instancetype)initWithDestinationIndex:(int)destination Index;
- (UIImage *) destinationImage;
- (NSString *) destinationName;
- (CLLocationCoordinate2D) destinationCoordinate;
@end

As you can see, the code in Listing 11-1 contains an initialization method. This method will enable the Trip object to set itself up for the selected destination. Allow me to explain initialization and a few other things.

Initialization is the logical place to start, but first you need to add some instance variables and import the Destination header file if you're going to use it (which you are, in the Trip implementation). Add the bolded code in Listing 11-2 to Trip.m.

Listing 11-2: Updating the Trip Implementation

  #import "Trip.h"
#import "Destination.h"

@interface Trip ()  
  @property (strong, nonatomic) NSDictionary *destinationData;
  @property (strong, nonatomic) Destination* destination;
@end

@implementation Trip

Now you can add the initWithDestinationIndex: method in Listing 11-3 to Trip.m. Note that until you have completed the code in this chapter, you will have an error related to the not-yet-entered Destination class.

Listing 11-3: Adding initWithDestinationIndex:

  - (instancetype)initWithDestinationIndex:(int)destinationIndex {
  
  if ((self = [super init])) {
    
    NSString *filePath = [[NSBundle mainBundle] 
         pathForResource:@"Destinations" ofType:@"plist"];  
    NSDictionary *destinations = 
     [NSDictionary dictionaryWithContentsOfFile:filePath];
    NSArray *destinationsArray = 
           destinations[@"DestinationData"];
    _destinationData = 
       destinationsArray[destinationIndex];
    _destination = [[Destination alloc] 
               initWithDestinationIndex:destinationIndex];    
  }
  return self;
}

Before I explain the logic in these listings, I want to explain initialization in general. (And yes, you’ll see compiler warnings, and you’ll be fixing them as you go along.)

Initializing objects

Initialization is the procedure that sets the instance variables of an object (including pointers to other objects) to a known initial state. Essentially, you need to initialize an object in order to assign initial values to these variables. Initialization isn't required in every class in every app; if you can live with all the instance variables initialized to 0 and nil, you need do nothing. Trip, however, will need to create the objects it will be using, and you’ll do that during initialization.

image Even when you use declared properties, remember that Xcode is automatically creating backing variables for them. As a result, you may think you are declaring and then initializing the property, but in reality, Xcode is working with the backing variables.

An initialization method doesn’t have to include an argument for every instance variable because some will become relevant only during the course of your object’s existence. You must make sure, however, that all the instance variables your object uses, including other objects that it needs to do its work, are in a state that enables your object to respond to the messages it receives.

You may think that the main job in initialization is to, well, initialize the variables in your objects (hence the name), but more is involved when you have a superclass and a subclass chain.

image Most of the time you use declared properties to manage your class's data. Xcode automatically creates instance variables (sometimes called backing variables) and creates getters and setters to and from the instance variables that the property uses behind the scenes. However, inside your .m file and, particularly in initialization code, you sometimes work directly with the instance variable.

To see what I mean, start by looking at the initializer I use for the Trip class in Listing 11-3 (shown previously). By convention, initialization methods begin with the abbreviation init. (This is true, however, only for instance — as opposed to class — methods.) If the method takes no arguments, the method name is just init. If it takes arguments, labels for the arguments follow the init prefix. As you can see, the initializer in Listing 11-3 has a return type of instanceType. You discover the reason for that in the next section.

Initialization involves these three steps:

1.     Invoke the superclass init method.

2.     Initialize instance variables.

3.     Return self.

The following sections explain each step.

Invoking the superclass’s init method

Here is the type of statement you use to get the init method up and running:

    self = [super init];
  if (self) {

[super init] does nothing more than invoke the superclass init method. By convention, all initializers are required to assign self. self is the “hidden” variable accessible to methods in an object that points its instance variables to whatever object you get back from the superclass initializer, which explains self = [super init].

The if statement can be a little confusing to people. You may not get an object returned from the super class init method. If that's the case, you don’t want to do any further initialization.

Although the scenario just described is possible, it isn't common and won't happen in this book (and in general). You might find it in classes that need certain resources to initialize themselves, and if they aren't present, the object can't be created.

Initializing instance variables

Initializing instance variables, including creating the objects you need, is what you probably thought initialization is about. Notice that you’re initializing your instance variable after your superclass’s initialization, which you can see in Listing 11-3 (shown previously). Waiting until afteryour superclass does its initialization gives you the opportunity to actually change something your superclass may have done during its initialization, but more importantly, it allows you to perform initialization knowing that what you have inherited is initialized and ready to be used.

In your initWithDestinationIndex: method, you start by finding the plist that holds the Trip data or location of the data you need:

  NSString *filePath = [[NSBundle mainBundle] 
        pathForResource:@"Destinations" ofType:@"plist"];  

Next, you create a dictionary to hold the data. You use the method initWithContentsOfFile:, which does all the heavy lifting for you. It reads in the Destinations plist file and creates a dictionary for you. The plist, as I said previously, is really a dictionary with a single entry with the keyDestinationData. The dictionaryWithContentsOfFile: method creates a dictionary from the plist (and objects and keys for all of its entries) with dictionary keys that are the keys you specified in the plist.

This method also allocates and initializes all the elements in the dictionary (including other dictionaries), so when it's done, you're ready to roll:

  NSDictionary *destinations = 
     [NSDictionary dictionaryWithContentsOfFile:filePath];

image NSDictionary, NSMutableDictionary, NSArray, and NSMutableArray all have the methods initWithContentsOfFile: and writeToFile:atomically: that read themselves in from a file and write themselves out to a file, respectively. This is one of the capabilities that makes property list objects so useful.

image Property list containers — and only property list containers (and NSString and NSData) — can read themselves in from and write themselves out to a file. The other property list objects can only store themselves, without any effort on your part, as part of a file.

Your next step in initializing instance variables is to use the destination
Index to get the right element in the array of dictionaries in the Destination
Data entry based on the destination chosen by the user. (You’ll specify that in the AppDelegate in Listing 11-10, later in this chapter, where you'll allocate the Trip object.)

  NSArray *destinationsArray = 
           destinations[@"DestinationData"];
_destinationData = 
       destinationsArray[destinationIndex];

Finally, you allocate and initialize the Destination object:

  _destination = [[Destination alloc] 
               initWithDestinationIndex:destinationIndex];

Returning self

Earlier in this chapter, I explain that the self = statement ensures that self is set to whatever object you get back from the superclass initializer. No matter what you get back from invoking the superclass initializer in the initialization method, you need to set self to that value and then return it to the invoking method — the method that wants to instantiate the object or a subclass that invoked the superclass’s init method.

After the code block that initializes the variables, you insert the following:

  return self;

The reason the return type is an instancetype is that sometimes what you ask for isn't exactly what you get. But don’t worry; that becomes transparent to you if you follow the rules for initialization I just explained.

The reason that you may get back a different class than what you asked for is that under certain circumstances when you allocate a framework object, what you may get back may be a class optimized for your use based on the context. The relatively new instancetype is used to indicate that the returned value is not any old object but one that is specifically relevant to the initializer. Old code may return an id value which, indeed, can be any old object. Using instancetype is a safer and preferred style of coding today.


So where do the braces go?

If you look in the code provided by the template, sometimes you see a method implementation that looks like this:

  - (void)viewDidLoad
{

and sometimes you’ll see one that looks like this:

  - (IBAction)testDrive(id)sender {

I personally prefer the latter — with the bracket on the same line as the method — and will use it in the methods I have you add.

Quite frankly, it doesn’t matter to the compiler, but it can raise itself to a religious issue among programmers. Do what you’d like.


Initializing the Destination Class

Now it’s time to turn to the Destination class and its initialization. Add the bolded code in Listing 11-4 to Destination.h to update its interface to add the header files, the properties you’ll be using, and the method declarations.

Listing 11-4: Updating the Destination Interface

  #import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>

@interface Destination : NSObject

@property (nonatomic, readwrite) 
                       CLLocationCoordinate2D coordinate;
@property (nonatomic, readwrite, copy) NSString *title;
@property (nonatomic, readwrite, copy) NSString *subtitle;
@property (nonatomic, strong) NSString *destinationName;
@property (nonatomic, strong) UIImage *destinationImage;

- (instancetype)initWithDestinationIndex:
                             (NSUInteger)destinationIndex;
@end

You’ll be displaying the destination on a map with an annotation (that pop-up window that displays information about the location when you touch the pin on the map). Doing that requires a coordinate property of type CLLocationCoordinate2d (that’s why you need to include the MapKitand CoreLocation frameworks) and optional title and subtitle properties. Although you won’t be doing anything with this part of Destination until Chapter 17, I have you initialize Destination with what is described in the plist but defer the explanation until Chapter 17.

When you’re done, you can add the initWithDestinationIndex: method in Listing 11-5 to Destination.m.

Listing 11-5: Add initWithDestinationIndex:

  - (instancetype)initWithDestinationIndex:  
                            (NSUInteger)destinationIndex {
  
  self = [super init];
  if (self) {
    
    NSString *filePath = [[NSBundle mainBundle] 
         pathForResource:@"Destinations" ofType:@"plist"];  
    NSDictionary *destinations = [NSDictionary 
                   dictionaryWithContentsOfFile:filePath];
    NSArray *destinationsArray = 
           destinations[@"DestinationData"];
    NSDictionary *data = 
       destinationsArray[destinationIndex];
    
    
    _destinationImage = 
       [UIImage imageNamed:data[@"DestinationImage"]];

    _destinationName = 
                   data[@"DestinationName"]; 
    NSDictionary* destinationLocation = 
               data[@"DestinationLocation"];
    CLLocationCoordinate2D destinationCoordinate;
    destinationCoordinate.latitude = 
      [destinationLocation[@"Latitude"] doubleValue];
    destinationCoordinate.longitude = 
      [destinationLocation    
                   [@"Longitude"] doubleValue];
    _coordinate = destinationCoordinate;
    _title = 
              destinationLocation[@"Title"];
    _subtitle = 
           destinationLocation[@"Subtitle"];
  }
  return self;

}

Destination initializes itself more or less the same way that Trip did. It starts by loading its data:

  NSString *filePath = [[NSBundle mainBundle] 
         pathForResource:@"Destinations" ofType:@"plist"];  
NSDictionary *destinations = 
    [NSDictionary dictionaryWithContentsOfFile: filePath];
NSArray *destinationsArray = 
           destinations[@"DestinationData"];
NSDictionary *data = 
       destinationsArray[destinationIndex];

Then it uses the dictionary data to initialize its properties:

  ;
    _destinationImage = 
        [UIImageimageNamed:data[@"DestinationImage"]];

;

Although there are separate images for the iPad and iPhone, they're in an asset catalog and you just use the basic name for the image set containing the separate images.

  _destinationName = 
                   data[@"DestinationName"]; 
NSDictionary* destinationLocation = 
               data[@"DestinationLocation"];
CLLocationCoordinate2D destinationCoordinate;
destinationCoordinate.latitude = 
  [destinationLocation[@"Latitude"] doubleValue];
destinationCoordinate.longitude = 
  [destinationLocation  
                 [@"Longitude"] doubleValue];
_coordinate = destinationCoordinate;
_title = destinationLocation[@"Title"];
_subtitle = 
           destinationLocation[@"Subtitle"];

The initialization of the properties is simply done by using the keys you specified when you created the plist, which turn into dictionary keys when you load the dictionary (and its dictionaries) from the plist file.

Now you can add the Trip methods destinationImage, destinationName, and destinationCoordinate, which use the Destination object. Add the methods in Listing 11-6 to Trip.m.

Listing 11-6: Adding destinationImage, destinationName, and destinationCoordinate

  - (UIImage *) destinationImage {
  
  return  self.destination.destinationImage;
}

- (NSString *) destinationName {
  
  return  self.destination.destinationName;
}

- (CLLocationCoordinate2D) destinationCoordinate {

  return self.destination.coordinate;
}

These Trip methods will be used by the Master View controller to request the data it needs for its view.

Interestingly, in this case, all Trip does is turn around and send the request to the Destination object. This is, of course, an example of loose coupling, which I explain earlier.

In this case, there isn’t that much for Destination to do, so you could've simply had Trip manage the data. But in a more robust app (like one worth 99 cents), it would likely have more to do. In fact, you could start by having Trip manage all the data and add a Destination object when you felt you needed to. And when you did add the Destination object, doing so would have no impact on the objects needing that data — ah, loose coupling in action.

Creating the Trip Object

Finally, you have to create the Trip object. You need to make it accessible to the view controllers that need to use it, so you’ll make it an AppDelegate property. Any object in your app can find the AppDelegate, and from it get a pointer to the Trip object.

Add the bolded code in Listing 11-7 to AppDelegate.h.

Listing 11-7: Updating the AppDelegate Interface

  #import <UIKit/UIKit.h>
@class Trip;

@interface RTAppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (nonatomic, strong) Trip *trip;

- (void) createDestinationModel:(int)destinationIndex;

@end

createDestinationModel: is the method that actually creates the Trip object.

@class is a compiler directive to let the compiler know that Trip is a class (or type). You need to import the header to actually use it in your code, however, and you’ll do that by adding the bolded code in Listing 11-8 to AppDelegate.m.

Listing 11-8: Updating the AppDelegate Implementation

  #import "AppDelegate.h"
#import "Reachability.h"
#import "Trip.h"

@implementation AppDelegate

You also declare a method that will actually create the Trip object. Add the bolded code in Listing 11-9 to application:didFinishLaunchingWith
Options: in AppDelegate.m to use that method.

Listing 11-9: Updating application:didFinishLaunchingWithOptions:

  - (BOOL)application:(UIApplication *)application 
          didFinishLaunchingWithOptions:
                             (NSDictionary *)launchOptions
{
  UINavigationController *navigationController;
  if ([[UIDevice currentDevice] userInterfaceIdiom] == 
                               UIUserInterfaceIdiomPad) {
    UISplitViewController *splitViewController = 
      (UISplitViewController *)
                           self.window.rootViewController;
    splitViewController.presentsWithGesture = NO;
    UINavigationController *detailNavigationController 
         [splitViewController.viewControllers lastObject];
    splitViewController.delegate = 
         (id)detailNavigationController.topViewController;
    navigationController = 
    [splitViewController.viewControllers objectAtIndex:0];
  }
  else { 
    navigationController = (UINavigationController *)
                           self.window.rootViewController;
  }
  NetworkStatus networkStatus = [[Reachability reachabilityForInternetConnection] currentReachabilityStatus];
  if (networkStatus == NotReachable) {
    UIAlertView *alert = [[UIAlertView alloc] 
       initWithTitle:@"Network Unavailable"
       message:@"RoadTrip requires an Internet connection" 
       delegate:nil
       cancelButtonTitle:@"OK" 
       otherButtonTitles:nil];
    [alert show];
  } 
  
  [self createDestinationModel:0];
  return YES;
}

As you can see, you use the createDestinationModel: method to actually create the model. I have this as a separate method because you'll need to be able to send the AppDelegate a message to create a new Trip when the user chooses a new destination in Chapter 20.

Also notice that, for now, you’ll be defaulting to the first destination in the plist. You’ll fix that in Chapter 20 as well.

Add the createDestinationModel: method in Listing 11-10 to AppDelegate.m.

Listing 11-10: Adding createDestinationModel:

  - (void) createDestinationModel:(int)destinationIndex {
  
  self.trip = [[Trip alloc] 
initWithDestinationIndex:destinationIndex];
}

All you do is allocate and initialize the Trip object and assign it to the trip property.

More Debugger Stuff

In this section, I give you something more interesting to look at in the Debugger than you’ve had up to now for this book’s example app. As it stands, you can compile RoadTrip with no errors and it runs, albeit the same way that it did before. So you have no idea if all that code you added really works. Does it really find the Destinations.plist file and create all the dictionaries with their entries correctly?

One way to find out is to put a breakpoint in your code and see what’s there; so go ahead and set a breakpoint in Trip.m on the following line (in the initWithDestinationIndex: method) by clicking in the gutter next to (in front of) the line:

  _destination = [[Destination alloc] 
               initWithDestinationIndex:destinationIndex];    

Then set another breakpoint in Destination.m (in its initWith
DestinationIndex: method) on the following line:

  _subtitle = 
           destinationLocation[@"Subtitle"];

Run your app.

If your program execution stops at a breakpoint, you can move your pointer over an object or variable in the Source editor to see its contents. In Figure 11-14, I’ve done that with destinationData (and so should you).

destinationData is a pointer to the dictionary that contains the data for the first entry in the Destination plist’s DestinationData array. You’ll have six key/value pairs, as you should have, and if you look in the Variables pane in the Debugger, you’ll see two objects in thedestinationsArray, which is also as it should be.

You can explore the display by opening the disclosure triangles to drill down and look at the data as you see in Figure 11-15.

You can use the circled i to reveal the contents of the variable as shown in Figure 11-16.

image

Figure 11-14: Display the contents of a variable in the Source editor.

image

Figure 11-15: Drill down into the data.

image

Figure 11-16: Reveal the contents of a variable.

Finally, you can use the Quick Look icon to explore the variable and its memory location, as you see in Figure 11-17.

image

Figure 11-17: Use Quick Look while the app is stopped at a breakpoint.