iOS Recipes - Matt Drance [29]
[[NSBundle mainBundle] resourcePath]]];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc]
initWithContentsOfURL:url error:&error];
audioPlayer.numberOfLoops = 1;
[audioPlayer play];
You now have a way of adding a small slice of whimsy to some of the more mundane sections of your app. You have some flexibility in how you display the text on the page—you can change the text, the music, and the scroll speed.
Recipe 14 Create a Custom Number Control
Problem
You need a way to let your user select a numeric value—perhaps the difficulty level or number of players in a game. You could use one of the UI components Apple provides, but its style might not fit the look and feel of the rest of your app.
Solution
We can solve this problem in a number of ways, but our best bet is to create a custom control that maximizes the use of the touch interface (see Figure 18, The custom number spin control ). Through table views we’ve become accustomed to the dynamic feedback of our actions, with the momentum-based scrolling letting us “flick” our way through a whole set of data. We could go with UIPickerView, but it has a very specific style and only a few options for customization. What we’re looking for is something with similar mechanics but smaller in scope and more easily tailored to fit a particular UI style.
Figure 18. The custom number spin control
* * *
SWIZZLE, the free puzzle game in the App Store, uses just such a control as a means of selecting the game’s difficulty level. Let’s use an updated version of the code from that app, the SpinNumbers class, to walk through the technique for creating this style of control.
Like most of the UIKit control classes, such as buttons and sliders, SpinNumbers is a subclass of UIControl, which provides the methods we need to implement the target/action mechanism. This is the process by which the controller code can specify a selector to be called for a given event. Using the sendActionsForControlEvents: method, we can indirectly activate an action simply by specifying the appropriate control event. The UIControl base class code calls any selectors associated with that given event, so we don’t need to worry about which events, if any, have actions defined.
Looking at our implementation, the primary task of the setup method is to create the visual components of the control. These elements are based entirely on transformed layers. We start by constructing a composite image of the background and the label and then add the result as the content of each of the layers in turn. We apply an incrementing rotation transformation and positional translation to each layer, effectively adding it at the next position in a circle of layers.
To picture how this circle is constructed, imagine a playing card standing upright and a penny about 4 inches behind it. The card is our layer, and the penny is the center point of the circle we will create. Add another card next to the first, edges touching, and rotate it a little so that it is perpendicular to the penny. Repeat this until the cards form a complete circle around the penny.
As we construct our circle, each layer is added to a base layer, transformed, with a specific zPosition, equivalent to the radius of the circle, placing it at the correct distance from the center of the base layer. When we later rotate the base layer with a sublayer transform, the entire set of layers rotates as a single unit.
Say we want the control to have more of a three-dimensional appearance; we just add two semitransparent layers on top of the circle and to either side of the central layer. To give the impression of depth to the edges of the circle, we use gradients, from opaque to transparent, for the layer images—making it appear as though the center is spotlighted.
NumberSpinControl/NumberSpinControl/SpinNumbers.m
- (void)setup
{
CGFloat width = self.bounds.size.width;
self.cubeSize = self.bounds.size.height;
self.tileRect = CGRectMake(0, 0, self.cubeSize, self.cubeSize);
self.transformed = [CALayer layer];
self.transformed.frame = self.bounds;