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...
PermissionsSwiftUI: A SwiftUI package to handle permissions

PermissionsSwiftUI displays and handles permissions in SwiftUI. It is largely inspired by SPPermissions. The UI is highly customizable and resembles an Apple style. ...

Pager tab strip view

Introduction PagerTabStripView is the first pager view built in pure SwiftUI. It provides a component to create interactive pager views ...

PageView

SwiftUI view enabling page-based navigation, imitating the behaviour of UIPageViewController in iOS.

Pages

    

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 ...