Making the basic game work: UIButton and CALayer
Making the basic game work: UIButton and CALayer êŽë š
We're going to create an array of strings that will hold all the countries that will be used for our game, and at the same time we're going to create another property that will hold the player's current score â it's a game, after all!
Let's start with the new properties. Add these two lines directly beneath the @IBOutlet
lines you added earlier in ViewController.swift
:
var countries = [String]()
var score = 0
The first line is something you saw in project 1: it creates a new property called countries
that will hold a new array of strings. The second one creates a new property called score
that is set to 0. As youâve seen previously, Swiftâs type inference works wonders here â it figures out what data type a variable or constant should be based on what we put into it.
We're going to be putting all this into practice over the next few minutes. First, let's fill our countries array with the flags we have, so add this code inside the viewDidLoad()
method:
countries.append("estonia")
countries.append("france")
countries.append("germany")
countries.append("ireland")
countries.append("italy")
countries.append("monaco")
countries.append("nigeria")
countries.append("poland")
countries.append("russia")
countries.append("spain")
countries.append("uk")
countries.append("us")
This is identical to the code you saw in project 1, so there's nothing to learn here. If you recall your Swift introduction, youâll know there's a more efficient way of doing this, which is to create it all on one line. To do that, you would write:
countries += ["estonia", "france", "germany", "ireland", "italy", "monaco", "nigeria", "poland", "russia", "spain", "uk", "us"]
The next step is to write a method that shows three random flag images on the screen. Buttons have a setImage()
method that lets us control what picture is shown inside and when, so we can use that with UIImage
to display our flags.
Add this new method underneath viewDidLoad()
:
func askQuestion() {
button1.setImage(UIImage(named: countries[0]), for: .normal)
button2.setImage(UIImage(named: countries[1]), for: .normal)
button3.setImage(UIImage(named: countries[2]), for: .normal)
}
The first line is easy enough: we're declaring a new method called askQuestion()
, and it takes no parameters. The next three use UIImage(named:)
and read from an array by position, both of which we used in project 1, so that bit isn't new either. However, the rest of those lines is new, and shows off two things:
button1.setImage()
assigns aUIImage
to the button. We have the US flag in there right now, but this will change it whenaskQuestion()
is called.for: .normal
ThesetImage()
method takes a second parameter that describes which state of the button should be changed. We're specifying.normal
, which means "the standard state of the button."
That .normal
parameter looks like an enum, but itâs actually a static property of a struct called UIControlState
. In Objective-C â the language UIKit was written in â itâs an enum, but in Swift it gets mapped to a struct that just happens to be used like an enum, so if you want to be technically correct itâs not a true enum in Swift. At this point in your Swift career there is no difference, but letâs face it: âtechnically correctâ is the best kind of correct.
Now that we have the countries all set up and a method that displays flags, all we need to do is add one more line just before the end of viewDidLoad()
to make it all spring to life:
askQuestion()
At this point the game is in a fit state to run, so letâs give it a try.
First, select the iPhone XR simulator by going to the Product menu and choosing Destination > iPhone XR. Now press Cmd+R now to launch the Simulator and give it a try.
You'll immediately notice two problems
- We're showing the Estonian and French flags, both of which have white in them so it's hard to tell whether they are flags or just blocks of color
- The "game" isn't much fun, because it's always the same three flags!
The second problem is going to have to wait a few minutes, but we can fix the first problem now. One of the many powerful things about views in iOS is that they are backed by what's called a CALayer
, which is a Core Animation data type responsible for managing the way your view looks.
Conceptually, CALayer
sits beneath all your UIView
s (that's the parent of UIButton
, UITableView
, and so on), so it's like an exposed underbelly giving you lots of options for modifying the appearance of views, as long as you don't mind dealing with a little more complexity. We're going to use one of these appearance options now: borderWidth
.
The Estonian flag has a white stripe at the bottom, and because our view controller has a white background that whole stripe is invisible. We can fix that by giving the layer of our buttons a borderWidth
of 1, which will draw a one point black line around them. Put these three lines in viewDidLoad()
directly before it calls askQuestion()
:
button1.layer.borderWidth = 1
button2.layer.borderWidth = 1
button3.layer.borderWidth = 1
Remember how points and pixels are different things? In this case, our border will be 1 pixel on non-retina devices, 2 pixels on retina devices, and 3 on retina HD devices. Thanks to the automatic point-to-pixel multiplication, this border will visually appear to have more or less the same thickness on all devices.
By default, the border of CALayer
is black, but you can change that if you want by using the UIColor
data type. I said that CALayer
brings with it a little more complexity, and here's where it starts to be visible: CALayer
sits at a lower technical level than UIButton
, which means it doesn't understand what a UIColor
is. UIButton
knows what a UIColor
is because they are both at the same technical level, but CALayer
is below UIButton
, so UIColor
is a mystery.
Don't despair, though: CALayer
has its own way of setting colors called CGColor
, which comes from Apple's Core Graphics framework. This, like CALayer
, is at a lower level than UIButton
, so the two can talk happily â again, as long as you're happy with the extra complexity.
Even better, UIColor
(which sits above CGColor
) is able to convert to and from CGColor
easily, which means you don't need to worry about the complexity â hurray!
So, let's put all that together into some code that changes the border color using UIColor
and CGColor
together. Put these three just below the three borderWidth
lines in viewDidLoad()
:
button1.layer.borderColor = UIColor.lightGray.cgColor
button2.layer.borderColor = UIColor.lightGray.cgColor
button3.layer.borderColor = UIColor.lightGray.cgColor
As you can see, UIColor
has a property called lightGray
that returns (shock!) a UIColor
instance that represents a light gray color. But we can't put a UIColor
into the borderColor
property because it belongs to a CALayer
, which doesn't understand what a UIColor
is. So, we add .cgColor
to the end of the UIColor
to have it automagically converted to a CGColor
. Perfect.
If lightGray
doesn't interest you, you can create your own color like this:
UIColor(red: 1.0, green: 0.6, blue: 0.2, alpha: 1.0).cgColor
You need to specify four values: red, green, blue and alpha, each of which should range from 0 (none of that color) to 1.0 (all of that color). The code above generates an orange color, then converts it to a CGColor
so it can be assigned to a CALayer
's borderColor
property.
That's enough with the styling, I think â if you run the app now it should look better.