Skip to content

6. OOP in Swift and Swift UI

1 Swift Tutorial

Classes

class Person {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    func sayHello() {
        print("Hello, my name is \(name), I am \(age) years old.")
    }
}

let person = Person(name: "John", age: 21)

class Employee : Person {
    var baseSalary: Double = 10000.0
    var salaryMultiplier: Double = 0.0
    var salary: Double { // Computed property
        return baseSalary * salaryMultiplier
    }

    init(name: String, age: Int, salaryMultiplier: Double) {
        self.salaryMultiplier = salaryMultiplier
        super.init(name: name, age: age)
    }

    override func sayHello() {
        print("Hello, my name is \(name), I am \(age) years old and I earn \(salary).")
    }
}

UIKit

  • Announced in 2007.
  • All elements are subclasses of NSObject which is the root class of all Objective-C classes.

2 SwiftUI Tutorial

  • A declarative framework for building user interfaces for Apple platforms.
  • Announced in 2019.

Declarative vs Imperative Programming

Aspect Declarative Imperative Comment
1 Describes structure and elements of the program Uses statements to change program state
2 No specification of control flow Consists of commands
Code understanding and maintainability Easier Harder
Code size Smaller Larger

SwiftUI Vs UIKit

  • Elements from UIKit that have been removed from SwiftUI:
    • Storyboards.
    • Auto Layout.
    • The View Controller.
  • New Elements in SwiftUI:
    • The Combine framework: controls communications and data sharing between views.
    • The SwiftUI is used to develop apps for all Apple platforms (iOS, iPadOS, macOS, watchOS, tvOS).
  • In the preview section, click on the component and press command + click to see the context menus, including the Ui inspector.

Text

Text("Hello, World!")
    .fontWeight(.bold)
    .font(.body)
    .foregroundColor(.gray)
    .multilineTextAlignment(.leading)
    .truncationMode(.middle)
    .lineSpacing(10)
    .padding()
    .fixedSize(horizontal: false, vertical: true)
    .lineLimit(nil)
    .rotationEffect(.degrees(3))
    .rotation3DEffect(.degrees(4), axis: (x: 1, y: 0, z: 0))
    .shadow(color:.gray.opacity(0.22), radius: 0, x: 2, y: 9)

3 Swift Enum

enum ConnectionStatus {
    case Unknown
    case Disconnected
    case Connecting
    case Connected

}

var connectionStatus = ConnectionStatus.Connecting
connectionStatus = .Connected

switch connectionStatus {
    case .Disconnected:
    print("Disconnected")

    case .Connecting:
    print("Connecting")

    case .Connected:
    print("Connected")

    default:
    print("Unknown Status")
}

4 Swift Documentation

  • Enums and Structs are value types (passed by value), Classes, functions, and closures are reference types (passed by reference).

Closures

  • Closures Group code that executes together, without creating a named function.
  • Closure Types:
    • Global functions: have a name and do not capture any values.
    • Nested functions: have a name and can capture values from their enclosing function.
    • Closure expressions: are unnamed and can capture values from their surrounding context.
  • Closures in Swift can mean closures of JavasScript or lambda expressions of Java.
// Closure expression syntax
{ (parameters) -> return type in
    statements
}

// Example: sorted(by:) which accept closer of type (String, String) -> Bool
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

// Inferring Type From Context: the `(s1: String, s2: String) -> Bool` can be omitted
//          as their types are inferred from the context of the `sorted(by:)` method.
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

// Implicit Returns from Single-Expression Closures: the `return` keyword can be omitted
//          as the closure only contains a single expression.
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

// Shorthand Argument Names: the arguments can be referred to by the shorthand names `$0`, `$1`, ...
reversedNames = names.sorted(by: { $0 > $1 } )

// Operator Methods: the `>` operator is a function that takes two `String` arguments and returns a `Bool` value.
//          This means you can omit the closure’s argument list from its definition.
reversedNames = names.sorted(by: >) // craze, right?

// Trailing Closures: if the last argument of a function is a closure, you can use trailing closure syntax.
//      The closure `{$0 > $1}`  is still an argument of the `sorted()` method
reversedNames = names.sorted() { $0 > $1 }

// if the trailing closure is the only argument, you can omit the parentheses entirely
reversedNames = names.sorted { $0 > $1 } // `()` is omitted from the function call
  • Closures that capture values:
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementor() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementor
}

let incrementByTen = makeIncrementor(forIncrement: 10)
incrementByTen() // 10
incrementByTen() // 20, as `runningTotal` and `amount` are captured by the closure


// Closures are reference types
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen() // 30, as `incrementByTen` reference is stored in `alsoIncrementByTen`

//but
let anotherIncrementByTen = makeIncrementor(forIncrement: 10)
anotherIncrementByTen() // 10, this is a new reference to the closure

// Escaping Closures:
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

// AutoClosures:
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count) // 5
let customerProvider = { customersInLine.remove(at: 0) /** customersInLine is available here */ }

Enumerations

  • Enums are classes.
  • Values of enums are not integers, they are values of their own type.
// Basic enum
enum CompassPoint {
    case north // north, NOT 0
    case south
    case east
    case west
}


// Iterable enums
enum Beverage: CaseIterable {
    case coffee, tea, juice
}

let numberOfChoices = Beverage.allCases.count
for beverage in Beverage.allCases {
    print(beverage) // coffee, tea, juice
}

// Associated values in enums
enum Barcode {
    case upc(Int, Int, Int, Int) // only one value can be set
    case qrCode(String) // only one value can be set
}

var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")

switch productBarcode {
    case .upc(let numberSystem, let manufacturer, let product, let check):
        print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
    case .qrCode(let productCode):
        print("QR code: \(productCode).")
}

productBarcode = .upc(8, 85909, 51226, 3) // reset the value, not adding a new one

// Raw values in enums
enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}

// Implicitly Assigned Raw Values
enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
    // venus = 2, earth = 3, ...
}

// Initializing from a Raw Value
let possiblePlanet = Planet(rawValue: 7) // uranus

// Recursive Enumerations
enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

Structures and Classes

  • Structures are value types, classes are reference types.
  • Both can Have: properties, methods, initializers, subscripts, and extensions.
  • Classes can:
    • Inherit other classes, structures cannot.
    • Override methods, structures can NOT.
    • Final classes cannot be subclassed.
    • Deinitializers, structures cannot.
    • Classes do not need mutating keyword to change properties, as they are reference types and are mutable by default.
  • Structs have:
    • A default initializer, classes do not.
    • Any change to a property in a method must be marked with the mutating keyword, as the struct itself is a constant.
struct Resolution {
    var width = 0
    var height = 0
    init(width: Int, height: Int) {
        self.width = width
        self.height = height
    }
}
class VideoMode {
    var resolution = Resolution(1,1)
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

var res1 = Resolution(1,1)
var res2 = res1
res2.width = 2 // res1 is not affected
print(res1.width) // 1, as res1 is a value type

var videoMode1 = VideoMode()
var videoMode2 = videoMode1
videoMode2.resolution.width = 2 // videoMode1 is affected
print(videoMode1.resolution.width) // 2, as videoMode1 is a reference type

Properties

  • Stored properties: store constant and variable values as part of an instance (available in classes and structures).
  • Computed properties: calculate a value. (available in classes, structures, and enumerations).
  • Type properties (aka, static properties): properties that belong to the type itself, not to any one instance of that type (available in classes, structures, and enumerations).
  • Property observers:
    • observe and respond to changes in a property’s value
    • Available in classes, structures, and enumerations.
    • Subclasses can inherit property observers, and can observe properties in superclasses.
  • Property Wrappers: reuse setter and getter logic.
  • Lazy Stored Properties: a property whose initial value is not calculated until the first time it is used.

// property wrapper
@propertyWrapper
struct TwelveOrLess {
    private var number: Int
    init() { self.number = 0 }
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}

struct FixedLengthRange {
    var firstValue: Int // Stored properties, variable
    let length: Int // Stored properties, constant
    lazy var lp: Int = 1 // Lazy Stored Properties, will change after initialization
    var lastValue: Int { // Computed properties
        get {
            return firstValue + length - 1
        }
        set {
            firstValue = newValue - length + 1
        }
    }
    var obs: Int { // Observers
        willSet (newValue){
            print("About to set obs to \(newValue)")
        }
        didSet (newValue, oldValue) {
            if newValue > oldValue {
                print("obs \(totalRange - oldValue)")
            }
        }
    }
    @TwelveOrLess var pw: Int // Property Wrappers
    static var storedTypeProperty = "Some value." // Type Properties
}

Methods

  • Instance methods: functions that belong to instances of a particular class, structure, or enumeration.
  • Type methods: functions that belong to the type itself, not to any one instance of that type.
class Counter {
    var count = 0
    var x = 0
    func increment() {
        count += 1 // self is implicitly used, but can be omitted
    }
    func increment(by amount: Int) {
        self.count += amount // explicitly use of self
    }

    func setXq(x: Int) {
        self.x = x // self is required to distinguish between the property and the parameter
    }

    static func someTypeMethod() {
        print("Some type method.")
    }

}
  • Mutating methods: methods that change the instance itself.
struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        self = Point(x: x + deltaX, y: y + deltaY) // a new instance is created and assigned to self
    }
}
  • Static or Type methods, mutating methods to change a static property.
struct LevelTracker {
    static var highestUnlockedLevel = 1 // Type Properties
    var currentLevel = 1 // Stored Properties, instance


    static func unlock(_ level: Int) {
        if level > highestUnlockedLevel { highestUnlockedLevel = level }
    }


    static func isUnlocked(_ level: Int) -> Bool {
        return level <= highestUnlockedLevel
    }


    @discardableResult // mutating func to change a static property
    mutating func advance(to level: Int) -> Bool {
        if LevelTracker.isUnlocked(level) {
            currentLevel = level
            return true
        } else {
            return false
        }
    }
}
  • Designated initializers: initializers that fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.
  • Convenience initializers: initializers that call a designated initializer from the same class.
  • Two phase initialization:
    • Phase 1: initializes all stored properties introduced by that class.
    • Phase 2: additional customization that is not allowed until after the initial phase of initialization is complete.
class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}

class Bicycle: Vehicle {
    init() {
        super.init()
        numberOfWheels = 2
        // super.init() is implicitly called here
    }

    override var description: String {
        return super.description + ", which is a bicycle"
    }

    convenience init(custom: Int) {
        self.init()
        numberOfWheels = custom
    }
}

DeInitializers

  • Deinitializers are called immediately before a class instance is deallocated.
class Bank {
   // ...
   deinit {
      // perform the deinitialization
   }
}

Optional Chaining

  • Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might currently be nil.
  • Optional chaining is the opposite of forced unwrapping.

Protocols

  • Protocols are similar to interfaces in Java, that is, they define contracts that classes and structs can conform to.
  • Protocols can be adopted by classes, structures, and enumerations.
  • Protocol inheritance allows for multiple inheritance.
protocol SomeProtocol {
    // protocol definition goes here
    var id: Int { get set }
    func someMethod()
}

// someStruct conforms to SomeProtocol
struct SomeStruct: SomeProtocol {
    var id: Int
    func someMethod() {
        // do something
    }
}

Extensions

  • Extensions add new functionality to an existing class, structure, enumeration, or protocol type.
extension Int {
    var isEven: Bool {
        return self % 2 == 0
    }

    func squared () -> Int {
        return self * self
    }
}

let x = 2
x.isEven // true
x.squared() // 4

5 UIKit


References


  1. Ching, C. (2021, April 19). The definitive Swift tutorial for beginners. CodeWithChris. https://codewithchris.com/swift-tutorial-complete/ 

  2. Ng S. (n.d.). Mastering SwiftUI. AppCoda. https://www.appcoda.com/learnswiftui/swiftui-basics.html 

  3. Swift Enum - Javatpoint. (n.d.). javaTpoint.com. https://www.javatpoint.com/swift-enum 

  4. The swift programming language swift 5.7. (n.d.). Apple Inc. https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html 

  5. UIKit. (n.d.). Apple Developer. https://developer.apple.com/documentation/uikit