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.
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:
enum Direction {
case north, south, east, west
}
Once an enum is defined, you can create instances of it:
// 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
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
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:
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:
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:
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:
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.
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
You can create enum instances with their associated values:
// 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:
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:
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:
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
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
Menu Options
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:
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:
- Type safety: Enum cases create a closed set of possible values
- Raw values: Connect enum cases to underlying primitive values
- Associated values: Allow cases to store custom data
- Pattern matching: Enable powerful control flow with switch statements
- 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
-
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). -
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. -
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. -
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! :)