Building My First iOS Game
Sunday night two weeks ago, I had nothing better to do. So I started googling for some iOS tutorial and landed on this amazing video tutorial by Ray Wenderlich. It’s quite exciting and easy to follow for a total beginner like me. I delved into Swift documentation before and got bored easily. 😝
On that tutorial, you’ll learn the basic of iOS development by building some simple game named Bull’s Eye. It generates some random number between 1 and 100. It then challenges the user to estimate and move the slider as close as possible to that random number. The closer it gets, the higher the point. It’s pretty simple.
You can check out the complete project on my Github repository: risan/bullseye-ios.
Here are a few things that I learned and gathered so far about Swift programming language. Note that I use the latest Swift 4.2 version.
Table of Contents
Variables, Constants, and Data Types
Basic Data Types
There are several basic data types provided by Swift, here are some of the types that I’ve used so far:
Int
: For a positive or negative integer number (e.g.100
,0
, or-12
);Float
: For a decimal number (e.g.3.14
or-0.123
);Double
: Double precision floating point number—a lot more precise than theFloat
;Bool
: For boolean value (true
orfalse
);Character
: For a single character (e.g.A
,b
, or&
); andString
: Collection of a character (e.g.Hello World
).
Working with Integer
Swift also provides several other data types for handling an integer number:
UInt
—the unsigned version ofInt
(zero and positve number only)Int8
andUInt8
—the 8 bit version;Int16
andUInt16
—the 16 bit version;Int32
andUInt32
—the 32 bit version; andInt64
andUInt64
—the 64 bit version.
Unless you know about what you’re doing, always use the Int
data type when dealing with an integer number.
Working with Decimal
There are also several other data types for handling a decimal number:
Float32
—the 32 bit version of floating point type;Float64
—the 64 bit version; andFloat80
—the extended precision floating point type.
However, the Swift documentation recommends us to always use the Double
data type when dealing with a decimal number.
Declaring a Variable
Use the var
keyword to declare a variable:
var name: String = "John Doe"
var age: Int = 23
var score: Double = 75.34
var isAwesome: Bool = true
var grade: Character = "A"
Since it’s a variable, we are allowed to change the value—as long as it has the same data type.
var name: String = "John Doe"
name = "Foo Bar"
// Will throw an error, because it's an integer not a string.
name = 12
// We have to explicitly convert the new value into the same data type.
name = String(12)
Declaring a Constant
To declare a constant, we use the let
keyword:
let max: Int = 100
let pi: Double = 3.14
// Will throw an error, since we're not allowed to change the value.
pi = 3.1423
Type Inference
Swift is a strongly typed language. However, it allows us to declare a variable or a constant without even specifying its data type. Swift is smart enough to figure out the data type based on its initial value.
var totalItems = 5 // Int
var totalAmounts = 50.3 // Double
let message = "Success!" // String
let isPaid = true // Bool
let grade = "A" // String
Note that by default, a decimal number will be inferred as a Double
—not a Float
. A round number also will be inferred as an Int
, even if the value may also fit in another data type like UInt8
.
String Extrapolation
In Swift, we can put the value of a variable or a constant within a string like this:
"Some string \(yourVariableOrConstant)"
When we extrapolate a variable or a constant within a string, we don’t need to explicitly convert the data type—even if it’s not a string.
let totalItems = 5
let totalAmounts = 50.3
let isPaid = true
let message = "Success!"
print("Total items: \(totalItems)\nTotal amount: $\(totalAmounts)")
print("Is Paid: \(isPaid)\nMessage: \(message)")
The above code will print the following output:
Total items: 5
Total amount: $50.3
Is Paid: true
Message: Success!
We can also use the triple quotes ("""
) to construct a multiline string. The following code will have the same exact output like above:
print("""
Total items: \(totalItems)
Total amount: $\(totalAmounts)
Is Paid: \(isPaid)
Message: \(message)
""")
Functions
Use the func
keyword to create a function:
func sayHello() {
print("Hello World!")
}
// Calling the function.
sayHello()
Function with Parameter
We can also create a function with a parameter:
func greet(name: String) {
print("Hello \(name)!")
}
greet(name: "Mr Data") // Hello Mr Data!
greet(name: "Darth Vader") // Hello Darth Vader!
greet(name: 123.45) // Will throw an error, since we pass a double
Note how we also pass the name
when calling the greet
function above. In Swift, the name
is also called “argument label”.
We can have as many parameters as we want:
func introduce(name: String, age: Int) {
print("Hi my name is \(name) and I'm \(age) years old.")
}
introduce(name: "John", age: 23)
// Will throw an error, since we can't change the argument's order.
introduce(age: 19, name: "Luke")
Note that even though the arguments are already labeled, we can’t change the order of these arguments.
Omitting Argument Label in a Function
We can configure the function so we don’t have to specify the argument label when calling it. To omit the argument label, simply put an underscore (_
) before the parameter name:
func greet(_ name: String) {
print("Hi \(name)!")
}
greet("Captain Kirk") // Hi Captain Kirk!
// Will throw an error, we have to omit the argument label.
greet(name: "John")
We can combine a named parameter with the omitted one:
func introduce(_ name: String, age: Int) {
print("Hi my name is \(name) and I'm \(age) years old.")
}
introduce("John", age: 23) // Hi my name is John and I'm 23 years old.
And of course we can also have multiple parameters without any argument labels:
func introduce(_ name: String, _ age: Int) {
print("Hi my name is \(name) and I'm \(age) years old.")
}
introduce("Jar Jar", 33) // Hi my name is Jar Jar and I'm 33 years old.
Renaming the Function Parameter
In Swift, we are allowed to have a different identifier between the parameter name and the argument label.
func welcome(to name: String) {
print("Welcome to the \(name)!")
}
welcome(to: "Jungle") // Welcome to the Jungle!
The to
will be used as an argument label when calling the welcome
function. While the name
will be used as an identifier for the parameter within the function’s body.
Function with Return Value
So far we’ve only created functions with no return value. To create a function with a return value, simply use the return
keyword and declare the return type right before the opening curly brace:
func sum(_ a: Int, _ b: Int) -> Int {
return a + b
}
sum(10, 20) // 30
sum(10, -20) // -10
Optional
“Optional” was one of the concepts that I was struggling with when trying out Swift for the first time years ago. Now I finally understand. It’s simply a data type that accepts nil
as a value—which means an absence of value. We use a question mark (?
) to indicate an optional type:
var name: String? // Optional String type
var message: String = "John" // Just regular String type
name = "John" // We can assign a regular String value
name = nil // or we can also assign a `nil`
// However this will throw an error, since message is not an optional type.
message = nil
Optional Binding
We can think of optional type as a box. It can either contain something (some value) or nothing at all (a nil
value). In order to know what’s inside of this box, we have to open it. In Swift, this process is called “unwrapping”—like unwrapping present box!
One of the way to “unwrap” an optional type is by using an optional binding. We assign the optional type to some constant using the let
keyword; we then wrap this assignment with an if
statement like so:
var name: String?
if let unwrappedName = name {
// This block will be executed if name is not nil
print("Hi \(unwrappedName)!")
} else {
// This block will be executed if name is nil
print("Name is empty")
}
Class
Use the class
keyword to define a class:
class Person {
let isHuman = true
var name = "John Doe"
func sayHi() {
print("Hi, my name is \(name)!")
}
}
let john = Person()
john.isHuman // true
john.name // John Doe
john.sayHi() // Hi, my name is John Doe!
john.name = "Yoda"
john.sayHi() // Hi, my name is Yoda!
// Will throw an error because `isHuman` is a constant.
john.isHuman = false
Just like calling a function, we use Person()
to initialize a new object of Person
class. We can then use the dot (.
) to access the object’s properties and methods.
Class Initializer
The class can also have an initializer or a constructor. This is a good place for us to set up the class’s instance; like assigning some initial values to the properties:
class Person {
var name: String
var age:Int?
// The class initializer
init(name: String, age: Int?) {
self.name = name
self.age = age
}
func sayHi() {
var message = "Hi, my name is \(name)"
if let ageValue = age {
message += " and I'm \(ageValue) years old"
}
print(message)
}
}
let anakin = Person(name: "Anakin", age: 20)
let luke = Person(name: "Luke", age: nil)
anakin.sayHi() // Hi, my name is Anakin and I'm 20 years old
luke.sayHi() // Hi, my name is Luke
Note that when the parameter in a method has the same name as one of the class’s property, we can use the self
keyword to refer to the class’s property.
In Swift, we are allowed to declare a function or a method with the same name, as long as it has different parameter signature. And so does the class initializer, we can have several init()
with different parameter signature:
class Person {
var name: String
var age:Int?
// The age parameter is not optional now.
init(name: String, age: Int) {
self.name = name
self.age = age
}
// Another init but without age parameter.
init(name: String) {
self.name = name
}
func sayHi() {
var message = "Hi, my name is \(name)"
if let ageValue = age {
message += " and I'm \(ageValue) years old"
}
print(message)
}
}
let anakin = Person(name: "Anakin", age: 20)
let luke = Person(name: "Luke")
anakin.sayHi() // Hi, my name is Anakin and I'm 20 years old
luke.sayHi() // Hi, my name is Luke
Other Discoveries
Here are some other discoveries I’ve made throughout the tutorial:
- We don’t need to use a semicolon (
;
) at the end of each statement, unless we want to have multiple statements within one line. - Every UI object dropped to the storyboard from the library, is an object from some class.
- Use the
@IBOutlet
keyword on the class’s property if we want it to refer to some object on the storyboard. - Use the
@IBAction
keyword on the class’s method if want it to be executed when a particular event occurred on some object on the storyboard.
Force Unwrapping
@IBOutlet weak var slider: UISlider!
The exclamation mark (!
) at the end of the above code is used for unwrapping the optional type forcefully. It means that if the slider
is nil
the code execution will be aborted.
Class Inheritance
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
In the code above, we declare a class named ViewController
that inherits from the UIViewController
class. In Swift, we are allowed to override the parent’s method. To do so, we have to use the override
keyword before the new method declaration. We can also use the super
keyword to access parent’s original methods or properties.
Known Type Parameter
Below is the signature of the setThumbImage
method from a UISlider
class:
func setThumbImage(_ image: UIImage?, for state: UIControl.State)
Suppose we have a variable named slider
which is an instance of the UISlider
class. We can call the above method like this:
slider.setThumbImage(someUIImageInstance, for: UIControl.State.normal)
UIControl.State
is a struct that contains several constants describing the state of a control. And since the type of the for
argument is already known to be the UIControl.State
, we can actually just use a dot (.
) to access the struct’s constant:
slider.setThumbImage(someUIImageInstance, for: .normal)
Using a Closure
UIAlertAction(title: "OK", style: .default, handler: {
action in
self.startNewRound()
})
The code above is taken from the tutorial’s source code. We declare a closure as an argument for the handler
parameter. The in
keyword is used to separate the closure’s parameter(s) from the body. In the code above we only have one parameter: action
—which is an instance of the newly created UIAlertAction
class.
Displaying Local HTML File on a WebView
Here’s the code that we use to display a locally saved HTML file within the WebKit WebView:
// Load the local HTML file named "about.html"
if let htmlPath = Bundle.main.path(forResource: "about", ofType: "html") {
let htmlUrl = URL(fileURLWithPath: htmlPath)
let htmlUrlRequest = URLRequest(url: htmlUrl)
webView.load(htmlUrlRequest)
}
Note that the webView
identifier above refers to the WKWebView
object on the storyboard.
Closing Thought
I must say, so far I enjoy creating an iOS application using Swift. Swift syntax is cleaner and more concise than its predecessor—Objective C. For me it’s also a lot easier to understand.
This little quest of mine, leading me to find more tutorials about building iOS development with Swift. So far I’ve done another 4 tutorials:
3. Creating a paginated sliding cards
I probably should start reading the Swift documentation. 🤔