Skip to content

How to use inits and enums in SwiftUI | SwiftUI Bootcamp #13

Custom init and enum are the tools that turn hardcoded views into reusable components. After this lesson you'll know how to build a configurable SwiftUI view that accepts a typed parameter, derives its own properties from that type, and can never be put into an invalid state.

What You'll Learn

  • How to write a custom init on a SwiftUI View struct to derive stored properties from parameters
  • Why enum is better than Bool or String for expressing a view's configuration options
  • How Swift's exhaustive if/switch on an enum eliminates entire categories of runtime bugs

Mental Model

Think of a custom init + enum as a vending machine interface. Rather than accepting arbitrary input (a string like "apple" or "orange"), you offer a sealed menu of valid choices — the enum cases. The customer presses button A (.apple) or button B (.orange), and the machine internally figures out what to dispense (the title, the background color). The customer never needs to know about those internal derivations, and you can never accidentally order something that doesn't exist.

Without an enum, you might write init(count: Int, fruitName: String) — but what happens if someone passes "banana"? With an enum, impossible input is impossible to express in code, and the compiler enforces this at every call site.

Detailed Explanation

In Swift, view structs get a free memberwise initializer, but it exposes all stored properties. A custom init lets you present a simpler, intentional interface — callers provide count and a Fruit enum case, and the init derives title and backgroundColor from those inputs. The caller never has to know about those implementation details.

Enums in Swift are first-class types. An enum case in SwiftUI is particularly powerful because Swift guarantees exhaustive matching — every if or switch on a Fruit must cover all cases. This means if you add a new fruit later, the compiler immediately flags every if/switch that needs to handle it. You can never silently forget to handle a new case.

Placing the enum inside the view struct (as a nested type) is a clean pattern for view-specific types. It signals clearly that Fruit is an implementation detail of InitializerBootcamp, not a general-purpose type used elsewhere. At the call site, callers use the .apple shorthand without needing to write InitializerBootcamp.Fruit.apple.

The custom init is also where you can validate inputs, apply transformations, and set default values — things the automatic memberwise initializer can't do.

Code Structure

The sample is in InitializerBootcamp.swift and defines a single view with a nested Fruit enum and a custom init. The preview instantiates the view twice — once for apples, once for oranges — side by side in an HStack to demonstrate how the same view component produces two distinct visual outcomes from a single typed parameter.

Complete Code

InitializerBootcamp.swift

swift
import SwiftUI

struct InitializerBootcamp: View {
    
    let backgroundColor: Color // derived from the fruit type; callers don't set this directly
    let count: Int
    let title: String           // derived from the fruit type; callers don't set this directly
    
    init(count: Int, fruit: Fruit) { // public interface: just a count and a fruit choice
        self.count = count
        
        if fruit == .apple {  // derives the internal properties from the enum case
            self.title = "Apples"
            self.backgroundColor = .red
        } else {
            self.title = "Oranges"
            self.backgroundColor = .orange
        }
    }
    
    enum Fruit { // nested enum — scoped to this view, not exposed as a global type
        case apple
        case orange
    }
    
    var body: some View {
        VStack(spacing: 12) {
            Text("\(count)")
                .font(.largeTitle)
                .foregroundColor(.white)
                .underline() // underline to visually highlight the number
            
            Text(title)
                .font(.headline)
                .foregroundColor(.white)
        }
        .frame(width: 150, height: 150) // fixed square frame for the stat tile
        .background(backgroundColor)    // the color is set by the init based on fruit choice
        .cornerRadius(10)               // rounds the tile's corners to look like a card
    }
}

struct InitializerBootcamp_Previews: PreviewProvider {
    static var previews: some View {
        HStack {
            InitializerBootcamp(count: 100, fruit: .apple)  // red tile with "Apples"
            InitializerBootcamp(count: 46, fruit: .orange)  // orange tile with "Oranges"
        }
    }
}

Code Walkthrough

  1. Stored propertiesbackgroundColor, count, and title are let constants because they're set once in init and never change. Making them immutable communicates that this view is a pure function of its inputs.
  2. init(count:fruit:) — The public API. Callers only need to know "how many" and "which fruit." The view's internal styling logic is hidden inside the init, following the encapsulation principle.
  3. if fruit == .apple — Swift's enum equality comparison. For enums with no associated values (like this one), == just checks the case. For more complex branching, use switch fruit { case .apple: ... case .orange: ... } for exhaustive coverage.
  4. enum Fruit nested inside the struct — Nesting is a Swift best practice for types that are only meaningful in the context of their parent. The PreviewProvider calls InitializerBootcamp(count: 100, fruit: .apple) — the .apple shorthand works because Swift infers the Fruit type from the parameter.
  5. .background(backgroundColor) — The background color is whatever the init derived. The body has no conditional logic about fruit types — it just uses the pre-computed stored property. This separation of "configuration logic" (in init) from "display logic" (in body) is a key SwiftUI architecture principle.
  6. Preview with HStack — Shows two instances side by side to visually confirm that the same view struct produces correctly differentiated output based on the fruit parameter.

Common Mistakes

Mistake: Using String parameters for configuration instead of enuminit(count: Int, fruitName: String) compiles fine but accepts any string — including "banana", "", or even a SQL injection string. An enum makes invalid input unrepresentable. If you catch yourself writing if fruitName == "apple", convert the parameter to an enum.

Mistake: Computing derived values in body instead of in init Putting let color = fruit == .apple ? Color.red : Color.orange inside body works, but it re-runs on every render and the logic is mixed with layout code. Computing in init runs once and stores the result cleanly. For complex derivation logic, init is always the right place.

Mistake: Forgetting to assign all stored properties in the custom init Swift requires all let properties to be assigned before init returns. If you add a new stored property without updating the init, the compiler will catch it. This is a feature, not a limitation — it prevents partially initialized views.

Key Takeaways

  • Use a custom init to derive stored properties from typed inputs, hiding implementation details from callers
  • Enums make invalid states unrepresentable — prefer them over Bool or String parameters for view configuration options
  • Placing the enum as a nested type inside the view struct communicates that it's scoped to that view and improves call-site readability

Last updated: June 27, 2026

Released under the MIT License.