Blog

Switching with Associated Values

Enums are a powerful tool, allowing you to create expressive and precise APIs. However, extracting associated values with a switch statement sometimes feels a little clunky.

Consider the following example:

enum Layout {
    case view(width: CGFloat)
    case space(width: CGFloat)
    case newline
}

Here, we’re modelling a UI layout: we can place either a view or a space on a line, both of which have an associated value for their width, or break to a new line. If we want to compute the total width of views and spaces during layout calculations, we might write something like this:

switch layout {
case let .view(width):
    currentX += width
case let .space(width):
    currentX += width
case .newline:
    break
}

Alternatively, as long as the variables we’re binding in the cases match up, we can combine the cases and write this in a more concise way:

switch layout {
case let .view(width), let .space(width):
    currentX += width
case .newline:
    break
}

This will also work with nested enums. For example, we might want to support different kinds of widths in our layout, such as absolute widths and flexible widths:

enum Width {
    case absolute(CGFloat)
    case flexible
}

enum Layout {
    case view(Width)
    case space(Width)
    case newline
}

To calculate the minimum width of a line, we want to extract all the absolute width values. We could achieve this by writing an unwieldy switch statement, duplicating much code:

switch layout {
case let .view(width):
    switch width {
    case let .absolute(x):
        currentX += x
    case .flexible:
        break
    }
case let .space(width):
    switch width {
    case let .absolute(x):
        currentX += x
    case .flexible:
        break
    }
case .newline:
    break
}

Instead, we can express the same logic in a more elegant way:

switch layout {
case let .view(.absolute(x)), let .space(.absolute(x)):
    currentX += x
case .view(.flexible), .space(.flexible), .newline:
    break
}

This technique can result in unwanted complexity, especially where we need to describe many patterns in a single case. If we add a minimum width value to the .flexible case, we already have to specify four different patterns:

enum Width {
    case absolute(CGFloat)
    case flexible(minimum: CGFloat)
}

switch layout {
case let .view(.absolute(x)), let .view(.flexible(x)), let .space(.absolute(x)), let .space(.flexible(x)):
    currentX += x
case .newline:
    break
}

We can reduce this complexity by using a computed property to separate the matching on the Layout enum from the matching on the Width enum:

extension Width {
    var minimum: CGFloat {
        switch self {
        case let .absolute(x), let .flexible(x): return x
        }
    }
}

switch layout {
case let .view(width), let .space(width):
    currentX += width.minimum
case .newline:
    break
}

This example is inspired by an upcoming Swift Talk series, where we build an adaptive layout library. Our goal is to define layouts in a way that makes it easy to accommodate large variations in screen size, font size, and so on.

Here’s a little preview:


Stay up-to-date with our newsletter or follow us on Twitter.

Back to the Blog

recent posts