What You’ll Learn in This Hour:
The use of sliders and steppers for user input
Configuring and manipulating slider and stepper input ranges
How to add image views to your projects
Ways of creating and controlling simple animations
How to change the appearance of the iOS status bar
The text input and output that you learned about in the preceding hour is certainly important, but iOS is known for its attractive graphics and “touchable” user interface (UI). This hour expands our interface toolkit to include images, animation, and the very touchable slider and stepper controls.
We’ll implement an application to combine these new features with some simple logic to manipulate input data in a unique way. These new capabilities will help you build more interesting and interactive applications—and of course, there’s more to come.
Although application logic is always the most important part of an application, the way the interface works plays a big part in how well it will be received. For Apple and the iDevices, providing a fun, smooth, and beautiful user experience has been key to their success; it’s up to you to bring this experience into your own development. The iOS interface options give you the tools to express your application’s functionality in fun and unique ways.
This hour introduces three very visual interface features: sliders and steppers for input, and image views for output.
The first new interface component that we use this hour is a slider (UISlider
). Sliders are a convenient touch control used to visually set a point within a range of values. Huh? What?
Suppose that you want your user to be able to speed something up or slow it down. Asking users to input timing values is unreasonable. Instead, you can present a slider, as shown in Figure 8.1, where they can touch and drag an indicator (called a thumb) back and forth on a line. Behind the scenes, a value
variable property is being set that your application can access and use to set the speed. There is no need for users to understand the behind-the-scenes details or do anything more than drag with their fingers.
Sliders, like buttons, can react to events or can be read passively like a text field. If you want the user’s changes to a slider to immediately have an effect on your application, you must have it trigger an action.
Similar to a slider is a stepper (UIStepper
). Like a slider, a stepper offers a means of inputting a number from a range of values visually. How it accomplishes this, however, differs a bit. A stepper, shown in Figure 8.2, offers –/+ buttons in a single control. Pushing a side of the control decrements or increments an internal value
variable property.
You can use steppers as alternatives to traditional text input fields for values, such as setting a timer or controlling the speed of an onscreen object. Because they do not provide an onscreen representation of their current internal value, you must make sure that when a stepper is used to update a portion of your interface that you indicate a change has been made.
Steppers provide the same range of events as sliders, making it possible to easily react to changes or read the internal value
variable at any time.
Image views (UIImageView
) do precisely what you’d think: They display images. You can add them to your application views, using them to present information to the user. You can even use an instance of UIImageView
to create a simple frame-based animation with controls for starting, stopping, and even setting the speed at which the animation is shown.
With Retina display devices, your image views can even take advantage of the high-resolution display for crystal-clear images. Even better, you need no special coding. Instead of checking for a specific device, you can just add multiple images to your project, and the image view will load the right one at the right time. We do not go through all the steps to make this happen each time we use an image in this book, but later in this hour’s lesson, you will learn how you can add this capability to your projects.
There’s something about interface components that make users take notice. They’re visually interesting, attract and keep attention, and, on a touch screen, are fun to play with. In this hour’s project, we take advantage of all of our new UI elements (and some old friends) to create a user-controlled animation.
As mentioned earlier, image views can be used to display image file resources and show simple animations, whereas sliders provide a visual way to choose a value from a range. We’ll combine these in an application we’re calling ImageHop.
In ImageHop, we create a looping animation using a series of images and an image view instance (UIImageView
). We allow the user to set the speed of the animation using a slider (UISlider
). What will we use as an animation? A field of hopping bunnies. What will the user control? Hops per second for the “lead” bunny, of course. The “hops” value will be set by the slider and displayed in a label (UILabel
). A stepper provides another way of changing the speed in precise increments. The user can also stop or start the animation using a button (UIButton
).
Figure 8.3 shows the completed application with the bunnies at rest.
We should discuss two pieces of this project before getting too far into the implementation:
First, image view animations are created using a series of images. I’ve provided a 20-frame animation with this project, but you’re welcome to use your own images if you prefer.
Second, although steppers and sliders enable users to visually enter a value from a range, there isn’t much control over how that is accomplished. For example, the minimum value must be smaller than the maximum, and you can’t control which dragging direction of the slider (or which side of the stepper) increases or decreases the result value. These limitations aren’t showstoppers; they just mean that you might have to do a bit of math (or experimentation) to get the behavior you want.
Begin this project in the same way as the last:
1. Launch Xcode, and then choose File, New, Project.
2. Select the iOS Application project type, and then find and select the Single View Application option in the Template list on the right.
3. Click Next to continue.
4. Enter the project name ImageHop.
5. Be sure that the appropriate device is selected, along with the Swift programming language, and then click Next.
6. Choose a save location and click Create to generate the new project.
This project makes use of 20 frames of animation stored as PNG files. The frames are included in the Images folder within the ImageHop project folder.
Because we know upfront that we need these images, we can add them to the project immediately. Open the project group in the project navigator area of Xcode. Click the main Images.xcassets file to open the project’s image assets. Now, drag the Images folder from the Finder into the column on the left inside the asset library. You’ll see a new Images folder appear within the assets library; this folder contains all the animation images we need for the project.
We can now access the image files easily within our code and the Interface Builder (IB) editor.
In this application, we need to provide outlets and actions for several objects.
A total of nine outlets are required. First we need five image views (UIImageView
), which will contain the five copies of our bunny animation. These are referenced through the variable properties bunnyView1
, bunnyView2
, bunnyView3
, bunnyView4
, and bunnyView5
. The slider control (UISlider
) sets the speed and is connected via speedSlider
, and the speed value itself is output in a label named hopsPerSecond
(UILabel
). A stepper control (UIStepper
) gives another means of setting the speed and can be accessed with speedStepper
.
Finally, a button (UIButton
) toggles the animation on and off and is connected to an outlet toggleButton
.
Note
Why do we need an outlet for the button? Shouldn’t it just be triggering an action to toggle the animation? Yes, the button could be implemented without an outlet, but by including an outlet for it, we have a convenient way of setting the button’s title in the code. We can use this to change the button to read Stop when the image is animating or Start when the animation has stopped.
We need three actions. setSpeed
is the method called when the slider value has changed and the animation speed needs to be reset. setIncrement
serves a similar purpose and is called when the stepper control is used. And toggleAnimation
is used to start and stop the animation sequence.
Now let’s create the UI.
With all the outlets and actions we just discussed, it might seem like creating the UI for ImageHop will be a nightmare. In reality, it’s quite simple because the five animation sequences are really just copies of a single image view (UIImageView
). Once we add one, we can copy it four times almost instantly.
To get started, I recommend opening the Main.storyboard file, selecting the view controller for your scene, and then using the Attributes Inspector to set the simulated screen size to something manageable (the 4.7-inch iPhone, for me). This will make it easier to layout an interface for the animations.
In this exercise, our view creation begins with the most important object of the project: the image view (UIImageView
). With the Main.storyboard file open, access the Object Library, and drag an image view into the application’s view.
Because the view has no images assigned, it is represented by a light-gray rectangle. Use the selection handles on the rectangle to size it to fit in the upper center of the interface (see Figure 8.4).
There are very few properties for configuring the functionality of an image view. We are interested in the image variable property: the image that is going to be displayed. Select the image view and press Option-Command-4 to open the Attributes Inspector (see Figure 8.5).
Using the Image drop-down menu, choose one of the image resources available. This is the default image that is shown before the animation runs, so using the first frame (frame-1
) is a good choice.
The image view updates in IB to show the image resource that you’ve chosen.
Note
What about the animation? Isn’t this just a frame? Yes, if we don’t do anything else, the image view shows a single static image. To display an animation, we need to create an array with all the frames and supply it programmatically to the image view object. We do this in a few minutes, so just hang in there.
After you’ve added the image view, create four additional copies by selecting it in your UI and choosing Edit, Duplicate (Command-D) from the menu. Scale and position the copies around the first image view. Don’t worry if there is some overlap between the image views; this does not affect the application at all. For my implementation, I also used the Attributes Inspector (Option-Command-4) to set an alpha of .75
and .50
on some of the image views to make them partially transparent.
You’ve just created your field of bunnies. Your display should now resemble Figure 8.6.
The next piece that our interface needs is the slider that will control the speed. Open the Object Library and drag the slider (UISlider
) into the view, under the image views. Using the selection handles on the slider, click and drag to size it to take up width of the view, using the alignment guides to leave an appropriate space on the left and right.
Because a slider has no visual indication of its purpose, it’s a good idea to always label sliders so that your users will understand what they do. Drag a label object (UILabel
) from the library into your view (just above the slider). Double-click the text and set it to read Speed:. Position it so that it is center-aligned with the slider, as shown in Figure 8.7.
Sliders make their current settings available through a value variable property that we’ll be accessing in the view controller. To change the range of values that can be returned, we need to edit the slider attributes. Click to select the slider in the view, and then open the Attributes Inspector (Option-Command-4), as shown in Figure 8.8.
You should change the Minimum, Maximum, and Initial fields to contain the smallest, largest, and starting values for the slider. For this project, use .25, 1.75, and 1.0, respectively.
Make sure that the Continuous check box is not checked. This option, when enabled, has the control to generate a series of events as the user drags back and forth on the slider. When it isn’t enabled, events are generated only when users lifts their finger from the screen. For our application, this makes the most sense and is certainly the least resource-intensive option.
You can also configure the slider with images at the minimum and maximum sliders of the control. Use the Min Image and Max Image drop-downs to select a project image resource if you want to use this feature. (We do not use it in this project.)
With the slider in place, our next UI element is the stepper. Using the Object Library, drag a stepper button (UIStepper
) into the view. Position the stepper directly below, and centered on, the slider, as shown in Figure 8.9.
Once the stepper is added, you must configure its range attributes just as you did the slider. Ultimately, we want to use the value of the stepper to change the speed in exactly the same way as with the slider, so the closer the two elements mirror one another, the better.
To set the range allowed on the stepper, select it in the view, and then open the Attributes Inspector (Option-Command-4). Again, provide .25, 1.75, and 1.0 for the Minimum, Maximum, and Current (initial) values for the stepper. Set the step value to .25. This is the amount added to or subtracted from the current value when the stepper is pressed.
Use the Behavior check box to turn off Autorepeating, meaning that the user will not be able to press and hold to continue incrementing or decrementing the stepper’s value. You should also uncheck the Continuous check box so that only distinct events are generated when the user finishes interacting with the control. Finally, turn on the Wrap behavior. Wrap, when on, automatically sets the stepper’s value to the minimum value when the maximum is exceeded (or vice versa), effectively wrapping around the range of values it can represent. If Wrap is off, the stepper stops at the minimum or maximum value and does not change. Figure 8.9 shows the final stepper configuration in the Attributes Inspector.
The remaining components of the ImageHop application are interface features that you’ve used before, so we’ve saved them for last. We finish things up by adding a button to start and stop the animation, a readout of the speed of the lead animated rabbit in maximum hops per second, and a pretty background for the bunnies to hop on.
Drag two labels (UILabel
) to the top of the view. The first label should be set to read Maximum Hops Per Second: and be located in the upper left of the view. Add the second label, which is used as output of the actual speed value, to the right of the first label.
Change the output label to read 1.00 hps. (The speed that the animation will be starting out at.) Using the Attributes Inspector (Option-Command-4), set the text of the label to align right; this keeps the text from jumping around as the user changes the speed.
The last functional part of the ImageHop interface is the button (UIButton
) that starts and stops the animation. Drag a new button from the Object Library to the view and positioning it at the bottom center of the UI. Double-click the button to edit the title and set it to Hop!.
For fun, we can spruce up the application a bit by toning down the blinding-white screen that the iOS views use by default. To do this, click the View icon in the document outline and open the Attributes Inspector (Option-Command-4). Use the Background attribute to set a green background for the application, as shown in Figure 8.10.
In addition to the color, it would be great if the bunnies could be hopping in grass, wouldn’t it? (Bunnies like grass.) To add a background image, drag another instance of UIImageView
to the view. Resize it to cover the image views that contain the bunny animations and stretch all the way to the top of the view. Use Editor, Arrange, Send to Back to place the background image view behind the animation image views and the “hops per second” labels.
With the background image view selected, use the Attributes Inspector to set the Image value to the background image resource that you added earlier in this hour.
Notice that now the “hops per second” labels are difficult to read. To fix this, select the labels and use the Attributes Inspector to set their color to white. Figure 8.11 shows the final application interface.
With that, it’s time to create the outlets and actions and begin coding.
Whew! That’s the most complicated application interface we’ve had to deal with yet. Reviewing what we’ve done, we have a total of nine outlets that need to be created, along with three actions.
In case you don’t recall what these were, let’s review, starting with the outlets:
Bunny animations (UIImageView): bunnyView1
, bunnyView2
, bunnyView3
, bunnyView4
, and bunnyView5
Slider speed setting (UISlider): speedSlider
Stepper speed setting (UIStepper): speedStepper
Maximum speed readout (UILabel): hopsPerSecond
Hop/Stop button (UIButton): toggleButton
And the actions:
Start/stop the animation using the Hop/Stop button: toggleAnimation
Set the speed with the slider changes: setSpeed
Set the speed when the stepper changes: setIncrement
Prepare your workspace for making the connections. Make sure that the Main.storyboard file is open in the IB editor and switch to the assistant editor mode. If the ViewController.swift file isn’t picked by default, select it from the bar at the top of the editor. Your UI design and the ViewController.swift file should be visible side by side.
Start by Control-dragging from the main ImageView
instance (the large bunny) to the line following the class
directive in the ViewController.swift file. When prompted, be sure to configure the connection as an outlet and the name as bunnyView1, leaving the other values set to their defaults (type UIImageView
, storage Weak), as shown in Figure 8.12.
Repeat the process for the remaining images views that we want to animate, targeting each successive connection below the last line that was added. It doesn’t matter which bunny is bunnyView2
, bunnyView3
, bunnyView4
, or bunnyView5
, just as long as they’re all connected.
After connecting the image views, proceed with the rest of the connections. Control-drag from the slider (UISlider
) to the line under the last @IBOutlet
declaration and add a new outlet named speedSlider
. Do the same for the stepper (UIStepper
), adding an outlet named speedStepper
. Finish off by connecting the hops per second output UILabel
(1.00 hps initially) to hopsPerSecond
and the Hop! UIButton
to toggleButton
.
Our outlets are finished. Let’s take care of our actions.
This project requires three distinct actions. The first, toggleAnimation
, is triggered when the user presses the Hop! button, and it starts the animation sequence. Add a definition for this action by Control-dragging from the button in your interface to a line below the outlet declarations. When prompted, set the connection type to Action and provide toggleAnimation
as the name, leaving all other values as the defaults, as shown in Figure 8.13.
Next, Control-drag from the slider to a line below the just-added IBAction
line. Create an action named setSpeed
that is triggered from the UISlider
’s Value Changed event.
Finally, create the third action, this one triggered from the stepper, naming it setIncrement and again using the Value Changed event.
To check your work, the code at the top of your ViewController.swift file should now resemble Listing 8.1.
class ViewController: UIViewController {
@IBOutlet weak var bunnyView1: UIImageView!
@IBOutlet weak var bunnyView2: UIImageView!
@IBOutlet weak var bunnyView3: UIImageView!
@IBOutlet weak var bunnyView4: UIImageView!
@IBOutlet weak var bunnyView5: UIImageView!
@IBOutlet weak var speedSlider: UISlider!
@IBOutlet weak var speedStepper: UIStepper!
@IBOutlet weak var hopsPerSecond: UILabel!
@IBOutlet weak var toggleButton: UIButton!
@IBAction func toggleAnimation(sender: AnyObject) {
}
@IBAction func setSpeed(sender: AnyObject) {
}
@IBAction func setIncrement(sender: AnyObject) {
}
We’re ready to start coding the implementation of our magical hopping bunny animation. Surprisingly, the code needed to make this work is really quite minimal.
The view controller needs to manage a total of four different things to make our application work as we envision.
First, we need to load the image animations for each of the ImageViews
(bunnyView1
, bunnyView2
, and so on); we managed to set a static frame of the image in IB, but that isn’t enough to make them animate. Next, we must implement toggleAnimation
so that we can start and stop the onscreen animation from the Hop! button. Finally, the setSpeed
and setIncrement
methods must be written to control the maximum speed of the animations.
Animating images requires us to build an array of image objects (UIImage
) and pass them to an image view object. Where should we do this? As with setting button templates in the last hour’s project, the ViewDidLoad
method of our view controller provides a convenient location for doing additional setup for the view, so that’s what we’ll use.
1: override func viewDidLoad() {
2: super.viewDidLoad()
3: // Do any additional setup after loading the view, typically from a nib.
4: let hopAnimation: [UIImage] = [
5: UIImage(named: "frame-1")!,
6: UIImage(named: "frame-2")!,
7: UIImage(named: "frame-3")!,
8: UIImage(named: "frame-4")!,
9: UIImage(named: "frame-5")!,
...
20: UIImage(named: "frame-16")!,
21: UIImage(named: "frame-17")!,
22: UIImage(named: "frame-18")!,
23: UIImage(named: "frame-19")!,
24: UIImage(named: "frame-20")!
25: ]
26: bunnyView1.animationImages=hopAnimation
27: bunnyView2.animationImages=hopAnimation
28: bunnyView3.animationImages=hopAnimation
29: bunnyView4.animationImages=hopAnimation
30: bunnyView5.animationImages=hopAnimation
31: bunnyView1.animationDuration=1.0
32: bunnyView2.animationDuration=1.0
33: bunnyView3.animationDuration=1.0
34: bunnyView4.animationDuration=1.0
35: bunnyView5.animationDuration=1.0
36: }
To configure the image views for animation, first an array constant of UIImage
objects is declared in line 4. This array is named hopAnimation
and is populated in lines 5–24. To build the array, all we need to do is provide a comma-separated list of objects (in this case, UIImage
objects). To create these objects, we use the UIImage
convenience initialization method init-Named
that takes a single string parameter, named
, and returns a UIImage
object of the image with that name.
Each use of the UIImage
initialization method requires the an exclamation point (!) suffix to unwrap it. This is because the initialization, if given a bad image name, will return nothing. The !
tells Xcode that we know what we’re doing and we want to unwrap (use) the value returned regardless. Note that lines 10–19 have been removed to save space; if you don’t add them, the animation will be missing a few frames.
Once an array is populated with image objects, you can use it to set up the animation of an image view. To do this, set the animationImages
variable property of the image view to the array. Lines 26–30 accomplish this for bunnyView1
through bunnyView5
in our sample project.
Another UIImageView
variable property that we want to set right away is the animationDuration
. This is the number of seconds it takes for a single cycle of the animation to be played. If the duration is not set, the playback rate is 30 frames per second. To start, our animations are set to play all the frames in 1 second, so lines 31–35 set the animationDuration
to 1.0
for each bunnyView UIImageView
.
We’ve now configured all five of our image views to be animated, but even if we build and run the project, nothing is going to happen. That’s because we haven’t added controls for actually starting the animation.
You’ve just learned how the animationDuration
variable property can change the animation speed, but we need three more variables/methods to actually display the animation and accomplish everything we want:
isAnimating: This variable property is true
if the image view is currently animating its contents.
startAnimating: This method starts the animation.
stopAnimating: This method stops the animation if it is running.
When the user touches the Hop! button, the toggleAnimation
method is called. This method should use the isAnimating
variable property of one of our image views (bunnyView1
, for example) to check to see whether an animation is running. If it isn’t, the animation should start; otherwise, it should stop. To make sure that the user interface makes sense, you should also alter the button itself (toggleButton
) to show the title Sit Still! if the animation is running and Hop! when it isn’t.
Add the code in Listing 8.3 to the toggleAnimation
method in your view controller swift file.
1: @IBAction func toggleAnimation(sender: AnyObject) {
2: if (bunnyView1.isAnimating()) {
3: bunnyView1.stopAnimating()
4: bunnyView2.stopAnimating()
5: bunnyView3.stopAnimating()
6: bunnyView4.stopAnimating()
7: bunnyView5.stopAnimating()
8: toggleButton.setTitle("Hop!", forState: UIControlState.Normal)
9: } else {
10: bunnyView1.startAnimating()
11: bunnyView2.startAnimating()
12: bunnyView3.startAnimating()
13: bunnyView4.startAnimating()
14: bunnyView5.startAnimating()
15: toggleButton.setTitle("Sit Still!", forState: UIControlState.Normal)
16: }
17: }
Lines 2 and 9 provide the two different conditions that we need to work with. Lines 3–8 are executed if the animation is running, and lines 10–15 are executed if it isn’t. In lines 3–7 and lines 10–14, the stopAnimating
and startAnimating
methods are called for the image views to stop and start the animation, respectively.
Lines 8 and 15 use the UIButton
instance method setTitle:forState
to set the button title to the string "Hop!"
or "Sit Still!"
. These titles are set for the button state of UIControlState.Normal
. The normal state for a button is its default state, prior to any user event taking place.
At this point, if you’re really anxious, you can run the application and start and stop the animation. That said, we’ve only got a few more lines of code required to set the animation speed, so let’s move on.
The slider triggers the setSpeed
action after the user adjusts the slider control. This action must translate into several changes in the actual application: First, the speed of the animation (animationDuration
) should change. Second, the animation should be started if it isn’t already running. Third, the button (toggleButton
) title should be updated to show the animation is running. And finally, the speed should be displayed in the hopsPerSecond
label.
Add the code in Listing 8.4 to the setSpeed
method stub, and then let’s review how it works.
1: @IBAction func setSpeed(sender: AnyObject) {
2: bunnyView1.animationDuration=Double(2.0-speedSlider.value)
3: bunnyView2.animationDuration =
4: bunnyView1.animationDuration+Double(arc4random_uniform(10))/10.0
5: bunnyView3.animationDuration =
6: bunnyView1.animationDuration+Double(arc4random_uniform(10))/10.0
7: bunnyView4.animationDuration =
8: bunnyView1.animationDuration+Double(arc4random_uniform(10))/10.0
9: bunnyView5.animationDuration =
10: bunnyView1.animationDuration+Double(arc4random_uniform(10))/10.0
11:
12: bunnyView1.startAnimating()
13: bunnyView2.startAnimating()
14: bunnyView3.startAnimating()
15: bunnyView4.startAnimating()
16: bunnyView5.startAnimating()
17:
18: toggleButton.setTitle("Sit Still!", forState: UIControlState.Normal)
19:
20: let hopRateString=String(format: "%1.2f hps", 1/(2-self.speedSlider.value))
21: hopsPerSecond.text=hopRateString
22: }
In line 2, the bunnyView1 animationDuration
variable property is set to 2
minus the value of the slider (speedSlider.value
); this sets the speed of our “lead” bunny animation. This, if you recall, is necessary to reverse the scale so that faster is on the right and slower is on the left.
Lines 3–10 set the remaining image animations to the same speed as the lead animation (bunnyView1.animationDuration
) plus a fraction of a second. How do we get this fraction of a second? Through the magic of this randomization function: Double(arc4random_uniform(10))/10
. The function arc4random_uniform(10)/10
returns a random number between 0 and 10. We divide this by 10 to give us a fraction (1/10, 2/10, and so on). We must use the function Double()
with arc4random_uniform()
because the animationDuration
variable property expects a double
floating-point value and arc4random_uniform
returns just a floating-point value
. Why doesn’t Xcode convert this for us automatically? I’m not sure, but if you don’t use Double()
, you’ll get an error noting that a conversation is necessary. Perhaps in the future, this won’t be required.
Lines 12–16 use the startAnimating
method to start the animations running. Note that it is safe to use this method if the animation is already started, so we don’t really need to check the state of the image view. Line 18 sets the button title to the string “Sit Still!
” to reflect the animated state.
Line 20 creates a hopRateString
constant. The string is initialized with a format of "%1.2f"
, based on the calculation of 1/(2-animationSpeed.value)
.
Let’s break that down a bit further: Remember that the speed of the animation is measured in seconds. The fastest speed we can set is 0.25 (a quarter of a second), meaning that the animation plays four times in 1 second (or four hops per second). To calculate this in the application, we simply divide 1 by the chosen animation duration, or 1/(2-animationSpeed.value)
. Because this doesn’t necessarily return a whole number, we use the stringWithFormat
method to create a string that holds a nicely formatted version of the result. The format
parameter string "%1.2f hps"
is shorthand for saying the number being formatted as a string is a floating-point value (f
) and that there should always be one digit on the left of the decimal and two digits on the right (1.2
). The hps
portion of the format is just the hops per second unit that we want to append to the end of the string. For example, if the equation returns a value of .5 (half a hop a second), the string stored in hopRateString
is set to "0.50 hps"
.
In line 21, the output label (UILabel
) in the interface is set to the hopRateString
.
With that, the slider speed control is in place. Just one more method to implement: setIncrement
, triggered by the UIStepper
(speedStepper
) interface object.
This hour’s lesson has been pretty intensive, and, if you’re like me, your fingers are probably getting tired of clicking and typing. The bad news is that we aren’t quite done; we still need to implement the setIncrement
method. The good news? It takes two lines to complete.
Given all the work required to set the speed with the slider, how is this possible? Quite simple. Because we configured the stepper to generate the same range of values as the slider, we set the slider’s value variable property to the value of the stepper. When that is done, we can manually call the setSpeed
method and everything will (almost) just work.
Update the setIncrement
method stub in your view controller to read as shown in Listing 8.5.
@IBAction func setIncrement(sender: AnyObject) {
speedSlider.value=Float(speedStepper.value)
setSpeed(nil)
}
The first line, as expected, sets the value variable property of the slider to the value of the stepper. What isn’t expected, however, is that we must use a function, this time Float()
, to convert the value
of the stepper (a Double
) to a float
. Again, this is something I’d expect to happen automatically, but at present, it doesn’t.
The assignment in the first line will trigger the slider to update visually in your interface, but it won’t trigger its Value Changed event and call the setSpeed
method. We do that manually by calling the setSpeed
method we implemented in Listing 8.4.
Unfortunately, that’s not quite the end of the story. Notice that nil
is passed as a parameter when we call setSpeed
. This is because setSpeed
, as an action method, was created with a sender
parameter that is automatically set to the object that triggered the action. The method can then examine the sender
and react accordingly.
In the case of setSpeed
, we never used the sender
variable in the implementation, so sending the nil
value will satisfy the method’s requirement for a parameter, but one minor adjustment still needs to be made. So, revisit the setSpeed
method from Listing 8.4, and make the sender
value optional by adding a question mark at the end of the parameter declaration, as follows:
@IBAction func setSpeed(sender: AnyObject?) {
This change enables us to use nil
(“nothing”) as the parameter value when we call the method. As long as a variable is declared as optional, it can hold a value, or nothing (nil
).
If you build your application, it will run and behave just as we planned. Unfortunately, you’ll have an iOS status bar that doesn’t quite fit with your background. Instead of a dark status bar lost on a background of lush beautiful grass, wouldn’t it be great to have a light status bar that you can actually read? Unfortunately, there isn’t an IB setting to make this happen, but fixing it is simple. Add the code in Listing 8.6 to your ViewController.swift file.
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return UIStatusBarStyle.LightContent
}
This method, added to your view controller, tells iOS what kind of status bar it should display. You can choose from UIStatusBarStyle.Default
(a dark status bar) or UIStatusBarStyle.LightContent
(a light status bar).
Well done. With the inclusion of the status bar fix, you’ve just completed the app.
To try your hand at controlling an out-of-control bunny rabbit, click Run on the Xcode toolbar. After a few seconds, the finished ImageHop application will start, as shown in Figure 8.14.
Although ImageHop isn’t an application that you’re likely to keep on your device (for long), it did provide you with new tools for your iOS application toolkit. The UIImageView
class can easily add dynamic images to your programs, and UISlider
and UIStepper
offer uniquely touchable input solutions.
Although many hours in this book focus on adding features to the UI, it is important to start thinking about the application logic that will bring your UI to life. As we experienced with our sample application, sometimes creativity is required to make things work the way we want.
Review the properties and methods for UISlider
and UIStepper
classes and consider how you might use these elements in your own apps. Can you think of any situations where the stepper values couldn’t be used directly in your software? How might you apply application logic to map slider values to usable input? Programming is very much about problem solving; you’ll rarely write something that doesn’t have at least a few “gotchas” that need solved.
In addition to UISlider
and UIStepper
, you may want to review the documentation for UIImage
. Although we focused on UIImageView
for displaying our image animation, the images themselves were objects of type UIImage
. Image objects will come in handy for future interfaces that integrate graphics into the user controls themselves.
Finally, for a complete picture of how your applications will almost automatically take advantage of the higher-resolution Retina display, be sure to read the section “Supporting High-Resolution Screens in Views” within the Drawing and Printing Guide for iOS.
Users of highly visual devices demand highly visual interfaces. In this hour’s lesson, you learned about the use of three visual elements that you can begin adding to your applications: image views, sliders, and steppers. Image views provide a quick means of displaying images that you’ve added to your project—even using a sequence of images to create animation. Sliders can be used to collect user input from a continuous range of values. Steppers also provide user input over a range of numbers, but in a more controlled, incremental fashion. These new input/output methods start our exploration of iOS interfaces that go beyond simple text and buttons.
Although not complex, the information you learned in this hour will help pave the way for mega-rich, touch-centric user interfaces.
Q. Is the UIImageView the only means of displaying animated movies?
A. No. iOS includes a wide range of options for playing back and even recording video files. The UIImageView
class is not meant to be used as a video playback mechanism.
Q. Is there a vertical version of the slider control (UISlider)?
A. Not really. Only the horizontal slider is currently available in the iOS UI library. If you want to use a vertical slider control, you can use the horizontal slider, but apply a transformation (basically “spin” it in code). For example, if your slider is named mySlider
, you could add the following to viewDidLoad
to turn it into a vertical slider:
let transformSlider : CGAffineTransform =
CGAffineTransformMakeRotation(-(CGFloat(M_PI_2)))
speedSlider.transform = transformSlider;
1. What method would you use to set the text on a UIButton
?
a. setText:forState
b. setTitle:forState
c. setButton:forState
d. setButtonTItle:forState
2. Which of the following is a series of images that you can use to create an animation with this object?
a. UIImageView
b. UIImageAnimation
c. UIImageMovie
d. UIImage
3. What type of UI control is used to increment and decrement values by way of two connected buttons?
a. Segmented control
b. Slider
c. Switch
d. Stepper
4. You can choose how long it takes for an animation to run by using which UIImageView
variable property?
a. playDuration
b. animationLength
c. animationDuration
d. timeInSeconds
5. To pass nil
to the parameter of a method, that parameter must be defined as which of the following?
a. Unwrapped
b. Optional
c. Implied
d. Unneeded
6. Which UIImageView
method is used to start an animation sequence?
a. beginAnimation
b. startAnimation
c. beginAnimating
d. startAnimating
7. To check the status of an animation, you could turn to which variable property?
a. isAnimating
b. isAnimationRunning
c. animationStatus
d. animateStatus
8. The Continuous check box for a slider generates a continuous stream of what as the slider is moved?
a. Events
b. Methods
c. Functions
d. Outlets
9. Which function is used to turn a numeric value into a double
floating-point value?
a. Double()
b. Float()
c. DoubleFloat()
d. CGFloat()
10. The iPhone 6 and 6+ use these suffixes to denote their respective image scaling factor:
a. @1x, @2x
b. @1x, @3x
c. @3x, @4x
d. @2x, @3x
1. B. Use the setTitle:forState
method to set the title of a UIButton
.
2. A. Use a UIImageView
object to animate a series of still UIImage
objects.
3. D. A stepper moves through values using connected +/– buttons.
4. C. The animationDuration
variable property of a UIImageView
can be used to configure the length (in seconds) of the animation.
5. B. When a method’s parameter is defined as “optional” using the question mark (?
) character, you can pass nil
to that parameter.
6. D. To begin an animation, the startAnimating
method is used.
7. A. To check whether an animation is running, you can query the isAnimating
variable property to see if it is true
or false
.
8. A. A slider that is set to Continuous will generate a series of events as the user drags it from side to side.
9. A. If a Swift method or variable property expects a Double
floating-point value, the Double()
function can be used to convert other values to a double precision floating point number.
10. D. The @2x and @3x suffixes are used to denote the scaling factors of the iPhone 6 and iPhone 6+ displays, respectively.
1. Increase the range of speed options for the ImageHop animation example. Be sure to set the default value for the slider thumb to rest in the middle and update the stepper accordingly.
2. Provide an alternative means of editing the speed by enabling the user to manually enter a number in addition to using the slider. The placeholder text of the field should default to the current slider value.