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).
- The
- In the preview section, click on the component and press
command + click
to see the context menus, including theUi 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¶
-
Ching, C. (2021, April 19). The definitive Swift tutorial for beginners. CodeWithChris. https://codewithchris.com/swift-tutorial-complete/ ↩
-
Ng S. (n.d.). Mastering SwiftUI. AppCoda. https://www.appcoda.com/learnswiftui/swiftui-basics.html ↩
-
Swift Enum - Javatpoint. (n.d.). javaTpoint.com. https://www.javatpoint.com/swift-enum ↩
-
The swift programming language swift 5.7. (n.d.). Apple Inc. https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html ↩
-
UIKit. (n.d.). Apple Developer. https://developer.apple.com/documentation/uikit ↩