Today is Day 4 of the iPhone bootcamp at Big Nerd Ranch, taught by Joe Conway. Unfortunately that means we are closer to the end than to the beginning.
See here for a list of each day's blog entries.
Since we had not completely finished up the Core Graphics lab from Day 3, we spent the first hour completing the lab exercise, which was to build a "Tile Game" application where you take an image, split it into tiles, and the user can move the tiles around and try to arrange them in the correct way to produce the full image. Actually you don't really split the image; you use a grid of custom objects — which are a subclass of
UIControl — and translate the portion of the image being split to the location in the grid where the tile is located. When a user moves the tiles around, each tile still displays its original portion of the image. So, you are using the same image but only displaying a portion of the image in each tile.
After completing the lab, Joe went over view transitions. View transitions allow you to easily add common animations (like the peel and flip animations you see on many iPhone apps) to your applications to create nice visual effects and a good user experience. For example, in our Tile Game app, when you move a tile it abruptly jumps from the old location to the new location with no transition of any kind. Also, when you touch the "info" button to select a new tiled-image there is no animation. It would be better to make the tiles slide from square to square, and to flip the tile over to the image selection screen. View transitions let you do this pretty easily. (Joe mentioned that later we'll be covering Core Animation which is more powerful and lets you perform all kinds of advanced animations.)
So, to reiterate, view transitions are meant for simple transitions like flipping, peeling, and so on. There are several "inherently animatable" properties of a
UIView: the view frame, the view bounds, the center of the view, the transform (orientation) of the view, and the alpha (opacity) of the view. For example, to create a pulsing effect you could peform two alpha animations one after the other: the first one would transition the view from completely opaque to completely transparent, and the second animation would transition back from completely transparent to completely opaque. For the View Transition lab we used a "flip" animation to flip between views.
You use class methods of
UIView to begin animations, setup view transitions, and commit the transitions. You use
beginAnimations to begin animations. Then you define the animations using methods like
setAnimationTransition, which set the length of time over which the animation occurs and the type of animation such as peel or flip, respectively. Then you perform actions like add and remove subviews or perform transitions on any of the "inherently animatable" properties in an "animation block." To start the animations on screen you call
commitAnimations after the animation block. This seems similar to how transactions work in a relational database, in that you begin a transaction, define what you want done, and then when satisfied you commit those "changes." Joe mentioned that Core Graphics essentially uses a hidden or offscreen view to store the actions and only shows the animations and actions when the animations are committed.
Next up: Core Animation. While view transitions are for simple animations, with Core Animation you can animate almost anything you want. The basic idea here is that when using Core Animation, the animation takes place on a
CALayer rather than the
UIView. There is one
UIView, and the
CALayer is a cached copy of the content in the
UIView. As soon as an animation begins, the
CALayer are interchanged, and all drawing operations during the animation take place on the
CALayer; in other words when an animation begins the
CALayer becomes visible and handles drawing operations. After the animation ends the
UIView is made visible again and the
CALayer is no longer visible, at which point the
UIView resumes responsibility for drawing.
You create animations using subclasses of
CAAnimation and adding them to the
CABasicAnimation is the most basic form of animation: you can animate a specific property, known as a key path, to perform a linear transition from an original value to a final value. For example, using a key path of "opacity" you can transition an object from opaque to translucent, or vice versa.
You can also combine multiple animations using a
CAAnimationGroup, which is itself a subclass of
CAAnimation (hey, that's the Composite design pattern for anyone who cares anymore). You can use
CAKeyframeAnimation to perform nonlinear animations, in which you have one or more "waypoints" during the animation. In other words, with
CAKeyframeAnimation you define transitions for a specific attribute — such as opacity, position, and size — that have different values at the various waypoints. For example, in the Core Animation lab exercise we defined a rotation animation to "jiggle" an element in the Tile Game we created earlier to indicate that a tile cannot be moved. The "jiggle" animation uses the
CAKeyframeAnimation to rotate a tile left, then right, then left, then right, and finally back to its original position to give the effect of it jiggling back and forth several times.
To finish up Core Animation, Joe covered how to use Media Timing Functions to create various effects during animations. For example, we used
CAMediaTimingFunctionEaseIn while sliding tiles in the Tile Game. Ease-in causes the tile animation to start slowly and speed up as it approaches the final location. Finally, I should mention that, as with most iPhone APIs, you can set up a delegate to respond when an animation ends using
animationDidStop. For example, when one animation stops you could start another one and chain several animations together.
After a cheeseburger and fries lunch, we learned how to use the iPhone's camera. The bad news about the camera is that you can't do much with it: you can take pictures (obviously) and you can choose photos from the photo library. The good news is that using the camera in code is really simple. You use a
UIImagePickerController, which is a subclass of
UINavigationController, and push it modally onto the top view controller using the
presentModalViewController:animated method. Since it is modal, it takes over the application until the user cancels or finishes the operation. Once a user takes a picture or selects one from the photo library,
UIImagePickerController returns the resulting image to its delegate.
There are two delegate methods you can implement. One is called when the user finishes picking an image and the other is called if the user cancelled the operation. The delegate method called when a user selects a picture returns the chosen image and some "editingInfo" which allows you to move and scale the image among other things. The lab exercise for the camera involved modifying the Tile Game to allow the user to take a photo which then becomes the game's tiled-image.
The accelerometer on the iPhone is cool. It measures the G-forces on the iPhone. You use a simple, delegate-based API to handle accelerations. While the API itself is pretty simple, the math involved and thinking about the orientation of the iPhone and figuring out how to get the information you want is the hard part. But you'd kind of expect that transforming acceleration information in 3D space into information your application can use isn't exactly the easiest thing. (Or, maybe it's just that I've been doing web applications too long and don't know how to do math anymore.)
There is one shared instance of the accelerometer, the
UIAccelerometer object. As you might have guessed, you use a delegate to respond to acceleration events, specifically the
accelerometer:didAccelerate method which provides you the
UIAccelerometer and a
UIAcceleration object. You need to specify an update interval for the accelerometer, which determines how often the
accelerometer:didAccelerate delegate method gets called.
UIAcceleration object contains the measured G-force along the x, y, and z axes and a time stamp when the measurements were taken. One thing Joe mentioned is that you should not assume a base orientation of the iPhone whenever you receive acceleration events. For example, when your application starts you have no idea what the iPhone's orientation is. To do something like determine if the iPhone is being shaken along the z-axis (which is perpendicular to the screen) you can take an average of the acceleration values over a sample period and look at the standard deviation; if the value exceeds some threshold your application can decide the iPhone is being shaken and you can respond accordingly. For the lab exercise, we modified the Tile Game to use the accelerometer to slide the tiles around as the user tilts the iPhone and to randomize the tiles if the user shakes the iPhone. Pretty cool stuff!
Well, I guess the fun had to end at some point. That point was when we covered Web Services, mainly because it reminded me that, no, I'm not going to be programming the iPhone next week for the project I'm on, and instead I'm going to be doing "enterprisey" and "business" stuff. Oh well, if we must, then we must.
Fortunately, Joe is defining web services as "XML over HTTP" and not as WS-Death-*, though of course if you really want to reduce the fun-factor go ahead Darth. To retrieve web resources you can use
NSURL to create a URL,
NSMutableURLRequest to create a new request, and finally
NSURLConnection to make the connection, send the request, and get the response. You could also use
NSURLConnection with a delegate to do asynchronous requests, which might be better to prevent your application's UI from locking up until the request completes or times out.
If you have to deal with an XML response, you can use
NSXMLParser, which is an event-based (SAX) parser. (By default there is no tree-based (DOM) parser on the iPhone, but apparently you can use libxml to parse XML documents and get back a doubly-linked list which you can use to traverse the nodes of the document.) You give
NSXMLParser a delegate which recieves callbacks during the parse, for example
parser:didStartElement:namespaceURI:qualifiedName:attributes. Then it's time to kick it old school, handling the SAX events as they come in, maintaining the stack of elements you've seen, and writing logic to respond to each type of element you care about. For our Web Services lab we wrote a simple application that connected to random.org, which generates random numbers based on atmospheric noise, and made a request for 10 random numbers, received the response as XHTML, extracted the numbers, and finally displayed them to the user.
Last up for today was using the iPhone Address Book. There are two ways to use the address book. The first way leverages the standard iPhone Address Book UI. The second uses a lower-level C API in the AddressBook framework.
When you use the Address Book UI, you use the standard iPhone address book interface in your application. It allows the user to select a person or individual items like an email address or phone number. You must have a
UIViewController and define a delegate conforming to
ABPeoplePickerNavigationControllerDelegate protocol. You then present a modal view controller passing it a
ABPeoplePickerNavigationController which then takes over and displays the standard iPhone address book application. You receive callbacks via delegate methods such as
peoplePickerNavigationController:shouldContinueAfterSelectingPerson to determine what action(s) to take once a person has been chosen. You, as the caller, are responsible for removing the people picker by calling the
dismissModalViewControllerAnimated method. We created a simple app that uses the Address Book UI during the first part of the lab exercise.
Next we covered the AddressBook framework, which is bare-bones, pedal-to-the-metal, C code and a low-level C API. However, you get "toll-free bridging" of certain objects meaning you can treat them as Objective-C objects, for example certain objects can be cast directly to an Objective-C
NSString. Another thing to remember is that with the AddresBook framework there is no autorelease pool and you must call
CFRelease on objects returned by methods that create or copy values. Why would you ever want to use the AddressBook framework over the Address Book UI? Mainly because it provides more functionality and allows you to directly access and potentially manipulate, via the API, iPhone address book data. For the second part of the Address Book lab, we used the AddressBook API to retrieve specific information, such as the mobile phone number, on selected contacts.
Using View Transitions and Core Animation can really spice up your apps and create really a cool user experience. (Of course you could probably also create really bad UIs as well if overused.) The camera is pretty cool, but the most fun today was using the accelerometer to respond to shaking and moving the iPhone. Web services. (Ok, that's enough on the enterprise front.) Last, using the various address book frameworks can certainly be useful in some types of applications where you need to select contacts.
Something in one of the labs we did today was pretty handy, namely that messages to
nil objects are ignored in Objective-C. There are a lot of times this feature would be hugely useful, though I can also see how it might cause debugging certain problems to be really difficult! There's even a design pattern named the Null Object Pattern since in languages like Java you get nasty things like null-pointer exceptions if you try to invoke methods on a
Man, we really covered a lot today, and now I am sad that tomorrow is the last day of iPhone bootcamp. I should become a professional Big Nerd Ranch attendee; I think that would be a great job!