Advanced and Practical Enum usage in Swift

Methods and Properties

Methods and Properties

Swift enum types can have methods and properties attached to them. This works exactly like you'd do it for class or struct types. Here is a very simple example:

enum Transportation {
  case car(Int)
  case train(Int)

  func distance() -> String {
    switch self {
    case .car(let miles): return "\(miles) miles by car"
    case .train(let miles): return "\(miles) miles by train"
    }
  }
}

The main difference to struct or class types is that you can switch on self within the method in order to calculate the output.

Here is another, more involved, example where we use the enum values to determine the numerical attributes of a character in a method.

enum Wearable {
    enum Weight: Int {
        case light = 1
    }
    enum Armor: Int {
        case light = 2
    }
    case helmet(weight: Weight, armor: Armor)

    func attributes() -> (weight: Int, armor: Int) {
       switch self {
       case .helmet(let w, let a): 
          return (weight: w.rawValue * 2, armor: a.rawValue * 4)
       }
    }
}
let woodenHelmetProps = Wearable.helmet(weight: .light, armor: .light)
    .attributes()

Properties

Enums don't allow for adding stored properties. This means the following does not work:

enum Device {
  case iPad
  case iPhone
  
  let introduced: Int
}

Here, we'd like to store an Apple device together with the year when it was introduced. However, this does not compile.

Even though you can't add actual stored properties to an enum, you can still create computed properties. Their contents, of course, can be based on the enum value or enum associated value. They're read-only though.

enum Device {
  case iPad,
  case iPhone

  var introduced: Int {
    switch self {
    case .iPhone: return 2007
    case .iPad: return 2010
     }
  }
}

This works great as the year of the introduction of an Apple device never changes. You couldn't use this if you'd like to store mutable / changing information. In those cases you'd always use associated values:

enum Character {
  case wizard(name: String, level: Int)
  case warior(name: String, level: Int)
}

Also, you can always still add properties for easy retrieval of the associated value:

extension Character {
  var level: Int {
    switch self {
    case .wizard(_, let level): return level
    case .warior(_, let level): return level
    }
  }
}

Static Methods

You can also have static methods on enums, i.e. in order to create an enum from a non-value type.

Static methods are methods you can call on the name of the type instead of a specific instance of the type. In this example we add a static method to our enum Device which returns the most recently released device:

enum Device {
  static var newestDevice: Device {
    return .appleWatch
  }

  case iPad,
  case iPhone
  case appleWatch
}

Mutating Methods

Methods can be declared mutating. They're then allowed to change the case of the underlying self parameter. Imagine a lamp that has three states: off, low, bright where low is low light and bright a very strong light. We want a function called next that switches to the next state:

enum TriStateSwitch {
    case off, low, bright
    mutating func next() {
        switch self {
        case .off:
            self = low
        case .low:
            self = .bright
        case high:
            self = off
        }
    }
}
var ovenLight = TriStateSwitch.low
ovenLight.next()
// ovenLight is now equal to .bright
ovenLight.next()
// ovenLight is now equal to .off

Before we look at advanced enum usage, we'll do a brief recap of what we've learned in this section so far.