iOS: Asset Catalogs, UIButton, CALayer & More…
In case if you haven’t yet, join my Discord server! I’m looking to create a community of developers so we can reinforce, motivate, and get to know each other in our programming journey.
If you have any questions or comments, please don’t feel afraid to ask or connect with me on social media! You can also send me an email at email@example.com!
Without a further ado, let’s get started!
In this article, I will review the following concepts:
- Interface Builder
- Auto Layout
And I will cover the following new topics:
- @2x and @3x Images
- Asset Catalogs
- Random Numbers
Designing Your Layout
To add a navigation controller to ViewController, you can go to the Xcode menu → Editor > Embed In > Navigation Controller.
Within Interface Builder, you can adjust the size and position of any views (i.e. UIButtons) dragged from the Object Library using the Size Inspector, accessible from the Xcode menu → View > Inspectors > Size.
To create multiple constraints, there are 2 approaches:
- In the image above, the pop-up was displayed because we Ctrl + drag from the top UIButton to its white space above and releasing. Traditionally, we would add another constraint by repeating the same process. Or…
- We can hold down the shift key before selecting an item in the menu, which will allow you to select more than 1 constraint in the pop-up.
Asset catalogs are highly optimized ways of importing and using images in iOS projects. The default Xcode asset catalog is called Assets.xcassets.
iOS assets come in the sizes 2x and 3x, which are 2 and 3 times the size of the layout you created in Interface Builder. iOS would load the correct one so if you write hello.png in your code, iOS would know to look for and load firstname.lastname@example.org on retina devices. And Xcode knows to automatically place images into 2x and 3x buckets if they are named correctly. email@example.com is for retina devices and firstname.lastname@example.org is for retina HD devices.
Early iOS devices had non-retina screens (a screen resolution of 320 x 480 pixels) and you could place things exactly where you wanted them. Soon, Apple introduced retina screens with the iPhone 4 that had double the number of pixels as previous screens. With the introduction of retina screens, Apple automatically switched sizes from pixels to “points” — virtual pixels, which made everything look the same size and shape on both devices with a single layout.
Apple eventually introduced retina HD screens that have a 3x resolution. The App Store uses a technology called App Thinning that automatically delivers only the content each device is capable of showing — it strips out the other assets when the app is being downloaded so there is no wasted space.
As of iOS 10, no non-retina devices are being supported so if your app only supports iOS 10 or later devices, you need to only include the @2x and @3x images.
We Ctrl + drag from the middle button to the top button then select Vertical Spacing and Center Horizontally and repeat the same process from the bottom button to the middle button.
To tell Interface Builder to update all the frames of your buttons to match the Auto Layout rules you just created, you can do so by going to the Xcode menu → Editor > Resolve Auto Layout Issues > Update Frames. This command will update the frames (the positions and sizes) of each image view so that it matches the Auto Layout constraints we set.
As soon as you set a picture, our constraints for the UIButton at the bottom are complete for the following reasons:
- It has a Y position because we placed a constraint relative to the 2nd UIButton.
- It has an X position because we’re centering it horizontally.
- It has a width and height because it’s reading it from the image we assigned.
It is very convenient to create an IBOutlet utilizing your Assistant Editor. You can Ctrl + drag from your element from the Interface Builder onto traditionally the top of your ViewController class right beneath the class declaration.
UIButton and CALayer
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.
button1.setImage() assigns a UIImage to the button. The .setImage() method takes a second parameter that describes which state of the button should be changed, and .normal indicates the standard state of the button.
The .normal parameter looks like an enum, but it’s actually a static property of a struct called UIControlState. Furthermore, in Objective-C (the language UIKit was written in), the .normal parameter is an enum, but in Swift, it gets mapped to a struct that just happens to be used like an enum.
CALayer is a Core Animation data type responsible for managing the way your view looks. Conceptually, CALayer sits beneath all your UIViews.
button1.layer.borderWidth = 1
Our border will be 1 pixel on non-retina devices, 2 pixels on retina devices, and 3 pixels on retina HD devices.
button1.layer.borderColor = UIColor.lightGray.cgColor
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. Therefore, CALayer has its own way of setting colors called CGColor, which derives from Apple’s Core Graphics framework. UIColor (sitting above CGColor) is able to convert to and from CGColor easily.
button3.layer.borderColor = UIColor(red:, green:, blue:).cgColor
You can initialize a UIColor by setting its RGBA, where you specify 4 values (red, green, blue and alpha), each of which should range from 0 (none of that color) to 1.0 (all of that color).
Swift has built-in methods for shuffling arrays like shuffle() and shuffled(). The shuffle() method performs in-place shuffling, whereas the shuffled() method returns a new, shuffled array.
correctAnswer = Int.random(in: 0…2)
All Swift’s numeric types such as Int, Double, and CGFloat have a random(in:) method that generates a random number in a range.
title = countries[correctAnswer].uppercased()
The uppercased() method of any string uppercases the entire string.
Create an IBAction
To create an IBAction, select the view (in this case, UIButton) and Ctrl + drag it as you would to create an IBOutlet except drag it to the space in your code before the end of the class ViewController and let go when you see Insert Outlet, Action, or Outlet Collection.
Make sure that the Connection is set to Action instead of Outlet. If you want to connect multiple views to the same IBAction, then you can simply select the other views then Ctrl + drag them onto the IBAction. The whole method will turn blue signifying that it’s going to be connected, so you can just let go to make it happen. Also, if the method flashes after you let go, it means the connection was made.
This event means that the user has touched the view (in this case, UIButton) then released their finger while he or she was still over it. In other words, the button was tapped.
@IBOutlet is a way of connecting code to storyboard layouts whereas @IBAction is a way of making storyboard layouts trigger code.
This method takes 1 parameter called sender (in this case, of type UIButton) because we know that’s what will be calling the method.
Behind the scenes, all views have a special identifying number that we can set called its Tag.
let ac = UIAlertController(title: title, message: “Your score is \(score)”, preferredStyle: .alert)
A UIAlertController() is a data type that is used to show an alert with options to the user. It provides us with 2 kinds of preferred styles:
.alert pops up a message box over the center of the screen, and this is Apple’s recommendation when telling users about a situation change.
.actionSheet slides options up from the bottom, and this is Apple’s recommendation when asking users to choose from a set of options.
A closure is a special kind of code block that can be used like a variable. Behind the scene, Swift takes a copy of the block of code so that it can be called later and copies anything referenced inside the code.
ac.addAction(UIAlertAction(title: “Continue”, style: .default, handler: askQuestion))
A UIAlertAction() is a data type that allows you to add a button to the alert that says “Continue” and gives it the style “default”. There are 3 possible styles:
The handler parameter is looking for a closure, which is some code that it can execute when the button is tapped. You can write custom code in there if you want.
Warning: We must use askQuestion and not askQuestion(). Using askQuestion provides the handler with the name of the method to run, whereas using askQuestion() tells the handler to run the askQuestion() method because it will tell you the name of the method to run.
present(ac, animated: true)
present() takes 3 parameters:
- A view controller to present
- Whether to animate the presentation
- (Optional) A closure that should be executed when the presentation animation has finished
“Cannot convert value of type ‘() -> ()’ to expected argument type ‘((UIAlertAction) -> Void)?’.”
The following error message is an example of Swift being very confusing to what is causing the error. In this case, the error message is resulted because although we are adding a method for this closure in the handler parameter in the UIAlertAction, Swift wants the method itself to accept a UIAlertAction parameter saying which UIAlertAction was tapped.
Therefore, func askQuestion() needs to be changed to func askQuestion(action: UIAlertAction! = nil) or askQuestion(action: UIAlertAction!). The former doesn’t require the invocation of askQuestion() to be passed a parameter because we set a default value of nil which is what the “= nil” does, whereas the latter does need to be passed a parameter (and can be passed nil since we are also force unwrapping the UIAlertAction).