Hour 16. Building an iOS Application


What You’ll Learn in This Hour:

How to set up a project to build an application that can run on the iPhone, iPod touch, and iPad

How to add a Cocoa Touch static library target to an existing framework project, to enable its use on iOS

The kinds of things to watch out for when porting code between OS X and iOS

How to implement singleton classes to handle data passing between different controllers in your iOS MVC schema


In this hour, you rebuild the OS X application that you built in the preceding hour into a universal iOS application. Because OS X and iOS are converging, this gets easier with every new release of OS X, iOS, and Xcode, but for a number of reasons it is still not as seamless a process as one might hope, as follows:

UI manipulability: At the most basic, iOS devices require some adjustments to programming simply because of the practical difference in the iOS/Cocoa Touch and OS X/Cocoa user interfaces. For example, if your program responds to users hovering their cursor over an interface element in your OS X application, you must find an alternative for this for your iOS application because iOS does not have a cursor to hover.

Library differences: Annoyingly, differences at the library level must also be addressed, differences beyond those required by differing hardware. These are primarily the result of iOS using different, simplified libraries to replace some functionality that’s available in OS X, requiring that you rewrite some method calls for iOS.

UI element availability: There are also some differences in the suite of standard user interface elements available on iOS. So, if you have used elements that are unique to OS X/Cocoa, you must replace those and change your underlying code to support the iOS elements available.

MVC design constraints: In addition, developing for iOS requires you to adhere to the model-view-controller (MVC) design pattern much more closely than does OS X. Whereas it is not at all uncommon for the application delegate to end up carrying around the majority of the functionality in small OS X applications, in iOS applications the app delegate’s job is solely management of the application life cycle, and functionality is embedded in or below view controllers that are responsible for instantiating each scene or “fullscreen” display.

User expectations: Finally, although not a strict requirement for porting an application to iOS, users of iPhones, iPods, and iPads have high expectations for interactivity in their iOS applications. So, you will probably want to build new features to provide natural navigation and interaction via familiar iOS gestures.

Because good iOS interface design and appropriate use of iOS technologies is a better subject for a standalone book, this hour focuses on the changes necessary to port the functionality of a OS X application to the iOS environment. We leave the decisions about how to wrap additional and appropriate iOS functionality into the application up to your creative genius.

Assessing What You Already Have

When adapting an OS X application to iOS, you must first consider the pieces that you have in your OS X application and determine how they might work best work under iOS. You need to perform this analysis for both your interface and your underlying driving logic.

In the case of your application’s UI, your main window, if you have one, probably needs an entire iOS scene devoted to it. If you have dialogs in your OS X application, they probably need individual dedicated scenes. If your main interface combines both display elements and UI elements, you might need to split those off into separate iOS scenes.

You also need to consider the possible routes that a user can take through your application interface. In OS X, you are probably used to designing free-form user experiences where users can open multiple windows and wander as they choose between them. With the exception of a few special cases where users need to be taken linearly through a complete series of steps to complete some task, it is generally considered bad practice to put users on rails and only allow one path through your OS X interface. In iOS, however, your users are limited to a single scene at a time, and can only switch between that scene and the specific other scenes that you enable the interface to guide them to. In iOS, it is just as bad form to give them too many options as it is to guide them too carefully in OS X.

You also need to carefully study your application logic. You will likely have logic and data that you must split out of the application delegate and move into either an appropriate controller or model component. You also might need to break up some of your view logic. If you have previously instantiated what are really two independent views in the same OS X window by conflating their code under the same controller, you might be sharing data between them through that shared parent controller in a fashion that cannot be easily replicated when you split the views apart in iOS.

Looking at BeeLine from Hour 15, “Putting It All Together: Building an OS X Application,” we have a window that contains a graphics subview and an NSBrowser subview that are displaying the same data and user-entry text fields and a button. For purely screen-space reasons, an iOS version, especially an iPhone version, will probably need to have the graphics subview and NSBrowser subview split onto separate scenes.

Because we were working in the direction of connecting the NSBrowser functionality with the user-input functionality (to support changing the insertion point in our list of points and deleting points from the list), keeping the coordinate-entry text fields together with the NSBrowser component makes sense. We also previously used the app delegate as the controller for the graphics subview, and because we did this, we could get away with passing data between the graphics subview and the NSBrowser using the app delegate as an intermediate.

For iOS, both of these things must change. The controller for the graphics view must be pulled out as a separate class. The pointsList data, which previously was owned by the graphics subview and was communicated to the NSBrowser by the app delegate, must be managed by a separate class and some additional magic invented to communicate it to both controllers that need to use the data. (We accomplish this using a design pattern known as a singleton, discussed in more detail later in this hour.)

Finally, looking carefully at the interface elements available in iOS, we can see that the NSBrowser class that we used for listing points in the OS X version is not available in iOS. So, we need to replace that component. Although not hierarchical, the iOS-compatible TableView class is a reasonable substitute. If we had extended BeeLine to support multiple lists of points using the NSBrowser to select among the list hierarchy, we would need to invent some functionality here because iOS does not provide a similar hierarchy browser class.

In addition, we have been using our BetterList framework to provide the underlying data model, and Apple currently reserves dynamically linked frameworks for themselves in iOS, requiring that application authors use statically linked libraries instead. So, we need to create a static version of BetterList to support our iOS application.

All together, for our iOS conversion, we need to do the following:

1. Create an iOS application with two scenes.

2. Update our BetterList framework to create a static library for iOS and incorporate it into our iOS app.

3. Update our application logic to support iOS-specific library calls.

4. Create a singleton class to hold our point list data and pass it between the scenes.

5. Replace the NSBrowser support logic with TableView support logic.

6. Populate the primary scene with our graphics view.

7. Populate the secondary scene with a TableView (to replace our NSBrowser view), and with the X and Y coordinate input boxes.

8. Connect the UI elements in the primary and secondary scenes with the application logic.

9. Add UI tweaks to support iOS features such as rotation.

Throughout the rest of this hour, we walk through the steps necessary to complete each of these items. However, because you are now getting more experience with the specific actions required to perform these steps, you will find in this hour just general instructions for activities with which you are already familiar. If you need to refresh your memory on specific tasks, refer back to Hour 14, where each step is explained in greater detail.


By the Way

Hour 4, “Using Xcode Templates to Create Projects,” covered the various iOS design patterns (types) that Xcode supports. Remember, though, that you are not locked into just one design. Your application can incorporate aspects of different types as necessary or desired. For example, it is entirely possible to have a tabbed application with one tab presenting a master-detail view of some data, enabling the user to select from a list of items and peruse high-level details, and another tab presenting a page-based opportunity to browse through the contents of the selected item in depth. If you need to review the various design types available, see Hour 4.


Building from the Template

To re-implement BeeLine, we need a main view for the graph, and we need a secondary view where the user can add data and interact with the list of points. We build this using a Utility Application template. To get started, follow these steps:

1. Create a new project in Xcode. Under iOS, select Application and choose the Utility Application template, as shown in Figure 16.1.

Image

Figure 16.1. Selecting the Utility Application template from the iOS choices.

2. Fill in the details for your project as shown in Figure 16.2. So that we can use the same application on both iPhone and iPad devices, make sure that the Device Family setting is Universal. Select Use Storyboard and Automatic Reference Counting. We won’t be using Core Data with iBLine, so leave that option un-checked (although if you think you might need to add Core Data support at a later date, selecting this and working around the extra code it adds until you need it is much easier than gluing in the bits you need in the future). As a matter of principle, check the Include Unit Tests check box.

Image

Figure 16.2. Choosing options for the project.

3. Click Next, choose a place to store your project, and click Finish.

After working for a few moments, Xcode presents you with your bare Utility Application template. As shown in Figure 16.3, you are already provided with header and implementation files for the application delegate and with view controllers for both the main view and the “flipside” view. In the Editor section of the interface, you can configure numerous options for device features you want to support, such as what rotations your application needs to know about and standard things like application icons.

Image

Figure 16.3. Xcode provides header and implementation files for the application delegate, as well as for the main and flipside view controllers.

Adding a Static Library Target

Now that you have your basic application project created from the template, it’s time to add a Cocoa Touch-compatible static library to the BetterList framework project we’ve been using. To do this, follow these steps:


Did You Know?

A really careful programmer would have realized that BetterList needed some tweaks and worked on them before even bothering to start on the iBLine project. If you’re like me, though, you will inevitably end up in the situation we have set up here, where you have a new project started and another that you need to incorporate that requires additional editing. I have left the major steps in this order to show you the few extra things you need to do to succeed if you find yourself in this situation.


1. Close your iBLine project and open your BetterList project.


Only Open Projects Once

Closing iBLine isn’t strictly necessary, but Xcode currently gets confused sometimes when you have a project open directly and it is also included as a sub-project in another open project. To avoid situations where Xcode cannot decide which open project is really in control of a file, it is safer to have one open project and to make sure any others that have potentially overlapping content are closed.


2. Select the BetterList project in the Navigator and again in the Editor area. Click the Add Target button (+) at the bottom of the Editor area, as shown in Figure 16.4.

Image

Figure 16.4. Adding a target.

3. In the dialog that appears, select the Framework & Library option in the iOS section of the project templates. The Cocoa Touch Static Library template is currently the only option available. Select it, and then click Next.

4. Choose a name for your static library target. It would be nice if you could just re-use BetterList here so that your BetterList project could create both a BetterList.framework framework distribution and a libBetterList.a static library, but Xcode does not let you use an identical base name, even thought it is going to add prefixes and suffixes that would make them distinct for the actual targets. Because Xcode prepends lib to the name provided here, I’m not adding that myself. Instead, as shown in Figure 16.6, I call the new target BetterListlib.

Image

Figure 16.5. Selecting the Cocoa Touch Static Library template.

Image

Figure 16.6. Choosing options for the target.

5. Uncheck the Include Unit Tests check box. Unless something new and different is going to happen that is unique to your new library, the unit tests of logic already instantiated in the framework should suffice to make sure that the BetterList functionality remains stable.

6. Make sure that the project to which you want to add the target is selected, and then click Finish.

Xcode populates your project with a new target and with new header and implementation files to implement your target. Because the new target in this case is just a differently stored version of our existing functionality, we do not need any new implementation files. We just need to convince Xcode to build a static library target with our existing one.

7. Open the new group that Xcode has created for BetterListlib in the Navigator, and select the new implementation and header files. Right-click and select Delete, as shown in Figure 16.7. When prompted, tell Xcode to go ahead and delete the files from disk.

Image

Figure 16.7. Deleting the new implementation and header files for BetterListlib.

8. Select your original BetterList.m implementation file in the Navigator and reveal the Utilities area. Under the Target Membership heading, you’ll find the BetterList framework (toolbox icon) check box already selected. Check the check box beside your new target BetterListlib (Acropolis-looking classical architecture icon), too, as shown in Figure 16.8.

Image

Figure 16.8. Adding BetterList.m as a target member for BetterListlib.

9. Select your original BetterList.h header file from the Navigator, and likewise, in the Utilities, select it as a target member for BetterListlib. After doing so, change its membership setting from (the default) Project header to Public, as shown in Figure 16.9.

Image

Figure 16.9. Adding BetterList.h as a target member for BetterListlib and setting its membership to Public.

10. From the Product menu, choose Edit Scheme and, from the pop-up at the top of the dialog, switch to editing the BetterListlib scheme. Under the Build options, uncheck the Test build phase, as shown in Figure 16.10.

Image

Figure 16.10. Deselecting the Test build phase for BetterListlib.

11. Display the Run options for the BetterListlib scheme, and under the info tab, switch the build configuration to Release, as shown in Figure 16.11, and then close the Scheme Editor.

Image

Figure 16.11. Setting the build configuration for the BetterListlib scheme to Release.

12. (Optional) From the file menu, choose Project Settings, and then click the Advanced button to set the derived data location to Locations Specified by Targets.


By the Way

Everything can be completed with the default directories selected for the derived data location, but in the interim as Apple works out some of the kinks with projects, automatically finding the right paths for dependency data in subprojects, it is often, especially in small projects, much easier to be explicit with these settings rather than to rely on the sometimes-flakey automatic settings.


13. Back at the main Xcode window, from the Scheme pop-up menu on the toolbar select BetterListlib, iOS Device, as shown in Figure 16.12.

Image

Figure 16.12. Setting BetterListlib to test iOS device architecture.

14. From the Product menu, choose Build For, Build for Running.

You should be rewarded with a Build Succeeded notification, and if you check your BetterList directory in the Finder, you should find that its build subdirectory has now been populated with a Release-iphoneos subdirectory. In that subdirectory, you should find your static library (libBetterListlib.a) and a distribution hierarchy for the header, /usr/local/include/BetterList.h, as shown in Figure 16.13. If you do not see these things, check your steps and try again.

Image

Figure 16.13. The static library and header file should appear in a Release-iphoneos sub directory of your BetterList directory in the Finder.


By the Way

If things are not working out quite as expected at this point, the information available from the Log Navigator (little speech-bubble icon) under the Navigator can be invaluable. The most probable source of difficulty, if you’re getting a Build Succeeded notification and aren’t seeing any errors, is Xcode putting the file somewhere other than where you wanted it. If you look at the detailed log entries for the last steps of your build process, you can usually identify the trouble and either correct it or, as is often easier, just move the files somewhere more useful by hand.


After you have succeeded in getting the BetterList project to build a libBetterListlib.a static library, it is time to incorporate it into iBLine. To do so, follow these steps:

1. Close the BetterList project. If you don’t, you’ll drive yourself nuts when it adds itself to iBLine but you can’t interact with its components.

2. From the Finder (or from the File menu in Xcode), open the iBLine project.

3. Select the iBLine project in the Navigator and right-click it. Choose Add Files to iBLine from the pop-up menu that appears, as shown in Figure 16.14.

Image

Figure 16.14. Getting ready to add files to the iBLine project

4. In the file selection dialog that opens, navigate to wherever your BetterList project is located. Just in case you’re a bit disorganized and work in multiple locations, you might want to expand the folders under it to make sure that you’re really looking at the directory that contains the static library you just built. Select the BetterList.xcodeproj file and make sure it is going to be added to the iBLine target, as shown in Figure 16.15. Click the Add button.

Image

Figure 16.15. Selecting the BetterList.xcode proj file to add to the iBLine target.

5. The BetterList.xcodeproj project should now show up as a subproject of the iBLine project in the Navigator. Select the iBLine project in the Navigator and select the iBLine target in the Editor area. Open the tab for the iBLine build phases. Using either the + buttons at the bottom of each build section listing, or dragging the files from the Navigator area, add BetterListlib as a target dependency, and libBetterListlib.a to the list of libraries for linking, as shown in Figure 16.16.

Image

Figure 16.16. Updating the BetterList.xcode proj project by adding BetterListlib as a target dependency and libBetterListlib.a to the list of libraries to be linked.


By the Way

Remember that the BetterList project is now a child of both your original BeeLine OS X application and your iBLine iOS application. This is convenient from the point of view of making sure that when you add functionality to BetterList in either it’s available to both. However, it can cause problems if you do something to BetterList in one that breaks functionality in the other, because you won’t find out about this until you have the other project open in Xcode, and if that’s six months in the future, you are likely to have forgotten exactly what you changed.

It is also difficult to work on both BeeLine and iBLine at the same time with this configuration because only one of them can actually have the BetterList project open at a time. Whichever of the BeeLine and iBLine projects is opened second will complain about “project integrity” because it cannot open BetterList itself. Because of this, if you were to complete all your BetterList functionality and wouldn’t be touching it again, it would be better to just include the finished static library and header rather than the project. Alternatively, you could configure both BeeLine and iBLine to be peers within the same workspace (which you learn more about this in Hour 20, “Keeping things Organized: Shared Workspaces”), so that you could fiddle with BetterList from either of them without confusing Xcode when you open the other project.


To wrap things up so that the code in your iBLine project can find the headers defining the functionality available in BetterList, you need to complete one more step. (This will probably change as Xcode matures and the workspace functionality improves.)

6. Display the Build Settings tab for the iBLine target and choose to show all the settings. Look for the Search Paths section. You can use the search function to search for “header” or “path” to make things easier. Find the line for Header Search Paths. If you’re not going to install your BetterList.h header in its intended final /usr/local/include location, you must configure the header search path so that the compiler can find it. The correct setting for the header search paths depends on where you put your BetterList directory.

In my case, I have a Framework_Stuff directory parallel to my applications directory SGFApps. My BeeLine and iBLine project directories are in the SGFApps directory, and the BetterList directory is in the Framework_Stuff directory. Therefore, I added a line to header search paths that contains ${SRCROOT}/../../Framework_Stuff/BetterList/build/Release-iphoneos. That is to say, include headers from a path starting in the current source directory, go up two levels (to the SGFApps directory, and then to the one above it), then back down into the Framework_Stuff directory, the BetterList directory under it, and the build directory and Release-iphoneos directories that we previously identified as where the BetterList project was storing the finished libBetterListlib.a static library. Your configuration should look similar to that shown in Figure 16.17.

Image

Figure 16.17. Adding a header search path so that the iBLine project can find the headers in BetterLists.

If you select your iBLine scheme and iPhone simulator from the Xcode toolbar and run your iBLine project now, you should be greeted by not only a Build Succeeded notification but also by an onscreen iPhone running your app. The front side displays a blank view with an Info button icon (little italic i). You can flip this to the alternative view, which has a title bar and a Done button, which flips back to the main view, as shown in Figures 16.18 and 16.19. If you have gotten this far, you’re all set to start building your logic and connecting it to the interface.

Image

Figure 16.18. The front view with the Info button in the iPhone simulator.

Image

Figure 16.19. The alternative view with a title bar and a Done button in the iPhone simulator.

Updating Application Logic and Library Calls for iOS

There are surprisingly few modifications that must be performed in the application logic in BeeLine to make it work under iOS. However, OS X and iOS are still too distant for this to be a simple single-click process. As a result, you must dig through your application logic by hand and find places where library routines differ or where differences in iOS require a modification of approach. The specific issues that you need to deal with for any given application are sufficiently diverse that you really need a book on Cocoa Touch and additional references for Objective-C to complete this process for a general application.

For the purposes of converting BeeLine into iBLine, we just hit some of the highlights that are typical of the issues you’ll encounter with other applications. The full source for iBLine, appropriately converted for iOS, is available in the Hour 16 folder of the book source files, available from http://teachyourselfxcode.com/.

BeeLine’s QuartzGrough class translates rather directly into a graph-drawing class for iOS. In iBLine, I call the analogous class iGrough. Some of the specific differences are as follows:

• Because we need to break out the Model component more cleanly for our data, iGrough is not going to own the BetterList instance, as was done in QuartzGrough.

• QuartzGrough was an NSView and used NSPoints and [self size:[self bounds]] to identify the current drawing region. It also used NSColors and filled a filled NSBezierPath to draw the graph background. iGrough is a UIView and doesn’t know about NSPoints, NSColors, or filled NSBezierPaths. It also doesn’t respond to a bounds message. Instead, iGrough needs to use CGPoints, a CGColorSpace, and a CGContextFillRect.


By the Way

Note that the Core Graphics CG* routines and variables are C language functions and structs rather than Objective-C methods and objects. Keeping this in mind often helps when trying to figure out what the appropriate translation is between some of the analogous-but-not-identical OS X and iOS functionality.


• The UIView context vertical axis is reversed compared to the NSView context vertical axis. In the UIView, the 0,0 origin is at the upper left rather than the NSView location of the lower left. As a result, we need to invert the Y coordinate of our plot to cause it to draw right-side up.

• In BeeLine, the application delegate set the QuartzGrough anX and anY instance variables and then invoked the QuartzGrough instance method plotUpdate directly in response to the user clicking the Add button. This will not work for iBLine for two reasons: First, because the Add button will be in a different scene (the flipped view), invoking the iGrough plotting routine when Add is clicked doesn’t make any sense; we want the graphics to be plotted when the view flips back to the main view, not when Add is clicked. Second, because the Add button belongs to the FlipsideViewController, and the FlipsideViewController is a peer of the MainViewController, rather than its parent, we do not have convenient access to the iGrough instance variables from any method that the Add button can invoke. Part of this problem we alleviate by changing the QuartzGrough plotUpdate method into a sendPoints method in iGrough and passing in the whole list of points to be plotted. The other part of the problem we overcome by implementing a singleton class to facilitate communication between the FlipsideViewController and the MainViewController.


By the Way

See the provided code in the Hour 16 folder for this book, for specific code changes to address these and other issues in converting QuartzGrough into iGrough.


Creating a Singleton to Hold Data and Share It Between Scenes

Singleton classes are an extremely powerful design pattern that seem to generate an unwarranted amount of confusion, especially given that they are actually exceedingly straightforward in implementation. A singleton class is simply a class that only allows an individual instance of itself to exist at any time. You can use such a class to provide access to single-copy hardware devices, such as the accelerometer in the iOS devices (and Apple uses the singleton design pattern for similar purposes extensively), but it can also be used to provide a way to communicate shared data and resources between other (potentially peer) classes.

No matter which class instantiates the singleton first, a single instance of it comes into being, and every other class that attempts to instantiate it just gets back a reference to the same already-instantiated copy. As a result, any data that that one copy contains is available to all the classes that have (attempted to) instantiate the singleton, and with appropriate setter and getter methods, each class that needs to communicate via the singleton can store data into it and retrieve data out of it.

To pass our BetterList around so that it can be stored into by methods in the FlipsideViewController, and accessed by methods in the MainViewController and the iGrough view, we create a singleton to carry around a BetterList, and each class that needs to access the BetterList can do so by addressing it through the singleton. Our singleton class, named DataPhile, has a header file (DataPhile.h) that contains the code shown in Listing 16.1.

Listing 16.1. Header File DataPhile.h for the DataPhile Singleton Class


#import "BetterList.h"

typedef struct myPointType myPointType;
struct myPointType {
        float myX;
        float myY;
};

@interface DataPhile : NSObject

+ (id)getSharedDataPhile;
+ (BetterList*)setupPointsList: (void*) theThing;

- (NSMutableArray*) getDataPhileArray;
- (BetterList*)getPointsList;

@end


DataPhile also has an implementation file (DataPhile.m) that contains the code in Listing 16.2.

Listing 16.2. Implementation File for DataPhile.m for the DataPhile Singleton Class


#import "DataPhile.h"

@implementation DataPhile

static id dataPhile = nil;
static NSMutableArray *points;
static BetterList *pointsList = nil;

+ (void)initialize
{
    if(self == [DataPhile class])
    {
        dataPhile = [[self alloc] init];
        points = [NSMutableArray arrayWithCapacity:1];
    }
}

+ (id)getSharedDataPhile
{
    return dataPhile;
}

+ (BetterList*)setupPointsList: (void*) theThing
{
    pointsList = [[BetterList alloc] initDLList: theThing];
    return pointsList;
}
- (NSMutableArray*)getDataPhileArray
{
    return points;
}
- (BetterList*)getPointsList
{
    return pointsList;
}

@end


You might at first find the logic behind the class methods confusing, but it will quickly become second nature after you discover how useful this pattern is for coordinating between your views without needing to pass lengthy lists of always-the-same parameters around explicitly. The first thing to note is the class method initialize. The initialize class method is invoked once, and only once, during the first attempt to invoke an instance of the class. Inside initialize, we set the class variable dataPhile to the return of our instance allocation and init procedure, and the class variable points to an NSMutableArray initialized to be large enough to contain a single item.


Did You Know?

The astute reader will wonder why I’m talking about setting “class variables” when Objective-C ostensibly does not provide class variables. While technically true that Objective-C lacks a specific class variable idiom, that doesn’t mean that it is impossible to use the existing functionality to implement something that works like a class variable. The static global variables dataPhile, points, and pointsList can only be set through class methods in DataPhile, and their contents are available (identically) to any instance (although we can only have one instance here). Because it looks like a duck, walks like a duck, and quacks like a duck, I’m calling it a class variable as shorthand.



Did You Know?

The assignments in initialize are wrapped in a conditional that checks whether self is actually a literal DataPhile class. This is boilerplate. The initialize method is invoked once, regardless of whether it is the class or a derived class that is instanced first. If for some reason I want to do something different if a derived class is instanced first, I can catch that with this conditional. Because I have no derived classes for DataPhile, the conditional is completely redundant, and I’ve left it in the code only as a reminder in case I need it for something later.


The next features worthy of mention are the class methods getSharedDataPhile and setupPointsList:

getSharedDataPhile does nothing more than return the value of the dataPhile “class” variable. Because this is a static global value settable only by the initialize method, any class that messages DataPhile’s getSharedDataPhile method is going to get exactly the same response: a reference to the single instance of DataPhile that was created when the initialize method was called for the class.

• The setupPointsList class method is used to fill in the pointList “class” variable. It doesn’t strictly need to be a class method because the pointsList is global and there is only going to be one instance of the DataPhile class, but in the service of self-documenting code, there’s an elegance to having methods that affect class variables be class methods.

Finally, we have a pair of instance methods, getDataPhileArray, and getPointsList, that retrieve the “class” variables points and pointsList. These do not strictly need to be instance methods, and there’s at least one coherent argument to be made for making them class methods instead, that being that they interact with class variables and therefore are more transparent as class methods. However, a counterargument can be made that code looks cleaner if the routines that use your singleton retrieve an instance of your singleton class (always the same instance) and then retrieve values from the instance instead of always talking to the class itself. Maintaining getSharedDataPhile as a class method remains sensible here because it makes it explicit in the application code that the DataPhile instance is singular and belongs to the class rather than a unique instance created by the application code that is using it.

Finally, note that we’ve moved the definition of myPointType into the DataPhile.h header. This is purely for convenience. Every class that needs to access the points from our BetterList is going to need to include the DataPhile.h header, so moving that definition here, instead of keeping it with iGrough, keeps things neater.

All together, it takes much more effort to describe the singleton pattern than to use it. This might be its one negative aspect. After singletons stop looking peculiar and you become fluent with them, you’ll likely start seeing many situations where they could make your life easier. If you’re like most programmers, you’ll probably find more places where they look like they might fit than are actually good places to use them. An application that is using singletons to store all of its data is probably doing something wrong or is likely to have areas where its functionality could be significantly expanded if it weren’t limited to single-instance copies of data. Beware of painting yourself into a corner by overusing singletons, but don’t be afraid to use them judiciously if passing values between the components of your application would just add redundancy to your code.

Swapping iOS Components for OS X Components

This bit is a little tricky. Both the NSBrowser and the TableView are very powerful classes with many options, and neither the display nor the options translate directly between them. With the NSBrowser for BeeLine, we could subclass the NSBrowser class, set the interface element to be a member of our subclass, and add methods to populate it to that subclass. This is not how UITableViews are used. UITableViews are simply UITableViews, and they self-populate themselves with data by being linked, rather than subclassed into, a class that contains specifically named methods, and they achieve added functionality by specification of a delegate.

To attach a class to a UITableView as its data source, the class needs to implement specific instance methods. A minimalist set includes a numberOfSelectionsInTable View method and tableView:numberOfRowsInSection and tableView:cellFor RowAtIndexPath methods. The UITableView also needs a delegate containing at a minimum a tableView:didSelectRowAtIndexPath method in which to implement some action when a tableView row is selected. The Hour 16 folder of this book’s source files contains the minimal code necessary to support a TableView embedded in the FlipsideViewController. In a more complex application, where a UITableView could be afforded a scene of its own, it is better to create an independent class to serve as the UITableView data source and delegate. With the two-scene Utility Application template, however, using the FlipsideViewController is easier.

Populating the Primary Scene

Now that our code is in place, we can get on with building the UI and connecting all the pieces. Although you do not have to write all the code before adding the UI components, you might find doing so easier because if you do build the UI first, you’ll end up repeatedly going back and forth between the Interface Builder and the code. When you add interface elements, they often need code to support them. If the code is not already present, you must write it and then return to the Interface Builder and try to remember what you want to connect it to. By putting the code in place first, you can then place interface elements and immediately connect them with their appropriate classes without having to constantly flip back and forth.

Because we’re replicating the BeeLine project, the first thing we need to add to the main scene is a view area where we can plot our data. Doing this requires much the same sequence of actions with which you’re becoming familiar in Interface Builder:

1. Select the MainStoryboard_iPhone.storyboard in the navigator and maneuver the storyboard editor area until you can see the entire main view controller.

2. Open the Utilities area and show the Object Library.

3. Find the View item (a UIView) and drag it onto the Main View Controller scene in the storyboard, as shown in Figure 16.20.

Image

Figure 16.20. Dragging the View item into in the Main View Controller scene in the storyboard.

4. Resize the View item you have placed to suit your preferences, and then show the Identity Inspector in the Utilities area and change the class for the View item from the original UIView class to our UIView-derived iGrough class, as shown in Figure 16.21.

Image

Figure 16.21. Changing the class for the View to our iGrough class.

Populating the Secondary Scene

Populating the secondary scene with the rest of the UI elements is no more difficult:

1. Move the storyboard view until the FlipsideViewController scene is fully visible in the Editor area.

2. Drag in a pair of text fields for the X and Y coordinate entries and a pair of labels.

We did not need to change the default color of the labels in BeeLine because the default OS X application background is light gray and the default label color is black. Unfortunately, the default iOS background is dark gray, while the default label color remains black (note to Apple: that’s not insanely great), so you must change it.

3. Select both of your labels in Interface Builder by clicking on the first one and Option-clicking the second. Then show the Attributes Inspector in the Utilities. Near the bottom of the Label fields, you’ll see a Text Color pop-up. Click it. Don’t click and hold. A standard color picker will appear. Select the color you want for your text, as shown in Figure 16.22. Notice that your labels change color as you adjust the setting in the color picker, although they remain tinted with your highlight color until you click elsewhere in Interface Builder to deselect the labels.

Image

Figure 16.22. Selecting color for the text.

4. Complete the interface by adding a Round Rect Button for the Add function and a TableView to display the list of points, as shown in Figure 16.23.

Image

Figure 16.23. Finishing the interface with an Add button and a TableView to display the list of points.


Did You Know?

We are just filling out the iPhone interface components here. Setting up the iPad interface follows an identical procedure, the only difference being that you need to select the MainStoryBoard_iPad.storyboard to begin working on it.


Finally, it is time to connect the code functionality and the interface elements to complete our iOS application.

You will find most of this familiar from the procedure we followed to build the OS X application. As usual, though, Xcode provides more than one way to approach the problem, so we use a slight variation from the previous method. To connect the user interface components, follow these steps:

1. Select the MainStoryboard_iPhone.storyboard in the Navigator and open the Scene List sidebar. (If it’s not open, the right-pointing triangle at the bottom of the Interface Builder opens it.)

2. In the Main View Controller Scene group in the sidebar, reveal the contents of the main view controller, and then reveal the contents of the view contained in it.

3. Click the Assistant Editor button on the toolbar. If your Assistant Editor mode is set to Automatic, the MainViewController header file should appear in the Assistant Editor. If it does not, navigate to that file using the jump bar.

4. Control-click the iGrough in the sidebar (which peculiarly is only named Grough in the sidebar list, despite being properly an iGrough) and drag a connection from it over to the MainViewController header, as shown in Figure 16.24. Drop it below the last @property declaration and above the @end.

Image

Figure 16.24. Connecting the iGrough in the sidebar list to between the last @property and to the @end in the MainView Controller.h.

5. Create an outlet connection and name it. As shown in Figure 16.25, I’m using aGroughGraph, carried over from the BeeLine implementation.

Image

Figure 16.25. Creating an outlet connection and naming it.

6. Switch to FlipsideViewController. Select it in its sidebar group and expand it and also expand the view within it. If you need to manually point the Assistant Editor to the FlipsideViewController header, do so now.

7. Create outlets for both text fields by Control-clicking and dragging from each into the FlipsideViewController header. Name them xCoord and yCoord as previously done in BeeLine. Also create an action for the button. As you’re proceeding, things should look like Figure 16.26.

Image

Figure 16.26. Creating outlets for the text fields and an action for the button.

8. When it comes time to configure the action for the button, you’ll find that you have an additional configuration parameter for the event that’s not available for OS X applications. iOS, of course, lacks a cursor. As a result, traditional OS X events like MouseDown and MouseUp cannot be completely replicated, and probably shouldn’t mean the same things in iOS even if they could. Instead, they’re replaced by “touch” events. The most intuitively appropriate of the available options, for most button operations, is the Touch Up Inside option, so select this, as shown in Figure 16.27, for your button action.

Image

Figure 16.27. Configuring the action for the button to be a Touch Up Inside event.

9. Control-drag an outlet for the UITableView into the FlipsideViewController header. Configure it as shown in Figure 16.28.

Image

Figure 16.28. Configuring a UITableView outlet into the FlipsideViewController.

10. Now hide the Assistant Editor and show just the main Interface Builder editor again. Position the storyboard so that you can see the entire FlipsideViewController.

11. Control-click the UITableView in the Interface Builder. A Heads Up Display (HUD) dialog appears with some available fields that you probably haven’t seen before. Control-click the open outlet for the dataSource and drag and drop on the FlipsideViewController proxy in the dock beneath the scene, as shown in Figure 16.29. This tells Interface Builder and Xcode to use the canonically named methods found in the FlipsideViewController to provide data for the table.

Image

Figure 16.29. Setting the data provided to the table to use canonically named methods found in the FlipsideViewController.

12. Repeat this for the open outlet for the UITableView delegate, as shown in Figure 16.30. This tells Interface Builder and Xcode to set up the appropriate message delegation so that the table functionality can be enhanced by adding delegate methods to the FlipsideViewController.

Image

Figure 16.30. Adding delegate methods to the FlipsideViewController to enhance table functionality.

13. Finally, within Table View in the Interface Builder sidebar, select the Table View Cell or click the white bar just beneath the Prototype Cells header in the Table View, open the Attributes Inspector in the Utilities area, and change the identifier to freeCell, as shown in Figure 16.31. This connects the cells in the placed tableView with the cell allocation performed in the FlipsideViewController.

Image

Figure 16.31. Changing the Table View Cell identifier to freeCell.


Did You Know?

If you have set up an iPad version of the interface, you must repeat these connections for the iPad version.


The FlipsideViewController now needs a bit of extra code added to it to complete the plotPoint method for the Add button and to connect it to the singleton that carries our BetterList instance. The MainViewController also needs these connections added. The code for both is in the Hour 16 folder of the source code.

And now you should have an application that works on the iPhone with essentially identical functionality to the BeeLine application you constructed for OS X. If you click the Run button on the toolbar, you should be greeted (after a bit of compiling) by a virtual iPhone running your new application. Considerably more gratifying than before, your main view should now contain a white graphics view, as shown in Figure 16.32. If you click on the Info button (the italic i) to flip to the alternate view, you will see your X and Y coordinate entry boxes and your empty table view. Fill in a few values (between -1.0 and 1.0) and it should look like Figure 16.33. Tap the Done button to flip back to the main view, and the application will flip back to the main view and plot a graph of the points you have entered, as shown in Figure 16.34.

Image

Figure 16.32. The main view now has white graphics (left).

Image

Figure 16.33. The alternate view has the X and Y coordinate entry boxes and your table view with the entries (middle).

Image

Figure 16.34. After tapping Done, we return to the main view, which has the graph of the points that were entered in the alternate view (right).

Adding iOS Specialty Features

Although you’re technically done with the job of literally converting the application functionality from BeeLine to iBLine, you could still do plenty of things to make the user experience better on iOS (for example, supporting rotation). To get you started, the version of iBLine that’s in the Hour 16 folder of this book’s source files includes the logic and the modification of the notifications received by the MainViewController that are necessary to receive rotation events and to redisplay the graph interface as appropriate for rotated displays.

Summary

In this hour, you converted an OS X application into an iOS application, including some tweaks to the functionality to support iOS and Cocoa Touch-specific features. The steps you followed here are typical of what you must do any time you face this task.

OS X applications tend to use dynamic frameworks, so converting these to iOS static libraries is always necessary. Most of your code will translate, with the occasional minor annoyance of discovering, for example, that the graphics coordinate systems are inverted between the two platforms. You’ll probably have a few UI features in your OS X application that don’t have exact analogs in what iOS provides. Still, reusing the pieces from an OS X application on iOS should not be difficult, and if you have done a good job of separating the model, view, and controller aspects of your application, the specific features that you need to address should be well compartmentalized, enabling you to address each of them as discrete programming tasks.

As you become more familiar with both OS X and iOS fundamentals, you’ll find that these conversions become easier, both because of your increased skill and because you’ll find yourself favoring easily converted interface idioms when doing initial development on either platform. With Apple’s clear intent to unify the OS across desktop and palmtop platforms, getting into this habit is probably a great way to prepare for Apple’s next big thing.

Q&A

Q. Is there a better way to handle the Header Path setting so that the iBLine code can find the BetterLists header?

A. I sure hope so. We cover one improved alternative in Hour 20, but there should be other solutions that are better than setting an explicit path, as well. Unfortunately, none of them seem completely stable at the moment. For example, if go back and look at the Search Paths section of the Build settings, you’ll see an option to Always Search User Paths. Setting this to Yes produces the tempting behavior that the code editor thinks that the BetterList header can be found even without an explicit header search path. Unfortunately, although the code editor can find it, the compiler still can’t. Let’s hope this bug is resolved soon.

Q. What other iOS-specific features would be good to implement for iBLine?

A. Wow, lots of them. How about pinch/zoom on the iGrough? Shake to erase the entire plotted graph? Tie the point collection into either the accelerometer or the GPS so that users can play with drawing by waving the phone in the air or by walking around with it? The more iOS bells and whistles you can usefully add to your mobile application, the better it will fit with the other applications on the user’s device. This makes the user experience more seamless and makes users happy. Don’t go down the road of adding iOS fluff just to have more bells and whistles, though. Just as surely as users like appropriately applied iOS features, they react with visceral disgust when you misuse one. Please, please, please, don’t set up push notifications to send all your friends updated coordinate lists every time you add a point. They’ll hate you for that.

Q. Will this really run on an iPhone as well as in the simulator?

A. You bet. We cover provisioning actual iOS hardware devices and getting your application into the App Store in Hours 22 and 23 (“Managing and Provisioning iOS Devices” and “Distributing Your Applications,” respectively).

Q. I think I did everything right, but whenever I try to run my project, I get an error about something being undefined. I vaguely remember creating an outlet with that name a while ago, but I deleted those bits. What’s up?

A. The Storyboard and Interface Builder features are great for what they do well, but really annoying for what they do poorly. When you drag connections into pieces of code, you’re not just adding @property and @synthesize lines to your header and implementation files. You’re also telling Interface Builder to add some other things behind the scenes (If you enter the text-editing mode, rather than the graphical-editing mode for a NIB or storyboard, you’ll see what I mean). Unfortunately, when you delete the @property and @synthesize lines from your header and implementation files, this does not tell the Interface Builder or the Storyboard feature to delete those bits of internal magic. The error you’re seeing is because some interface element still thinks it has an outlet or action connected. Bring up the Interface Builder or Storyboard feature and right-click each interface element to bring up its HUD of connections. You’ll eventually find one that references the no-longer-existing @property that Xcode is complaining about. Click on the little X button beside that item, and it should disappear from the HUD. Now you should be good to go.

Workshop

Quiz

1. What does the storyboard editor use instead of the Interface Builder dock for holding proxies?

2. How many different ways are there to add an outlet connection for an interface element?

3. What code belongs in the application delegate?

Answers

1. It uses a little bar beneath each scene that holds the name of the scene (derived from the controller for that view) when the scene is not the active selected one and holds the proxy object/file representations for the scene when it is active.

2. At least four. You can Control-drag from the item in the Interface Builder or storyboard itself. You can Control-drag from the item in the expanded component list beside the storyboard or Interface Builder editor. You can right-click the item to show its Heads Up Display and drag from an existing outlet to a target or from the New Referencing Outlet item to a target. Finally, you can use the Connections Inspector in the Utilities to access the same list of outlets as you get from right-clicking the interface element. There are probably more.

3. Only what is necessary to manage the application life cycle. If it doesn’t have to do with setting up or tearing down the application, it doesn’t belong there.

Activities

1. Build the iPad interface for iBLine.

2. Implement pinch and zoom on the iGrough so that it changes the plotted coordinate range to something larger or smaller than the [-1,+1] range that it currently supports.

3. Split the dataSource and delegate for the UITableView out so that they aren’t embedded in the FlipsideViewController any more. Attach them to the DataPhile and remove the dependence on BetterLists from the iGrough.

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

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