## iOS Apprentice

Eighth Edition · iOS 13 · Swift 5.2 · Xcode 11

# 4. Swift Basics Written by Joey deVilla

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

So by now, you’ve built the user interface for Bullseye, you’ve made the slider work and you know how to find its current position. That already knocks quite a few items off your to-do list. In this chapter, you’ll take care of a few more items on that list. Here’s what this chapter will cover:

• Generating and displaying the target value: Select the random number that the player will try to match using the slider and display it onscreen.
• Calculating the points scored: Determine how many points to award to the player based on how close they came to positioning the slider at the target value.
• Writing methods: You’ve used some built-in methods so far, but built-in methods can’t cover everything. It’s time to write your own!
• Improving the code: Make the code more readable so that it’s easier to maintain and improve and less error-prone.
• Key points: A quick review of what you learned in this chapter.

## Generating and displaying the target value

First, you need to come up with the random number that the user will try to match using the slider. Where can you get a random number for each game’s target value?

### Generating (sort of) random numbers

Random numbers come up a lot when you’re making games because games need to have an element of unpredictability. You can’t get a computer to generate numbers that are truly random and unpredictable, but you can employ a pseudo-random number generator to spit out numbers that at least appear to be random.

### Generating a random target number

Swift’s data types for numbers, which include `Int` and `Double` numeric types, have a method that lets you generate random numbers in a given range.

``````@State var target: Int = Int.random(in: 1...100)
``````
``````@State var alertIsVisible: Bool = false
@State var sliderValue: Double = 50.0
@State var target: Int = Int.random(in: 1...100)
``````

### Displaying the target value

➤ Scroll down to the part of the `body` variable that begins with the comment line `Target row`:

``````// Target row
HStack {
Text("Put the bullseye as close as you can to:")
Text("100")
}
``````

``````Text("100")
``````
``````Text("\(self.target)")
``````

## Calculating and displaying the points scored

Now that you have both the target value and a way to read the slider’s position, as you learned from the previous chapter, you can calculate how many points the player scored.

### How close is the slider to the target?

The closer the slider is to the target when the player presses the Hit me!, the more points they should receive. To calculate the score for each round, you look at how far the slider’s value is from the target:

### Calculating the points scored

The number of points the player receives should depend on the difference between the slider value and the target value:

### Algorithms

In coming up with a way to calculate the score, you’ve come up with an algorithm. That’s a fancy term for a process or series of steps to follow to perform a calculation or solve a problem. This algorithm is very simple, but it’s an algorithm nonetheless.

Back near the start of Chapter 2, you read about the concept of functional decomposition, which is the process of tackling a large project by breaking it into sub-projects, and possibly breaking the sub-projects into even smaller sub-projects until they are manageable.

``````// Button row
Button(action: {
print("Button pressed!")
}) {
Text("Hit me!")
}
message: Text("The slider's value is \(Int(self.sliderValue.rounded()))."),
dismissButton: .default(Text("Awesome!")))
}
``````

### Implementing a basic method for calculating the points to award the player

In building a method to calculate how many points to award to the player, you’re going to use an approach called stepwise refinement. This means starting by building the simplest thing that could possibly work and then refining it over a number of steps until you get the desired result.

``````func pointsForCurrentRound() -> Int {
return 100
}
``````
``````func pointsForCurrentRound() -> Int {
``````
``````return 100
``````

### Calling the method and viewing its result

To see how it works, use `print` to show what `pointsForCurrentRound()` returns.

``````// Button row
Button(action: {
print("Points awarded: \(self.pointsForCurrentRound())")
}) {
Text("Hit me!")
}
message: Text("The slider's value is \(Int(self.sliderValue.rounded()))."),
dismissButton: .default(Text("Awesome!")))
}
``````

### Making `pointsForCurrentRound()` actually calculate points

Now that `pointsForCurrentRound()` returns a value and the alert pop-up displays that value, it’s time to change the value to the actual number of points that the player should receive.

``````func pointsForCurrentRound() -> Int {
var difference: Int
if self.sliderValue.rounded() > self.target {
difference = self.sliderValue.rounded() - self.target
} else if self.target > self.sliderValue.rounded() {
difference = self.target - self.sliderValue.rounded()
} else {
difference = 0
}
return 100 - difference
}
``````

``````func pointsForCurrentRound() -> Int {
var difference: Int
if Int(self.sliderValue.rounded()) > self.target {
difference = Int(self.sliderValue.rounded()) - self.target
} else if self.target > Int(self.sliderValue.rounded()) {
difference = self.target - Int(self.sliderValue.rounded())
} else {
difference = 0
}
return 100 - difference
}
``````
``````var difference: Int
``````
``````if Int(self.sliderValue.rounded()) > self.target {
difference = Int(self.sliderValue.rounded()) - self.target
} else if self.target > Int(self.sliderValue.rounded()) {
difference = self.target - Int(self.sliderValue.rounded())
} else {
difference = 0
}
``````
``````if something is true {
then do this
} else if something else is true {
} else {
do something when neither of the above are true
}
``````
``````if self.sliderValue.rounded() > self.target {
``````
``````  difference = self.sliderValue.rounded() - self.target
``````
``````a = b - c
``````
``````} else if self.target > self.sliderValue.rounded() {
``````
``````  difference = self.target - self.sliderValue.rounded()
``````
``````} else {
difference = 0
}
``````
``````return 100 - difference
``````

### Displaying the points

Now that `pointsForCurrentRound()` properly calculates the points the player earned, it’s time to display them. So next, you’ll make a change to the alert pop-up so that it displays the results of `pointsForCurrentRound()`.

``````// Button row
Button(action: {
print("Points awarded: \(self.pointsForCurrentRound())")
}) {
Text("Hit me!")
}
message: Text("The slider's value is \(Int(self.sliderValue.rounded())).\n" +
"The target value is \(self.target).\n" +
"You scored \(self.pointsForCurrentRound()) points this round."),
dismissButton: .default(Text("Awesome!")))
}
``````
``````message: Text("The slider's value is \(Int(self.sliderValue.rounded())).\n" +
"The target value is \(self.target).\n" +
"You scored \(pointsForCurrentRound()) points this round."),
``````
``````"The slider's value is \(Int(self.sliderValue.rounded())).\n"
``````
``````"The target value is \(self.target).\n"
``````
``````"You scored \(pointsForCurrentRound()) points this round."
``````

## Improving the code

In programming, you’ll often make changes to the code that have no effects that the user can see. These are changes to the app’s internal structure, and they’re visible only to the programmer — that is, you.

### Using a constant to DRY your code

Take a look at the `if` statement inside `pointsForCurrentRound()`:

``````if Int(self.sliderValue.rounded()) > self.target {
difference = Int(self.sliderValue.rounded()) - self.target
} else if self.target > Int(self.sliderValue.rounded()) {
difference = self.target - Int(self.sliderValue.rounded())
} else {
difference = 0
}
``````
``````func pointsForCurrentRound() -> Int {
var sliderValueRounded = Int(self.sliderValue.rounded())
var difference: Int
if sliderValueRounded > self.target {
difference = sliderValueRounded - self.target
} else if self.target > sliderValueRounded {
difference = self.target - sliderValueRounded
} else {
difference = 0
}
return 100 - difference
}
``````

### Introducing `let` and constants

If you’re into science fiction, you probably read “mutated” and thought of it as meaning “exposed to radiation or chemicals and turned into a horrible monster.” However, in programming, “mutated” simply means “changed”.

``````if sliderValueRounded > self.target {
difference = sliderValueRounded - self.target
} else if self.target > sliderValueRounded {
difference = self.target - sliderValueRounded
} else {
difference = 0
}
``````

``````func pointsForCurrentRound() -> Int {
let sliderValueRounded = Int(self.sliderValue.rounded())
var difference: Int
if sliderValueRounded > self.target {
difference = sliderValueRounded - self.target
} else if self.target > sliderValueRounded {
difference = self.target - sliderValueRounded
} else {
difference = 0
}
return 100 - difference
}
``````
``````func pointsForCurrentRound() -> Int {
let sliderValueRounded = Int(self.sliderValue.rounded())
var difference: Int
if sliderValueRounded > self.target {
difference = sliderValueRounded - self.target
} else if self.target > sliderValueRounded {
difference = self.target - sliderValueRounded
} else {
difference = 0
}
return 100 - difference
}
``````
``````var difference: Int
``````
``````let difference: Int
``````

### Another attempt to DRY some code

Since you’ve defined the `sliderValueRounded` constant in `pointsForCurrentRound()`, try using it in `ContentView`’s `body` to make it more DRY. Scroll up to the Button row section and pay particular attention to `message:`:

``````// Button row
Button(action: {
print("Button pressed!")
}) {
Text("Hit me!")
}
message: Text("The slider's value is \(Int(self.sliderValue.rounded())).\n" +
"The target value is \(self.target).\n" +
"You scored \(pointsForCurrentRound()) points this round."),
dismissButton: .default(Text("Awesome!")))
}
``````
``````alert(isPresented: self.\$alertIsVisible) {
message: Text("The slider's value is \(sliderValueRounded).\n" +
"The target value is \(self.target).\n" +
"You scored \(pointsForCurrentRound()) points this round."),
dismissButton: .default(Text("Awesome!")))
}
``````

``````alert(isPresented: self.\$alertIsVisible) {
message: Text("The slider's value is \(Int(self.sliderValue.rounded())).\n" +
"The target value is \(self.target).\n" +
"You scored \(pointsForCurrentRound()) points this round."),
dismissButton: .default(Text("Awesome!")))
}
``````

### The lives and times of variables and constants

If you look at the code in `ContentView`, you’ll see that there are variables and constants in two places:

``````func pointsForCurrentRound() -> Int {
let sliderValueRounded = Int(self.sliderValue.rounded())
let difference: Int
if sliderValueRounded > self.target {
difference = sliderValueRounded - self.target
} else if self.target > sliderValueRounded {
difference = self.target - sliderValueRounded
} else {
difference = 0
}
return 100 - difference
}
``````
``````struct ContentView : View {
@State var alertIsVisible: Bool = false
@State var sliderValue: Double = 50.0
@State var target: Int = Int.random(in: 1...100)

var body: some View {

...
``````
``````{
var a = 30  // a is in scope here

// (More code goes here)
{
var b = a + 1  // Both a and b are in scope here

// (More code goes here)
}
// b is no longer in scope.

print("The value of a is \(a).")  // a is still in scope
}

// Both a and b are no longer in scope.
``````

You’ve probably noticed the green text that begins with `//` a few times now. As I explained earlier, these are comments. You can write any text you want after the `//` symbol, and the compiler will ignore any text from the `//` to the end of the line.

``````// I am a comment! You can type anything here.
``````
``````/*
I am also a comment!
I can span multiple lines.
*/
``````

### A second attempt at DRYing some code

Right now, two different places in `ContentView` make use of the same calculation to get the value of the slider: Round it to the nearest whole number and convert it into an `Int`.

``````Alert(title: Text("Hello there!"),
message: Text("The slider's value is \(Int(self.sliderValue.rounded())).\n" +
"The target value is \(self.target).\n" +
"You scored \(pointsForCurrentRound()) points this round."),
dismissButton: .default(Text("Awesome!")))
}
``````
``````func pointsForCurrentRound() -> Int {
let sliderValueRounded = Int(self.sliderValue.rounded())
let difference: Int
if sliderValueRounded > self.target {
difference = sliderValueRounded - self.target
} else if self.target > sliderValueRounded {
difference = self.target - sliderValueRounded
} else {
difference = 0
}
``````
``````func sliderValueRounded() -> Int {
return Int(sliderValue.rounded())
}
``````
``````var sliderValueRounded: Int {
Int(self.sliderValue.rounded())
}
``````
``````Alert(title: Text("Hello there!"),
message: Text("The slider's value is \(self.sliderValueRounded).\n" +
"The target value is \(self.target).\n" +
"You scored \(self.pointsForCurrentRound()) points this round."),
dismissButton: .default(Text("Awesome!")))
``````
``````func pointsForCurrentRound() -> Int {
let difference: Int
if self.sliderValueRounded > self.target {
difference = self.sliderValueRounded - self.target
} else if self.target > self.sliderValueRounded {
difference = self.target - self.sliderValueRounded
} else {
difference = 0
}
return 100 - difference
}
``````

### Simplifying the `Alert` code

`ContentView`’s `body` defines the user interface; as a result, it’s big and can be unwieldy. For example, consider the code in `body` that generates the alert pop-up:

``````Alert(title: Text("Hello there!"),
message: Text("The slider's value is \(self.sliderValueRounded()).\n" +
"The target value is \(self.target).\n" +
"You scored \(pointsForCurrentRound()) points this round."),
dismissButton: .default(Text("Awesome!")))
``````
``````func scoringMessage() -> String {
return "The slider's value is \(self.sliderValueRounded).\n" +
"The target value is \(self.target).\n" +
"You scored \(self.pointsForCurrentRound()) points this round."
}
``````
``````Alert(title: Text("Hello there!"),
message: Text(self.scoringMessage()),
dismissButton: .default(Text("Awesome!")))
``````

### Removing redundancies

Take a look at the Properties section of `ContentView`, particularly the part marked User interface views:

``````@State var alertIsVisible: Bool = false
@State var sliderValue: Double = 50.0
@State var target: Int = Int.random(in: 1...100)
var sliderValueRounded: Int {
Int(self.sliderValue.rounded())
}
``````
``````@State var alertIsVisible = false
@State var sliderValue = 50.0
@State var target = Int.random(in: 1...100)
var sliderValueRounded: Int {
Int(self.sliderValue.rounded())
}
``````
``````@State var alertIsVisible = false
``````
``````@State var sliderValue = 50.0
``````
``````@State var target = Int.random(in: 1...100)
``````
``````let difference: Int
if self.sliderValueRounded > self.target {
difference = self.sliderValueRounded - self.target
} else if self.target > self.sliderValueRounded {
difference = self.target - self.sliderValueRounded
} else {
difference = 0
}
return 100 - difference
``````
``````var sliderValueRounded: {
Int(self.sliderValue.rounded())
}
``````