Skip to main content

Swift Enum Cases

Introduction

Enumerations (enums) are one of Swift's most powerful features, allowing you to define a common type for a group of related values. The individual values within an enumeration are called cases. Enum cases provide a clean and type-safe way to represent different states, options, or variations within a specific domain.

In this tutorial, we'll explore Swift enum cases in depth, including:

  • How to define basic enum cases
  • Working with raw values
  • Using associated values
  • Pattern matching with enum cases
  • Real-world applications of enum cases

Defining Basic Enum Cases

At its simplest, an enum is a list of related named values. Each value is called a "case" of the enumeration.

swift
enum Direction {
case north
case south
case east
case west
}

For brevity, you can also write multiple cases on a single line, separated by commas:

swift
enum Direction {
case north, south, east, west
}

Once an enum is defined, you can create instances of it:

swift
// Create a Direction variable and set it to .east
var heading = Direction.east

// Since the type is known, you can use the shorter dot syntax
heading = .west

Working with Raw Values

Swift allows you to assign underlying raw values to enum cases. Raw values can be strings, characters, or any integer or floating-point type.

String Raw Values

swift
enum CompassDirection: String {
case north = "NORTH"
case south = "SOUTH"
case east = "EAST"
case west = "WEST"
}

// Access the raw value
let directionName = CompassDirection.north.rawValue
print(directionName) // Output: "NORTH"

Integer Raw Values

swift
enum StatusCode: Int {
case success = 200
case notFound = 404
case serverError = 500
}

let successCode = StatusCode.success.rawValue
print(successCode) // Output: 200

Automatic Raw Value Assignment

If you don't specify raw values for string enums, Swift uses the case name as the raw value:

swift
enum Planet: String {
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}

print(Planet.earth.rawValue) // Output: "earth"

For integer enums, if you don't specify the first raw value, Swift starts at 0:

swift
enum Score: Int {
case low, medium, high, perfect
}

print(Score.low.rawValue) // Output: 0
print(Score.medium.rawValue) // Output: 1
print(Score.high.rawValue) // Output: 2
print(Score.perfect.rawValue) // Output: 3

If you specify the first raw value, subsequent values increment from that point:

swift
enum Level: Int {
case beginner = 1, intermediate, advanced, expert
}

print(Level.expert.rawValue) // Output: 4

Creating Enums from Raw Values

You can create an enum instance from its raw value, which returns an optional:

swift
if let status = StatusCode(rawValue: 404) {
print("Found status code: \(status)")
} else {
print("Invalid status code")
}
// Output: Found status code: notFound

// Example of an invalid raw value
if let status = StatusCode(rawValue: 418) {
print("Found status code: \(status)")
} else {
print("Invalid status code")
}
// Output: Invalid status code

Associated Values

One of the most powerful features of Swift enums is the ability to attach associated values to cases. This allows each case to store custom data alongside it.

swift
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}

You can create enum instances with their associated values:

swift
// UPC barcode: system, manufacturer, product, check
let productBarcode = Barcode.upc(8, 85909, 51226, 3)

// QR code containing a website URL
let websiteQR = Barcode.qrCode("https://www.example.com")

The real power comes when extracting these associated values:

swift
func processBarcode(_ barcode: Barcode) {
switch barcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check)")
case .qrCode(let productCode):
print("QR code: \(productCode)")
}
}

processBarcode(productBarcode)
// Output: UPC: 8, 85909, 51226, 3
processBarcode(websiteQR)
// Output: QR code: https://www.example.com

Simplifying the Value Extraction

If all associated values are extracted as constants or all are extracted as variables, you can place a single let or var before the case name:

swift
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check)")
case let .qrCode(productCode):
print("QR code: \(productCode)")
}

Combining Raw Values and Associated Values

It's important to note that an enum can have either raw values or associated values, but not both. They serve different purposes:

  • Raw values: Predefined values of the same type for all cases
  • Associated values: Custom data values that can vary for each instance and can be of different types

Pattern Matching with Enum Cases

Enums work exceptionally well with Swift's pattern matching capabilities, especially in switch statements:

swift
enum Weather {
case sunny(temperature: Double)
case cloudy(coverage: Double) // 0 to 1
case rainy(intensity: Double) // 0 to 1
case snowy(accumulation: Double) // in inches
}

let currentWeather = Weather.rainy(intensity: 0.8)

switch currentWeather {
case .sunny(let temperature) where temperature > 80:
print("It's hot and sunny! Temperature: \(temperature)°F")
case .sunny(let temperature):
print("It's a nice sunny day. Temperature: \(temperature)°F")
case .cloudy(let coverage) where coverage > 0.7:
print("It's very cloudy today, coverage: \(Int(coverage * 100))%")
case .cloudy:
print("It's somewhat cloudy")
case .rainy(let intensity) where intensity > 0.5:
print("Heavy rain, take an umbrella!")
case .rainy:
print("Light rain shower")
case .snowy(let accumulation) where accumulation > 6:
print("Heavy snowfall: \(accumulation) inches")
case .snowy:
print("Light snow")
}
// Output: Heavy rain, take an umbrella!

Practical Applications of Enum Cases

Let's explore some real-world applications of enum cases in Swift:

Network Request Status

swift
enum NetworkResponse {
case success(data: Data)
case failure(error: Error)
case offline
case timeout(after: TimeInterval)
}

func handleResponse(_ response: NetworkResponse) {
switch response {
case .success(let data):
print("Received \(data.count) bytes")
// Process data
case .failure(let error):
print("Request failed: \(error.localizedDescription)")
case .offline:
print("No internet connection available")
case .timeout(let duration):
print("Request timed out after \(duration) seconds")
}
}

// Example usage
let mockData = "Success!".data(using: .utf8)!
handleResponse(.success(data: mockData))
// Output: Received 8 bytes
swift
enum CoffeeStyle {
case regular
case espresso(shots: Int)
case latte(milk: String, foam: Bool)
case cappuccino(chocolate: Bool)
}

func prepareCoffee(_ order: CoffeeStyle) -> String {
switch order {
case .regular:
return "Regular coffee, ready to go!"
case .espresso(let shots):
return "Espresso with \(shots) shot\(shots > 1 ? "s" : "")"
case .latte(let milk, let foam):
return "Latte with \(milk) milk\(foam ? " and foam" : ", no foam")"
case .cappuccino(let chocolate):
return "Cappuccino\(chocolate ? " with chocolate sprinkles" : "")"
}
}

let order1 = CoffeeStyle.espresso(shots: 2)
print(prepareCoffee(order1))
// Output: Espresso with 2 shots

let order2 = CoffeeStyle.latte(milk: "almond", foam: false)
print(prepareCoffee(order2))
// Output: Latte with almond milk, no foam

Result Type

Swift's standard library includes a Result type which is an enum with two cases:

swift
enum CustomResult<Success, Failure: Error> {
case success(Success)
case failure(Failure)
}

// Example of how it's used
func fetchUser(id: Int, completion: @escaping (CustomResult<User, FetchError>) -> Void) {
// Simulating a network request
if id > 0 {
let user = User(id: id, name: "John Doe")
completion(.success(user))
} else {
completion(.failure(.invalidId))
}
}

// Supporting structures
struct User {
let id: Int
let name: String
}

enum FetchError: Error {
case invalidId
case networkError(String)
case serverError(code: Int)
}

// Using it
fetchUser(id: 123) { result in
switch result {
case .success(let user):
print("Fetched user: \(user.name)")
case .failure(let error):
print("Error: \(error)")
}
}
// Output: Fetched user: John Doe

Summary

Swift enum cases are a powerful tool in your programming arsenal, providing:

  1. Type safety: Enum cases create a closed set of possible values
  2. Raw values: Connect enum cases to underlying primitive values
  3. Associated values: Allow cases to store custom data
  4. Pattern matching: Enable powerful control flow with switch statements
  5. Readability: Make your code more expressive and self-documenting

By using enum cases effectively, you can write more concise, safer, and more expressive code. Whether you're representing states, options, or outcomes, enum cases provide a elegant solution for modeling the various aspects of your application domain.

Exercises

  1. Create an enum PaymentMethod with cases for cash, creditCard (with an associated string for the card number), and digitalWallet (with a string for the provider name and a double for the balance).

  2. Define an enum Shape with cases for circle (with radius), square (with side length), and rectangle (with width and height). Add a method to calculate the area of each shape.

  3. Implement an enum Notification with cases for email (with a recipient and message), push (with a device token and message), and sms (with a phone number and message). Write a function that sends a formatted notification based on the type.

  4. Create an enum FileType with raw string values for common file extensions (e.g., "pdf", "jpg", "txt"). Add a static method that takes a file name and returns the appropriate enum case based on the extension.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)