January 22nd, 2026

The Problem with Enums

With SwiftUI, it's possible to create your own alignment. Here's one that positions itself vertically at a third of a view's height.

extension VerticalAlignment {
    public static let third = VerticalAlignment(ThirdAlignmentId.self)
 
    enum ThirdAlignmentId: AlignmentID {
        static func defaultValue(in context: ViewDimensions) -> CGFloat {
            context.height / 3
        }
    }
}

You can then use our alignment with an HStack.

HStack(alignment: .third) {
    Text("Hello")
        .frame(height: 300)
    Text("World!")
}

This is super clever and I think it exposes a really good design case here. For years, I believed that alignments were enums. And this was because of their simplicity of use and their .something syntax.

In reality, SwiftUI's alignment system uses a polymorphism system to benefit from composition, extensibility, and decentralization.

Enums Don't Compose

In SwiftUI, there are three types of alignment:

  • VerticalAlignment
  • HorizontalAlignment
  • Alignment

This allows you to specify only the right type of alignment that you expect and detect errors at compile time.

// Doesn't make sense
// Will be caught at compile time
VStack(alignment: .bottom)

However, ZStack accepts a vertical alignment, or horizontal, or both! And the .topLeading syntax made me think for a long time that Alignment was an enum. But if that had been the case, there would have been some duplicated code somewhere because it's impossible to combine enums.

enum VerticalAlignment {
    case leading
    // ...
}
 
enum HorizontalAlignment {
    case top
    // ...
}
 
enum Alignment {
    case leading // duplicated?
    case topLeading // combined?
    // ...
}

Instead, Alignment is a struct that accepts a vertical and horizontal alignment.

struct Alignment {
    let vertical: VerticalAlignment
    let horizontal: HorizontalAlignment
}

To continue benefiting from the .topLeading syntax, SwiftUI exposes static properties that enjoy the same syntax.

extension Alignment {
    static let topLeading = Alignment(
        horizontal: .top,
        vertical: .top
    )
}

Enums Aren't Extensible

In Swift, it's impossible to add new cases to an enum because, fundamentally, enums represent a finite series of states.

If SwiftUI exposed ShapeStyle as an enum, it would be impossible for developers to create their own style.

enum ShapeStyle {
    case rect
    case circle
}

With the struct + protocol combo, it becomes possible to create new shapes outside of what SwiftUI exposes.

protocol ShapeStyle {}
 
struct Rect: ShapeStyle {}
struct Circle: ShapeStyle {}

And it's still possible to keep the .something enum syntax by using constrained extensions.

extension ShapeStyle where Self == Rect {
    static var rect: Self { Rect() }
}

Enums Are Centralized

To add logic to enums, you can use associated values and computed properties.

enum Shape {
    case rect(width: Double, height: Double)
    case circle(radius: Double)
 
    var area: Double {
        switch shape {
        case let .circle(r):
            return .pi * r * r
        case let .rectangle(w, h):
            return w * h
        }
    }
}

While this might be okay for such a simple implementation, SwiftUI's shapes are much more complex and such use leads to anti-patterns. You can't create a computed property to calculate the diameter of a circle without returning a default value for the rectangle.

enum Shape {
    case rect(width: Double, height: Double)
    case circle(radius: Double)
 
    var diameter: Double {
        switch shape {
        case let .circle(r):
            return r * 2
        case let .rectangle(w, h):
            return 0 // makes no sense
        }
    }
}

This return 0 reminds me of an abstract class where you return a default value for values meant to be overridden.

By using polymorphism, it's entirely possible to decentralize and create specific cases for each shape.

protocol Shape {
    var area: Double { get }
}
 
struct Circle: Shape {
    let radius: Double
 
    var area: Double {
        .pi * radius * radius
    }
 
    // This is possible and won't be implemented in Rect
    var diameter: Double {
        radius * 2
    }
}

Constrained extensions work here too to keep the .something syntax.

extension Shape where Self == Circle {
    static func circle(radius: Double) -> Self {
        Circle(radius: radius)
    }
}

Enums Are Too Simple

Enums are the choice to favor in most cases. But when you need to compose, extend, or decentralize, it's imperative to consider another approach.

We love the .something syntax of enums. But it's not exclusive to them! Constrained extensions and static properties also benefit from this behavior.

looping arrowfollow me here!