100 Days of SwiftUI – Day 10

·

Structs, part 1

How to create your own structs

A struct is a custom, complex data type complete with their own variables and functions.

struct Album {
    let title: String
    let artist: String
    let year: Int

    func printSummary() {
        print("\(title) (\(year)) by \(artist)")
    }
}

A struct when named is capitalized. All data types are capitalized.

// a couple of albums
let red = Album(title: "Red", artists: "TS", year: 2000)
let wings = Album(title: "Wings", artists: "BTX", year: 2020)

print(red.title)
print(wings.artist)

red.printSummary()
wings.printSummary()

We can have values that change based on parameters.

struct Employee {
    let name: String
    var vacationRemaining: Int

    func takeVacation(days: Int) {
        if vacationRemaining > days {
            vacationRemaining -= days
            print("I'm going on vacation!")
            print("Days remaining: \(vacationRemaining)")
        } else {
            print("Oops! There aren't enough days remaining.")
        }
    }
}

But if we create an Employee with a let, Swift will change everything in Employee into a constant. So we wan to add the keyword mutating.

Like this:

mutating func takeVacation(days: Int) {}

From here on out, we want to avoid using let to define a instance of a struct. Swift won’t build it as let and mutating doesn’t go together.

A couple more deets:

  • variables and constants that belong to structs are called properties
  • Functions that belong to structs are called methods
  • when we create a constant or a variable out of a struct, we call that an instance
  • The initializer for an instance of a struct, it kinda looks like a function call.
    Album(title: "Wings", artist: "A", year: 2020)

The way swift handles structs, it silently adds an init function to the struct and passes its properties into that. So these are equivalent:

:

var archer1 = Employee(name: "Sterling Archer", vacationRemaining: 14)
var archer2 = Employee.init(name: "Sterling Archer", vacationRemaining: 14)

Swift will even insert default values to structs. Given these 2 properties:

let name: String
var vacationRemaining = 14 // gets turned into a default

So both of these are valid:

let kane = Employee(name: "Lana Kane")
let poovey = Employee(name: "Pam Poovey", vacationRemaining: 35)

Scored 11/12 on Creating your own structs
Scored 10/12 on mutating methods

How to compute property values dynamically

Structs can have 2 kinds of properties

  • stored property – a variable or constant that holds data inside an instance of the struct
  • computed property – calculates the value of the property dynamically every time its accessed. They are a blend of stored properties and function, accessed like properties but work like functions

We had an example of an employee struct

struct Employee {
    let name: String
    var vacationRemaining: Int
}

var archer = Employee(name: "Sterling Archer", vacationRemaining: 14)
archer.vacationRemaining -= 5
print(archer.vacationRemaining)
archer.vacationRemaining -= 3
print(archer.vacationRemaining)

With this struct, we lose the information on how many vacation days they were originally granted.

We could adjust to:

struct Employee {
    let name: String
    var vacationAllocated = 14
    var vacationTaken = 0

    var vacationRemaining: Int {
        vacationAllocated = vacationTaken
    }
}

Here vacationRemaining is a value that is calculated dynamically but is accessed like a stored property.

var archer = Employee(name: "S A". vacationAllocated: 14)
archer.vacationTaken += 4
print(archer.vacationRemaining)
archer.vacationTaken += 4
print(archer.vacationRemaining)

in order to write to the stored property more modification is necessary. We need to use get & set in the computed property. These are fancy names for code that reads and code that sets.

.

“`swift
var vacationRemaining: Int {
// this stays the same
get {
vacationAllocated – vacationTaken
}

set {
    vacationAllocated = vacationTaken + newValue
}

}

With the getter and setter in place, we can now modify `vacationRemaining`.

swift
var archer = Employee(name: “s a”, vacationAllocated: 14)
archer.vacationTaken += 4
archer.vacationRemaining = 5
print(archer.vacationAllocated)
“`

When to use:

  • If you read the property regularly, when the value hasn’t changed – then use a store property. It will be much faster than using a calculated property
  • If a property is read very rarely, or not at all, then using a computed property saves you from having to calculate its value and store it somewhere

Scored 6/12 on Computed properties.

Note

Kinda annoyed at this test, I feel like the gotchas were a bit excessive. Questioning on past things we learned vs what this module was about. I’ll be a bit more careful next time.

Also

I’m not running the questions in a Swift playground before answering the questions, I’m looking at the code and trying to ascertain what the issues might be or what the answers are.

How to take action when a property changes

A property observer is a piece of code that run when properties change.

They take 2 forms:

  • didSet – runs when the property just changed
  • willSet – runs before the property changed

changed

struct Game {
    var score = 0
}

var game = Game()
game.score += 10
print("Score is now \(game.score)")
game.score -= 3
print("Score is now \(game.score)")
game.score += 1

That creates Game struct, and modifies score. Each time the score changes, we print the value to keep track of it. Except for the last iteration, the bug is the score doesn’t get printed out.

Note: the Swift code is valid. But the missing print statement is a mistake.

Solving with property observers:

struct Game {
    var score = 0 {
        print("Score is now \(score)")
    }
}

var game = Game()
game.score += 10
game.score -= 5
game.score -= 3

Note
Swift provides the constant oldValue inside didSet, in case you need to reference the original value.

willSet provides the constant newValue to reference the value that will be derived from the computed property.

.

“`swift
struct App {
var contacts = [String]() {
willSet {
print(newValue)
}
didSet {
print(oldValue)
}
}
}

var app = App()
app.contacts.append(“Name”)

> Note
> 
> Do not use these observers to do any slow work.
> 
> Most of the time we will use `didSet` more than `willSet` because we want to do something after the property changes & not before.
> 
> Most times `willSet` is used to check app state before some change occurs in the app

Scored 10/12 on property observers.

## How to create custom initializers

Swift silently creates a method called init for every struct that gets instantiated. 

We can also create our own. Syntax:

swift
init(params)

full example of a member-wise initializer:

swift
struct Player {
let name: String
let number: Int
}

let player = Player(name: “Megan R”, number: 15)

Now with custom init func 

swift
struct Player {
let name: String
let number: Int

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

}
“`

Note

  • init is a function that doesn’t use the func keyword
  • init doesn’t return a type, they return the struct they belong to
  • self basically means assign the parameter to the property

With a custom init fun we can initialize the struct properties however we’d like

struct Player {
    let name: String
    let number: Int

    init(name: String) {
        self.name = name
        number = Int.random(in: 1...99)
    }
}

let player = Player(name: "Megan R")
print(player.number)

Remember

All properties in the initializer need to have a value by the time the initializer ends. Swift will not build it otherwise

Your custom initializer takes the place of the default one Swift creates.

If you want to maintain the default member-wise initializer, take the custom init out of the struct and add it to an extension, like so:

struct Employee {
    var name: String
    var yearsActive = 0
}

extension Employee {
    init() {
        self.name = "Anonymous"
        print("Creating an anonymous employee…")
    }
}

// creating a named employee now works
let roslin = Employee(name: "Laura Roslin")

// as does creating an anonymous employee
let anon = Employee()

Scored 10/12 on Initializers
Scored 9/12 on referring to the current instance

Currently
Building

1 product