Chapters

Hide chapters

UIKit Apprentice

Second Edition · iOS 15 · Swift 5.5 · Xcode 13

My Locations

Section 3: 11 chapters
Show chapters Hide chapters

Store Search

Section 4: 13 chapters
Show chapters Hide chapters

21. Swift Review
Written by Fahim Farook

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

You have made great progress! You’ve learnt the basics of Swift programming and created two applications from scratch. You are on the threshold of creating your next app.

But, a good building needs a good foundation. And in order to strengthen the foundations of your Swift knowledge, you first need some additional theory. There is still a lot more to learn about Swift and object-oriented programming!

In the previous chapters I’ve shown you a fair bit of the Swift programming language already, but not quite everything. Previously, it was good enough if you could more-or-less follow along with what we were doing, but now is the time to fill in the gaps in theory. So, let’s do a little refresher on what we’ve talked about so far.

In this chapter, you will cover the following:

  • Variables, constants, and types: the difference between variables and constants, and what a type is.
  • Methods and functions: what are methods and functions — are they the same thing?
  • Making decisions: an explanation of the various programming constructs that can be used in the decision making process for your programs.
  • Loops: how do you loop through a list of items?
  • Objects: all you ever wanted to know about Objects — what they are, their component parts, how to use them, and how not to abuse them.
  • Protocols: the nitty, gritty details about protocols.

Variables, constants and types

Variables and types

A variable is a temporary container for a specific type of value:

var count: Int
var shouldRemind: Bool
var text: String
var list: [ChecklistItem]
var i = 10
var f: Float
f = i         // error
f = Float(i)  // OK
var i = 10              // Int
var d = 3.14            // Double
var b = true            // Bool
var s = "Hello, world"  // String
var f: Float = 3.14
var i: Double = 10
var i = 10.0
var item: ChecklistItem
item = ChecklistItem()
var item = ChecklistItem()
var item = ChecklistItem(text: "Charge my iPhone", checked: false)
class MyObject {
  var count = 0      // an instance variable

  func myMethod() {
    var temp: Int    // a local variable
    temp = count     // OK to use the instance variable here
  }

  // the local variable “temp” doesn’t exist outside the method
}
class MyObject {
  var count = 7      // an instance variable

  func myMethod() {
    var count = 42   // local variable “hides” instance variable
    print(count)     // prints 42
  }
}
  func myMethod() {
    var count = 42
    print(self.count)   // prints 7
  }

Constants

Variables are not the only code elements that can hold values. A variable is a container for a value that is allowed to change over the course of the app being run.

let pi = 3.141592
let difference = abs(targetValue - currentValue)
let message = "You scored \(points) points"
let image = UIImage(named: "SayCheese")

Value types vs. reference types

When working with basic values such as integers and strings — which are value types — a constant created with let cannot be changed once it has been given a value:

let pi = 3.141592
pi = 3                // not allowed
let item = ChecklistItem()
item.text = "Do the laundry"
item.checked = false
item.dueDate = yesterday
let anotherItem = ChecklistItem()
item = anotherItem   // cannot change the reference

Collections

A variable stores only a single value. To keep track of multiple objects, you can use a collection object. Naturally, I’m talking about arrays (Array) and dictionaries (Dictionary), both of which you’ve seen previously.

// An array of ChecklistItem objects:
var items: Array<ChecklistItem>

// Or, using shorthand notation:
var items: [ChecklistItem]

// Making an instance of the array:
items = [ChecklistItem]()

// Accessing an object from the array:
let item = items[3]
// A dictionary that stores (String, Int) pairs, for example a
// list of people’s names and their ages:
var ages: Dictionary<String, Int>

// Or, using shorthand notation:
var ages: [String: Int]

// Making an instance of the dictionary:
ages = [String: Int]()

// Accessing an object from the dictionary:
var age = dict["Jony Ive"]

Generics

Array and Dictionary are known as generics, meaning that they are independent of the type of thing you want to store inside these collections.

var items: Array  // error: should be Array<TypeName>
var items: []     // error: should be [TypeName]

Optionals

Sometimes it’s useful to have a variable that can have no value, in which case you need to declare it as an optional:

var checklistToEdit: Checklist?
if let checklist = checklistToEdit {
  // “checklist” now contains the real object
} else {
  // the optional was nil
}
if let age = dict["Jony Ive"] {
  // use the value of age
}
var age = dict["Jony Ive"]!
navigationController!.delegate = self
navigationController?.delegate = self
if navigationController != nil {
  navigationController!.delegate = self
}
var dataModel: DataModel!

Methods and functions

You’ve learned that objects, the basic building blocks of all apps, have both data and functionality. Instance variables and constants provide the data, methods provide the functionality.

let result = performUselessCalculation(314)
print(result)

. . .

func performUselessCalculation(_ a: Int) -> Int {
  var b = Int(arc4random_uniform(100))
  var c = a / 2
  return (a + b) * c
}
// Method with no parameters, no return a value.
override func viewDidLoad()

// Method with one parameter, slider. No return a value.
// The keyword @IBAction means that this method can be connected
// to a control in Interface Builder.
@IBAction func sliderMoved(_ slider: UISlider)

// Method with no parameters, returns an Int value.
func countUncheckedItems() -> Int

// Method with two parameters, cell and item, no return value.
// Note that the first parameter has an extra label, for, 
// and the second parameter has an extra label, with.
func configureCheckmarkFor(
  for cell: UITableViewCell, 
  with item: ChecklistItem)

// Method with two parameters, tableView and section. 
// Returns an Int. The _ means the first parameter does not 
// have an external label.
override func tableView(
  _ tableView: UITableView, 
  numberOfRowsInSection section: Int) -> Int

// Method with two parameters, tableView and indexPath.
// The question mark means it returns an optional IndexPath 
// object (may also return nil).
override func tableView(
  _ tableView: UITableView, 
  willSelectRowAt indexPath: IndexPath) -> IndexPath?
// Calling a method on the lists object:
lists.append(checklist)

// Calling a method with more than one parameter:
tableView.insertRows(at: indexPaths, with: .fade)
class DataModel {
  func loadChecklists() {
    . . .    
    sortChecklists()  // this method also lives in DataModel
  }

  func sortChecklists() { 
    . . . 
  }
}
  func loadChecklists() {
    . . .    
    self.sortChecklists()
  }
@IBAction func cancel() {
  delegate?.itemDetailViewControllerDidCancel(self)
}

Parameters

Often methods have one or more parameters, so they can work with multiple data items. A method that is limited to a fixed set of data is not very useful or reusable. Consider sumValuesFromArray(), a method that has no parameters:

class MyObject {
  var numbers = [Int]()

  func sumValuesFromArray() -> Int {
    var total = 0
    for number in numbers {
      total += number
    }
    return total
  }
}
func sumValues(from array: [Int]) -> Int {
  var total = 0
  for number in array {
    total += number
  }
  return total
}
func downloadImage(
  for searchResult: SearchResult,  
  withTimeout timeout: TimeInterval, 
  andPlaceOn button: UIButton
) {
  . . .
}
downloadImage(for: result, withTimeout: 10, andPlaceOn: imageButton)
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int

Making decisions

The if statement looks like this:

if count == 0 {
  text = "No Items"
} else if count == 1 {
  text = "1 Item"
} else {
  text = "\(count) Items"
}

Comparison operators

You use comparison operators to perform comparisons between two values:

let a = "Hello, world"
let b = "Hello," + " world"
print(a == b)             // prints true

Logical Operators

You can use logical operators to combine two expressions:

if ((this && that) || (such && so)) && !other {
  // statements
}
if ((this and that) or (such and so)) and not other {
  // statements
}
if ( 
      (this and that)
            or
      (such and so)
   )
   and
      (not other)

switch statement

Swift has another very powerful construct in the language for making decisions, the switch statement:

switch condition {
  case value1:
    // statements

  case value2:
    // statements

  case value3:
    // statements

  default:
    // statements
}
if condition == value1 {
  // statements
} else if condition == value2 {
  // statements
} else if condition == value3 {
  // statements
} else {
  // statements
}
switch difference {
  case 0:
    title = "Perfect!"
  case 1..<5:
    title = "You almost had it!"
  case 5..<10:
    title = "Pretty good!"
  default:
    title = "Not even close..."
}

return statement

Note that if and return can be used to return early from a method:

func divide(_ a: Int, by b: Int) -> Int {
  if b == 0 {
    print("You really shouldn't divide by zero")
    return 0
  }
  return a / b
}
func performDifficultCalculation(list: [Double]) {
  if list.count < 2 {
    print("Too few items in list")
    return
  }

  // perform the very difficult calculation here
}
func performDifficultCalculation(list: [Double]) {
  if list.count < 2 {
    print("Too few items in list")
  } else {
    // perform the very difficult calculation here
  }
}
func someMethod() {
  if condition1 {
    if condition2 {
      if condition3 {
        // statements
      } else {
        // statements
      }
    } else {
      // statements
    }
  } else {
    // statements
  }
}
func someMethod() {
  if !condition1 {
    // statements
    return
  }

  if !condition2 {
    // statements
    return
  }

  if !condition3 {
    // statements
    return
  }

  // statements
}
func someMethod() {
  guard condition1 else {
    // statements
    return
  }
  guard condition2 else {
    // statements
    return
  }
  . . .

Loops

You’ve seen the for...in statement for looping through an array:

for item in items {
  if !item.checked {
    count += 1
  }
}
for item in items where !item.checked {
  count += 1
}

Looping through number ranges

Some languages have a for statement that looks like this:

for var i = 0; i < 5; ++i {
  print(i)
}
0
1
2
3
4
for i in 0...4 {   // or 0..<5
  print(i)
}
for i in stride(from: 0, to: 5, by: 1) {
  print(i)
}

while statement

The for statement is not the only way to perform loops. Another very useful looping construct is the while statement:

while something is true {
  // statements
}
repeat {
  // statements
} while something is true
var count = 0
var i = 0
while i < items.count {
  let item = items[i]
  if !item.checked {
    count += 1
  }
  i += 1
}
var found = false
for item in array {
  if item == searchText {
    found = true
    break
  }
}
var uncheckedItems = items.filter { item in !item.checked }

Objects

Objects are what it’s all about. They combine data with functionality into coherent, reusable units — that is, if you write them properly! The data is made up of the object’s instance variables and constants. We often refer to these as the object’s properties. The functionality is provided by the object’s methods.

class MyObject {
  var text: String
  var count = 0
  let maximum = 100

  init() {
    text = "Hello world"
  }

  func doSomething() {
    // statements
  }
}

Properties

There are two types of properties:

var indexOfSelectedChecklist: Int {
  get {
    return UserDefaults.standard.integer(
      forKey: "ChecklistIndex")
  }
  set {
    UserDefaults.standard.set(
      newValue, 
      forKey: "ChecklistIndex")
  }
}

Methods

There are three kinds of methods:

let myInstance = MyObject()   // create the object instance
. . .
myInstance.doSomething()      // call the method
class MyObject {
  . . .

  class func makeObject(text: String) -> MyObject {
    let m = MyObject()
    m.text = text
    return m
  }
}

let myInstance = MyObject.makeObject(text: "Hello world")
class MyObject {
  . . .

  init(text: String) {
    self.text = text
  }
}

let myInstance = MyObject(text: "Hello world")

Protocols

Besides objects, you can also define protocols. A protocol is simply a list of method names and possibly, properties:

protocol MyProtocol {
  func someMethod(value: Int)
  func anotherMethod() -> String
}
class MyObject: MyProtocol {
  . . .
}
var m1: MyObject = MyObject()
var m2: MyProtocol = MyObject()
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

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