100 Days of SwiftUI Checkpoint 6

Course Credit:
This blog post's content is based on the fantastic learning materials from Paul Hudson's 100 Days of SwiftUI course, available for free at Hacking with Swift.
Structs in Swift
Structs are the building blocks of Swift. They are custom data types that allow you to group related data and functionality into a single, cohesive unit. Think of structs as blueprints for creating your own types, much like how a String
or Int
is a built-in type. They are a fundamental part of modern Swift development, essential for writing clear, organized, and powerful code.
Anatomy of a Struct
Syntax: Structs are defined using the struct
keyword, followed by a name and then curly braces {}
to contain its properties and methods.
Properties & Methods: Structs can have their own variables and constants (properties) to store data, and their own functions (methods) to perform actions.
Example:
struct Album {
let title: String
let artist: String
let year: Int
func printSummary() {
print("\(title) (\(year)) by \(artist)")
}
}
Creating Instances: You create a new instance of a struct by calling its name and providing initial values for its properties.
Example:
let red = Album(title: "Red", artist: "Taylor Swift", year: 2012)
red.printSummary()
Key Concepts
Mutating Methods
By default, the properties of a struct cannot be changed after it is created. If you need a method to modify one of the struct's properties, you must mark it with the mutating
keyword.
Example:
struct Employee {
var vacationRemaining = 14
mutating func takeVacation(days: Int) {
if vacationRemaining >= days {
vacationRemaining -= days
}
}
}
var archer = Employee()
archer.takeVacation(days: 5)
Computed Properties
A computed property does not store a value directly. Instead, it calculates its value dynamically every time it is accessed. You define a get
block to provide the value and an optional set
block to handle new assignments.
Example:
struct Employee {
var vacationAllocated = 20
var vacationTaken = 5
var vacationRemaining: Int {
get {
vacationAllocated - vacationTaken
}
}
}
let archer = Employee()
print(archer.vacationRemaining) // Returns 15
Property Observers
Property observers allow you to run code whenever a property’s value changes. The willSet
observer runs just before the change, and didSet
runs immediately after.
Example:
struct Game {
var score = 0 {
didSet {
print("Score is now \(score)")
}
}
}
var game = Game()
game.score += 10
Static Properties & Methods
Static properties and methods belong to the struct type itself, not to a specific instance of the struct. This is useful for creating shared data or utility functions.
Example:
struct School {
static var studentCount = 0
static func add(student: String) {
studentCount += 1
}
}
School.add(student: "Taylor Swift")
print(School.studentCount)
Why Use Structs?
Structs are a core part of Swift's value semantics. When you assign a struct to a new variable or pass it to a function, Swift creates a copy of it. This ensures that you aren't accidentally modifying the original data, which helps prevent bugs and makes your code more predictable. This behavior is different from classes, which use reference semantics.
In many cases, choosing a struct over a class is the right decision for your code's architecture. They are lightweight, safe, and are the most common way to represent data types in Swift.
import UIKit
//------ How to create your own structs
// --- Structs and Methods ---
// How to create your own structs
struct Album {
// Stored properties to hold the album's data.
let title: String
let artist: String
let year: Int
// A method to print a summary of the album.
func printSummary() {
print("\(title) (\(year)) by \(artist)")
}
}
// Create instances of the Album struct.
let red = Album(title: "Red", artist: "Taylor Swift", year: 2012)
let wings = Album(title: "Wings", artist: "BTS", year: 2016)
// Accessing the properties of the structs.
print(red.title)
print(wings.artist)
// Calling the method on the struct instances.
red.printSummary()
wings.printSummary()
// --- Mutating Methods ---
// How to create a method that modifies a property.
struct Employee {
let name: String
var vacationRemaining = 14
// The 'mutating' keyword is needed because this method changes the
// value of a property (vacationRemaining) within the struct.
mutating func takeVacation(days: Int) {
// Check if there are enough vacation days remaining.
if vacationRemaining >= days {
// Decrement the remaining vacation days.
vacationRemaining -= days
print("I'm going on vacation!")
print("Days remaining: \(vacationRemaining)")
} else {
print("Oops! There aren't enough days remaining.")
}
}
}
// Create an instance of the Employee struct and call the mutating method.
var archer = Employee(name: "Sterling Archer", vacationRemaining: 14)
archer.takeVacation(days: 5)
print("Vacation remaining for Archer: \(archer.vacationRemaining)")
// Another example with different instances.
var kane = Employee(name: "Lana Kane")
var poovey = Employee(name: "Pam Poovey", vacationRemaining: 35)
poovey.takeVacation(days: 5)
print("Vacation remaining for Poovey: \(poovey.vacationRemaining)")
// A quick note on data types and conversion.
let a = 1
let b = 2.0
// Convert 'a' to a Double before adding to 'b' to avoid type mismatch.
let c = Double(a) + b
print("The result of the calculation is: \(c)")
// --- Computed Properties ---
// How to compute property values dynamically.
struct Employee1 {
let name: String
var vacationAllocated = 0
var vacationTaken = 0
// A computed property. It doesn't store a value, but calculates it
// on the fly using a getter and a setter.
var vacationRemaining: Int {
// The 'get' block calculates the value when it's read.
get {
vacationAllocated - vacationTaken
}
// The 'set' block runs when a new value is assigned. 'newValue'
// is a special constant that holds the value being assigned.
set {
vacationAllocated = vacationTaken + newValue
}
}
}
// Demonstrate the computed property.
var archer1 = Employee1(name: "Sterling Archer")
archer1.vacationTaken += 11
// This will trigger the 'set' block of vacationRemaining.
archer1.vacationRemaining = 6
print("Sterling Archer must have started with \(archer1.vacationAllocated) vacation days.")
// --- Property Observers (willSet and didSet) ---
// How to take action when a property changes.
struct Game {
// The didSet block is called immediately after the score is changed.
var score = 0 {
didSet {
print("Score is now \(score)")
}
}
}
var game = Game()
game.score += 10
game.score -= 3
game.score += 1
struct App {
// The willSet block is called just before the property is about to change.
var contacts = [String]() {
willSet {
print("Current value is: \(contacts)")
print("New value will be: \(newValue)") // 'newValue' holds the upcoming value.
}
// The didSet block is called immediately after the property has changed.
didSet {
print("There are now \(contacts.count) contacts.")
print("Old value was \(oldValue)") // 'oldValue' holds the value before the change.
}
}
}
var app = App()
app.contacts.append("Adrian E")
app.contacts.append("Allen W")
app.contacts.append("Ish S")
// --- Custom Initializers ---
// How to create custom initializers for structs.
// A struct with a default, memberwise initializer.
struct Player0 {
let name: String
let number: Int
}
// Swift provides this initializer automatically.
let player0 = Player0(name: "Gal R", number: 15)
print(player0)
// A struct with a custom initializer.
struct Player {
let name: String
let number: Int
// A custom initializer. It doesn't need to take a number, but
// initializes the 'number' property to a random value.
init(name: String) {
self.name = name
number = Int.random(in: 1...99)
}
}
let player = Player(name: "Megan R")
print(player)
// --- Access Control and Static Properties ---
// How to limit access to internal data.
// By default, properties and methods are 'internal' which means they
// are accessible anywhere within the same module.
struct BankAccount {
var funds = 0
mutating func deposit(amount: Int) {
funds += amount
}
mutating func withdraw(amount: Int) -> Bool {
if funds >= amount {
funds -= amount
return true
} else {
return false
}
}
}
var account = BankAccount()
account.deposit(amount: 100)
let success = account.withdraw(amount: 200)
if success {
print("Withdrew money successfully")
} else {
print("Failed to get the money")
}
// In a real-world scenario, we would use 'private' to prevent direct
// modification of funds from outside the struct. For example:
// private var funds = 0
// --- Static Properties and Methods ---
// Static properties and methods belong to the struct itself, not to
// individual instances of the struct.
struct School {
// A static property that is shared across all instances of School.
static var studentCount = 0
// A static method that operates on the type itself.
static func add(student: String) {
print("\(student) joined the school.")
studentCount += 1
}
}
// Call the static method directly on the School struct.
School.add(student: "Taylor Swift")
print("Total student count: \(School.studentCount)")
// An example of static constants.
struct AppData {
static let version = "1.3 beta 2"
static let saveFilename = "settings.json"
static let homeURL = "https://www.hackingwithswift.com"
}
// An example of a static property that provides a default instance.
struct Employee9 {
let username: String
let password: String
// A static constant that provides a pre-configured example.
static let example = Employee9(username: "cfederighi", password: "hairforceone")
}
//------ Checkpoint 6
struct Car {
let name = "Chevy"
let model = "Corvette"
var currentGear: Int {
didSet {
// Check if the new value is outside the valid range.
if currentGear < minGear {
print("Can't go below minimum gear! Resetting to \(minGear).")
currentGear = minGear
} else if currentGear > maxGear {
print("Can't go above maximum gear! Resetting to \(maxGear).")
currentGear = maxGear
}
}
}
var minGear: Int {
return 1
}
var maxGear: Int {
return 10
}
}
var myCar = Car(currentGear: 1)
myCar.currentGear = 11
print("My car is in gear \(myCar.currentGear)")
myCar.currentGear -= 3
print("My car is now in gear \(myCar.currentGear)")
print("My car's minimum gear is \(myCar.minGear)")
print("My car's maximum gear is \(myCar.maxGear)")
/*
Recap of Structs:
- **Ubiquitous in Swift:** Many of Swift's core types like **String**, **Int**, and **Array** are structs. This means that methods like `isMultiple(of:)` are actually part of the `Int` struct.
- **Creating structs:** You can define your own structs using the `struct` keyword, followed by a name and then curly braces `{}` to contain its properties and methods.
- **Properties and methods:** Structs can have their own variables and constants (**properties**) and functions (**methods**).
- **Mutating methods:** If a method needs to change one of the struct's properties, it must be marked with the `mutating` keyword.
- **Stored vs. computed properties:** Properties can store values directly in memory or calculate them dynamically as a **computed property** every time they are accessed.
- **Property observers:** **`didSet`** and **`willSet`** can be attached to properties to run code every time a property's value changes.
- **Initializers:** These are special functions that set up a new struct instance. Swift automatically provides a default initializer, but you can create your own. When you do, you must ensure that every property has a value before the initializer finishes.
- **Access control:** You can use access control to limit which properties and methods are available to other parts of your code.
- **Static properties and methods:** You can use the `static` keyword to attach properties or methods directly to a struct itself, allowing them to be called without creating an instance of the struct.
*/