3.
Slider & Labels
Written by Eli Ganim
Now that you’ve accomplished the first task of putting a button on the screen and making it show an alert, you’ll simply go down the task list and tick off the other items.
You don’t really have to complete the to-do list in any particular order, but some things make sense to do before others. For example, you cannot read the position of the slider if you don’t have a slider yet.
Now add the rest of the controls — the slider and the text labels — and turn this app into a real game!
When you’re done, the app will look like this:
Hey, wait a minute… that doesn’t look nearly as pretty as the game you saw in the screenshots from the previous chapter! The difference is that these are the standard controls. This is what they look like straight out of the box.
You’ll start off with a barebone version of the app and add graphics later.
In this chapter, you’ll cover the following:
- Portrait vs. landscape: Switch your app to landscape mode.
- Objects, data and methods: A quick primer on the basics of object-oriented programming.
- Adding the other controls: Add the rest of the controls necessary to complete the user interface of your app.
Portrait vs. landscape
Notice that in the previous screenshot, the dimensions of the app have changed: The iPhone is rotated to its side and the screen is wider but less tall. This is called landscape orientation.
You’ve no doubt seen landscape apps before on iPhone. It’s a common display orientation for games, but many other types of apps work in landscape mode, too, usually in addition to the regular “upright” portrait orientation.
For instance, many people prefer to write emails with their device in landscape because the wider screen allows for a bigger keyboard and easier typing.
In portrait orientation, the iPhone 8 screen consists of 375 points horizontally and 667 points vertically. For landscape, these dimensions are switched.
Converting the app to landscape
To switch the app from portrait to landscape, you have to do two things:
-
Make the view in Main.storyboard landscape instead of portrait.
-
Change the Supported Device Orientations setting of the app.
➤ Open Main.storyboard. In Interface Builder, in the View as: iPhone 8 panel, change Orientation to landscape:
This changes the dimensions of the view controller. It also puts the button off-center.
➤ Move the button back to the center of the view because an untidy user interface just won’t do in this day and age.
That takes care of the view layout.
➤ Run the app on iPhone 8 Simulator. Note that the screen does not show up as landscape yet, and the button is no longer in the center.
➤ Choose Hardware ▸ Rotate Left or Rotate Right from Simulator’s menu bar at the top of the screen, or hold ⌘ and press the left or right Arrow keys on your keyboard. This will flip the simulator around.
Now, everything will look as it should.
Notice that in landscape orientation the app no longer shows the iPhone’s status bar. This gives apps more room for their user interfaces.
To finalize the orientation switch, you should do one more thing. There is a configuration option that tells iOS what orientations your app supports. New apps that you make from a template always support both portrait and landscape orientations.
➤ Click the blue Bullseye project icon at the top of the Project navigator. The editor pane of the Xcode window now reveals a bunch of settings for the project.
➤ Make sure that the General tab is selected:
In the Deployment Info section, there is an option for Device Orientation.
➤ Check only the Landscape Left and Landscape Right options and leave the Portrait and Upside Down options unchecked.
Run the app again and it properly launches in the landscape orientation right from the start.
Understanding objects, data and methods
Time for some programming theory. No, you can’t escape it.
Swift is a so-called “object-oriented” programming language, which means that most of the stuff you do involves objects of some kind. I already mentioned a few times that an app consists of objects that send messages to each other.
When you write an iOS app, you’ll be using objects that are provided for you by the system, such as the UIButton
object from UIKit, and you’ll be making objects of your own, such as view controllers.
Objects
So what exactly is an object? Think of an object as a building block of your program. Programmers like to group related functionality into objects. This object takes care of parsing a file, that object knows how to draw an image on the screen, and that object over there can perform a difficult calculation.
Each object takes care of a specific part of the program. In a full-blown app, you will have many different types of objects (tens or even hundreds).
Even your small starter app already contains several different objects. The one you have spent the most time with so far is ViewController
. The Hit Me! button is also an object, as is the alert pop-up. And the text values that you put on the alert — “Hello, World” and “This is my first app!” — are also objects.
The project also has an object named AppDelegate
— you’re going to ignore that for the moment, but feel free to look at its source if you’re curious. These object thingies are everywhere!
Data and methods
An object can have both data and functionality:
-
An example of data is the Hit Me! button that you added to the view controller earlier. When you dragged the button into the storyboard, it actually became part of the view controller’s data. Data contains something. In this case, the view controller contains the button.
-
An example of functionality is the
showAlert
action that you added to respond to taps on the button. Functionality does something.
The button itself also has data and functionality. Examples of button data are the text and color of its label, its position on the screen, its width and height and so on. The button also has functionality: It can recognize that the user tapped on it and it will trigger an action in response.
The thing that provides functionality to an object is commonly called a method. Other programming languages may call this a “procedure” or “subroutine” or “function.” You will also see the term function used in Swift; a method is simply a function that belongs to an object.
Your showAlert
action is an example of a method. You can tell it’s a method because the line says func
(short for “function”) and the name is followed by parentheses:
If you look through the rest of ViewController.swift, you’ll see another method, viewDidLoad()
. It currently doesn’t do much; the Xcode template placed it there for your convenience. It’s a method that’s often used by view controllers, so it’s likely that you will need to add some code to it at some point.
Note: These additional methods added by an Xcode template are known as “boilerplate code.” If you don’t need to add functionality to these boilerplate methods, feel free to remove them — it’ll make your code cleaner and more compact.
There’s a caveat though; sometimes, the boilerplate code is needed in order not to get a compiler error. You will see this later on when we start using more complex view controllers. So if you remove the boilerplate code and get a compiler error, restore the code and try removing the code selectively until you figure out what is needed and what is not.
The concept of methods may still feel a little weird, so here’s an example:
You (or at least an object named “You”) want to throw a party, but you forgot to buy ice cream. Fortunately, you have invited the object named Steve who happens to live next door to a convenience store. It won’t be much of a party without ice cream so, at some point during your party preparations, you send object Steve a message asking him to bring some ice cream.
The computer now switches to object Steve and executes the commands from his buyIceCream()
method, one by one, from top to bottom.
When the buyIceCream()
method is done, the computer returns to your throwParty()
method and continues with that, so you and your friends can eat the ice cream that Steve brought back with him.
The Steve object also has data. Before he goes to the store, he has money. At the store, he exchanges this money data for other, much more important, data: ice cream! After making that transaction, he brings the ice cream data over to the party (if he eats it all along the way, your program has a bug).
Messages
“Sending a message” sounds more involved than it really is.
It’s a good way to think conceptually of how objects communicate, but there really aren’t any pigeons or mailmen involved. The computer simply jumps from the throwParty()
method to the buyIceCream()
method and back again.
Often the terms “calling a method” or “invoking a method” are used instead. That means the exact same thing as sending a message: The computer jumps to the method you’re calling and returns to where it left off when that method is done.
The important thing to remember is that objects have methods (the steps involved in buying ice cream) and data (the actual ice cream and the money to buy it with).
Objects can look at each other’s data (to some extent anyway, just like Steve may not approve if you peek inside his wallet) and can ask other objects to perform their methods.
That’s how you get your app to do things. But not all data from an object can be inspected by other objects and/or code — this is an area known as access control and you’ll learn about this later.
Adding the other controls
Your app already has a button, but you still need to add the rest of the UI controls, also known as “views.” Here is the screen again, this time annotated with the different types of views:
As you can see, you’ll add placeholder values in some of the labels (for example, “999999”). That makes it easier to see how the labels will fit on the screen when they’re actually used. The score label could potentially hold a large value, so you’d better make sure the label has room for it.
➤ Try to re-create the above screen on your own by dragging the various controls from the Object Library onto your scene. You’ll need a few new Buttons, Labels and a Slider. You can see in the screenshot above how big the items should (roughly) be. It’s OK if you’re a few points off.
Note: It might seem a little annoying to use the Library panel since it goes away as soon as you drag an item from it. You then have to tap the icon on the toolbar to show the Library panel again to select another item. If you are placing multiple components, just hold down the Alt/Option key (⌥) as you drag an item from the Library panel — the Library panel will remain open, allowing you to select another item.
To tweak the settings of these views, you use the Attributes inspector. You can find this inspector in the right-hand pane of the Xcode window:
The inspector area shows various aspects of the item that is currently selected. The Attributes inspector, for example, lets you change the background color of a label or the size of the text on a button. You’ve already seen the Connections inspector that showed the button’s actions. As you become more proficient with Interface Builder, you’ll be using all of these inspector panes to configure your views.
➤ Hint: The ⓘ button is actually a regular button, but its Type is set to Info Light in the Attributes inspector:
➤ Also use the Attributes inspector to configure the slider. Its minimum value should be 1, its maximum 100, and its current value 50.
When you’re done, you should have 12 user interface elements in your scene: one slider, three buttons and a whole bunch of labels. Excellent!
➤ Run the app and play with it for a minute. The controls don’t really do much yet (except for the button that should still pop up the alert), but you can at least drag the slider around.
You can now tick a few more items off the to-do list, all without much programming! That is going to change really soon, because you will have to write Swift code to actually make the controls do something.
The slider
The next item on your to-do list is: “Read the value of the slider after the user presses the Hit Me! button.”
If, in your messing around in Interface Builder, you did not accidentally disconnect the button from the showAlert
action, you can modify the app to show the slider’s value in the alert pop-up. (If you did disconnect the button, then you should hook it up again first. You know how, right?)
Remember how you added an action to the view controller in order to recognize when the user tapped the button? You can do the same thing for the slider. This new action will be performed whenever the user drags the slider.
The steps for adding this action are largely the same as before.
➤ First, go to ViewController.swift and add the following at the bottom, just before the final closing curly bracket:
@IBAction func sliderMoved(_ slider: UISlider) {
print("The value of the slider is now: \(slider.value)")
}
➤ Second, go to the storyboard and Control-drag from the slider to View controller in the Document Outline. Let go of the mouse button and select sliderMoved: from the pop-up. Done!
Just to refresh your memory, the Document Outline sits on the left-hand side of the Interface Builder canvas. It shows the View hierarchy of the storyboard. Here, you can see that the View controller contains a view (succinctly named View), which, in turn, contains the sub-views you’ve added: the buttons and labels.
Remember, if the Document Outline is not visible, click the little icon at the bottom of the Xcode window to reveal it:
When you connect the slider, make sure to Control-drag to View controller (the yellow circle icon), not View controller Scene at the very top. If you don’t see the yellow circle icon, then click the arrow in front of View controller scene (called the “disclosure triangle”) to expand it.
If all went well, the sliderMoved:
action is now hooked up to the slider’s Value Changed event. This means the sliderMoved()
method will be called every time the user drags the slider to the left or right, changing its value.
You can verify that the connection was made by selecting the slider and looking at the Connections inspector:
Note: Did you notice that the
sliderMoved:
action has a colon in its name butshowAlert
does not? That’s because thesliderMoved()
method takes a single parameter,slider
, whileshowAlert()
does not have any parameters. If an action method has a parameter, Interface Builder adds a:
to the name. You’ll learn more about parameters and how to use them soon.
➤ Run the app and drag the slider.
As soon as you start dragging, the Xcode window should open a new pane at the bottom, the Debug area, showing a list of messages:
Note: If, for some reason, the Debug area does not show up, you can always show (or hide) the Debug area by using the appropriate toolbar button on the top right corner of the Xcode window. You will notice from the above screenshot that the Debug area is split into two panes. You can control which of the panes is shown/hidden by using the two blue square icons shown above in the bottom-right corner.
If you swipe the slider all the way to the left, you should see the value go down to 1. All the way to the right, the value should stop at 100.
The print()
function is a great way to show you what is going on in the app. Its entire purpose is to write a text message to the Console — the right-hand pane in the Debug area. Here, you used print()
to verify that you properly hooked up the action to the slider and that you can read the slider value as the slider is moved.
Developers often use print()
to make sure their apps are doing the right thing before they add more functionality. Printing a message to the Console is quick and easy.
Note: You may see a bunch of other messages in the Console, too. This is debug output from UIKit and iOS Simulator. You can safely ignore these messages.
Creating a variable and functions
Printing information with print()
to the Console is very useful during the development process, but it’s absolutely useless to users because they can’t see the Console when the app is running on a device.
You’re going to improve this by showing the value of the slider in the alert pop-up. So how do you get the slider’s value?
When you read the slider’s value in sliderMoved(), that piece of data disappears when the action method ends. It would be handy if you could remember this value until the user taps the Hit Me! button.
➤ Open ViewController.swift and add the following at the top, directly below the line that says class ViewController
:
var currentValue: Int = 0
➤ Change the contents of the sliderMoved()
method in ViewController.swift to the following:
@IBAction func sliderMoved(_ slider: UISlider) {
currentValue = lroundf(slider.value)
}
You removed the print()
statement and replaced it with this line:
currentValue = lroundf(slider.value)
➤ Now change the showAlert()
method to the following:
@IBAction func showAlert() {
let message = "The value of the slider is: \(currentValue)"
let alert = UIAlertController(title: "Hello, World",
message: message, // changed
preferredStyle: .alert)
let action = UIAlertAction(title: "OK", // changed
style: .default,
handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
As before, you create and show a UIAlertController
, except this time its message says: “The value of the slider is: X,” where X is replaced by the contents of the currentValue
variable (a whole number between 1 and 100).
Suppose currentValue
is 34, which means the slider is about one-third to the left. The new code above will convert the string "The value of the slider is: \(currentValue)"
into "The value of the slider is: 34"
and put that into a new object named message
.
The old print()
did something similar, except that it printed the result to the Console. Here, however, you do not wish to print the result but show it in the alert pop-up. That is why you tell the UIAlertController
that it should now use this new string as the message to display.
➤ Run the app, drag the slider and press the button. Now, the alert should show the actual value of the slider.
Cool. You have used a variable, currentValue
, to remember a particular piece of data, the rounded-off position of the slider, so that it can be used elsewhere in the app — in this case in the alert’s message text.
If you tap the button again without moving the slider, the alert will still show the same value. The variable keeps its value until you put a new one into it.
Your first bug
There is a small problem with the app, though. Maybe you’ve noticed it already. Here is how to reproduce the problem:
➤ Press the Stop button in Xcode to completely terminate the app, then press Run again. Without moving the slider, immediately press the Hit Me! button.
The alert now says: “The value of the slider is: 0”. But the slider is obviously at the center, so you would expect the value to be 50. You’ve discovered a bug!
Exercise: Think of a reason why the value would be 0 in this particular situation (start the app, don’t move the slider, press the button).
Answer: The clue here is that this only happens when you don’t move the slider. Of course, without moving the slider the sliderMoved()
message is never sent and you never put the slider’s value into the currentValue
variable.
The default value for the currentValue
variable is 0, and that is what you are seeing here.
➤ To fix this bug, change the declaration of currentValue
to:
var currentValue: Int = 50
Now the starting value of currentValue
is 50, which should be the same value as the slider’s initial position.
➤ Run the app again and verify that the bug is fixed.
You can find the project files for the app up to this point under 03-Slider and Labels in the Source Code folder.