How to create your own structs? How to compute property values dynamically?
  • July 29, 2025

1. Creating Your Own Structs

In Swift, a struct is a value type that you define with the struct keyword. You can give it stored properties, computed properties, initializers, methods, subscripts, and conformances to protocols.

// Define a simple struct
struct Person {
    // Stored properties
    var name: String
    var age: Int

    // Computed property
    var description: String {
        "\(name) is \(age) years old"
    }

    // Custom initializer (optional—Swift generates one automatically if you don't write it)
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    // Instance method
    func greet() {
        print("Hello, my name is \(name).")
    }
}

// Usage
var alice = Person(name: "Alice", age: 30)
print(alice.description)  // Alice is 30 years old
alice.greet()             // Hello, my name is Alice.

2. Optional Deep Dives

2.1 What’s the Difference Between a Struct and a Tuple?

  • Struct
    • Named type you define once; can have multiple properties, methods, initializers, protocol conformance, etc.
    • Members are accessed by name: person.name, person.age.
    • Value semantics: when you assign or pass a struct, you get a copy.
  • Tuple
    • Ad-hoc grouping of values; no custom behavior or methods.
    • Can have named or unnamed elements:
      let point = (x: 3, y: 4)
      print(point.x, point.y)
      let pair = ("hello", 42)
      print(pair.0, pair.1)
      
    • Light-weight, but no extensions, no methods, no protocol conformance.

2.2 What’s the Difference Between a Function and a Method?

  • Function
    • A standalone block of code defined at global or local scope.
    func add(_ a: Int, _ b: Int) -> Int {
        return a + b
    }
    
  • Method
    • A function that’s attached to a type (struct, class, enum).
    struct Calculator {
        func multiply(_ a: Int, _ b: Int) -> Int {
            return a * b
        }
    }
    

Methods have access to self and can read or modify the instance’s properties (if allowed).


2.3 Why Do We Need to Mark Some Methods as mutating?

Because structs (and enums) are value types, methods that change their stored properties must be marked mutating. This signals that the method will modify the instance itself, rather than just reading from it.

struct Counter {
    var count: Int = 0

    // Without `mutating` this would be a compile-time error:
    mutating func increment() {
        count += 1
    }
}

var c = Counter()
c.increment()
print(c.count)  // 1

Attempting to modify count inside a non-mutating method would not compile.


3. Tests

3.1 Test: Structs

// Define a struct Point and test its properties
struct Point {
    var x: Int
    var y: Int

    func movedBy(dx: Int, dy: Int) -> Point {
        Point(x: x + dx, y: y + dy)
    }
}

// Test creation and method
let p = Point(x: 2, y: 3)
let p2 = p.movedBy(dx: 5, dy: -1)
assert(p2.x == 7 && p2.y == 2)
print("Test Passed: Point moved correctly to (\(p2.x), \(p2.y))")

Expected output:

Test Passed: Point moved correctly to (7, 2)

3.2 Test: Mutating Methods

// Define a struct BankAccount with a mutating deposit
struct BankAccount {
    var balance: Double

    mutating func deposit(_ amount: Double) {
        balance += amount
    }
}

// Test the mutating method
var account = BankAccount(balance: 100.0)
account.deposit(50.0)
assert(account.balance == 150.0)
print("Test Passed: balance after deposit is \(account.balance)")

Expected output:

Test Passed: balance after deposit is 150.0


1. Computing Property Values Dynamically with Computed Properties

A computed property doesn’t store a value directly. Instead, it defines a getter (and optionally a setter) that calculates its value on the fly based on other properties or logic.

Syntax

struct Rectangle {
    var width: Double
    var height: Double

    // Computed property
    var area: Double {
        return width * height
    }

    // Computed property with setter
    var perimeter: Double {
        get {
            return 2 * (width + height)
        }
        set(newPerimeter) {
            // Adjust width proportionally (for example purposes)
            let ratio = width / (width + height)
            let half = newPerimeter / 2
            width  = half * ratio
            height = half * (1 - ratio)
        }
    }
}
  • Getter only: omit get { … } if it’s a single expression.
  • Getter + Setter: include set { … } or set(newValue) { … }.

2. When to Use Computed vs Stored Properties (Optional)

  • Stored Property
    • Use when you need to persist a value in memory.
    • Good for data that doesn’t need to be recalculated each time it’s accessed.
    • Example: a user’s name, a configuration flag.
  • Computed Property
    • Use when the value depends on other properties or external state.
    • Avoids duplication and keeps related logic in one place.
    • Example: area of a shape, fullName combining firstName and lastName.

Rule of thumb: if you find yourself writing didSet or willSet to maintain another property in sync, consider making that other property a computed one instead.


3. Test: Computed Properties

Paste this into a Swift playground or project to verify your computed properties work as expected:

// Define a struct with computed properties
struct Circle {
    var radius: Double

    // Computed area
    var area: Double {
        Double.pi * radius * radius
    }

    // Computed diameter (getter + setter)
    var diameter: Double {
        get {
            radius * 2
        }
        set {
            radius = newValue / 2
        }
    }
}

// Test 1: area calculation
let c1 = Circle(radius: 3)
let expectedArea = Double.pi * 9
assert(abs(c1.area - expectedArea) < 1e-10)
print("Test 1 Passed: area is \(c1.area)")

// Test 2: diameter getter
assert(c1.diameter == 6)
print("Test 2 Passed: diameter getter == \(c1.diameter)")

// Test 3: diameter setter updates radius
var c2 = Circle(radius: 1)
c2.diameter = 10
assert(abs(c2.radius - 5) < 1e-10)
print("Test 3 Passed: radius after setting diameter == \(c2.radius)")

Expected output:

Test 1 Passed: area is 28.2743338823081
Test 2 Passed: diameter getter == 6.0
Test 3 Passed: radius after setting diameter == 5.0
YOU MIGHT ALSO LIKE...
How to take action when a property changes

1. Taking Action When a Property Changes: Property Observers Swift lets you observe and respond to changes in a property’s ...

How to use trailing closures and shorthand syntax?

1. Trailing Closure Syntax When the last parameter to a function is a closure, you can write that closure after ...

How to create and use closures?

1. What Is a Closure (and Why Swift Loves Them) A closure in Swift is a self-contained block of functionality ...

How to provide default values for parameters How to handle errors in functions

1. Providing Default Values for Function Parameters (Deep Dive) 1.1 Syntax and Ordering Declaration You assign a default right in ...

exyte

Concentric Onboarding iOS library for a walkthrough or onboarding flow with tap actions written with SwiftUI         Usage Create View's ...