Skip to main content

Swift Enum Properties

Introduction

Enumerations in Swift are more powerful than in many other programming languages. One of the features that makes Swift enums so versatile is their ability to have properties, including computed properties, methods, and initializers. This enables enums to go beyond simply being a collection of related values and instead function as full-fledged types with behavior and functionality.

In this tutorial, we'll explore how to add properties to Swift enums, how they work, and practical use cases that demonstrate their power in real-world applications.

What are Enum Properties?

Enum properties allow you to associate functionality with enumeration types. Unlike stored properties (which enums cannot have), computed properties in enums calculate values rather than storing them. Enums can have:

  1. Computed properties
  2. Methods
  3. Initializers
  4. Conform to protocols

These features make enums in Swift much more powerful than traditional C-style enumerations.

Computed Properties in Enums

Since enums can't have stored properties (because they represent a single value, not a container of values), we use computed properties to add functionality.

Basic Syntax

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

var description: String {
switch self {
case .north:
return "Heading North"
case .south:
return "Heading South"
case .east:
return "Heading East"
case .west:
return "Heading West"
}
}
}

let heading = Direction.north
print(heading.description) // Output: "Heading North"

In this example, the description property computes a string value based on the enum case. When we access heading.description, the property's getter runs the switch statement and returns the appropriate string.

Methods in Enums

You can also add methods to enums, which allows them to have behaviors.

swift
enum Measurement {
case centimeters(Double)
case inches(Double)

func toCentimeters() -> Double {
switch self {
case .centimeters(let value):
return value
case .inches(let value):
return value * 2.54
}
}

func toInches() -> Double {
switch self {
case .centimeters(let value):
return value / 2.54
case .inches(let value):
return value
}
}
}

let length = Measurement.inches(10)
print(length.toCentimeters()) // Output: 25.4
print(length.toInches()) // Output: 10.0

let width = Measurement.centimeters(30)
print(width.toInches()) // Output: 11.811023622047244

In this example, the Measurement enum has methods that convert between different units of measurement, making unit conversion intuitive and type-safe.

Mutating Methods

Since enums are value types, methods that need to modify self must be marked as mutating.

swift
enum ToggleSwitch {
case on
case off

mutating func toggle() {
switch self {
case .on:
self = .off
case .off:
self = .on
}
}
}

var lightSwitch = ToggleSwitch.off
print("Light is \(lightSwitch)") // Output: Light is off

lightSwitch.toggle()
print("Light is \(lightSwitch)") // Output: Light is on

lightSwitch.toggle()
print("Light is \(lightSwitch)") // Output: Light is off

The toggle() method changes the enum value from .on to .off or vice versa. Without the mutating keyword, this code would not compile.

Static Properties and Methods

Enums can also have static properties and methods that apply to the enum type itself rather than instances.

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

static var allPlanets: [Planet] {
return [.mercury, .venus, .earth, .mars,
.jupiter, .saturn, .uranus, .neptune]
}

static func isPlanet(name: String) -> Bool {
switch name.lowercased() {
case "mercury", "venus", "earth", "mars",
"jupiter", "saturn", "uranus", "neptune":
return true
default:
return false
}
}

var distanceFromSun: Double {
switch self {
case .mercury: return 57.9
case .venus: return 108.2
case .earth: return 149.6
case .mars: return 227.9
case .jupiter: return 778.3
case .saturn: return 1427.0
case .uranus: return 2871.0
case .neptune: return 4497.1
}
}
}

print(Planet.allPlanets.count) // Output: 8
print(Planet.isPlanet(name: "Pluto")) // Output: false
print(Planet.earth.distanceFromSun) // Output: 149.6

In this example, allPlanets is a static computed property that returns an array of all planet enum cases. The isPlanet(name:) method is a static method that checks if a string represents a valid planet name.

Initializers in Enums

Enums can have initializers that create enum instances based on input parameters.

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

init?(degree: Int) {
switch degree {
case 0, 360:
self = .north
case 90:
self = .east
case 180:
self = .south
case 270:
self = .west
default:
return nil
}
}
}

if let direction = Direction(degree: 90) {
print("Heading: \(direction)") // Output: Heading: east
} else {
print("Invalid direction")
}

if let direction = Direction(degree: 45) {
print("Heading: \(direction)")
} else {
print("Invalid direction") // Output: Invalid direction
}

Here, the initializer is failable (marked with ?), which means it can return nil if the provided degree doesn't correspond to one of the cardinal directions.

Raw Values and Computed Properties

You can combine raw values with computed properties to create powerful enumerations.

swift
enum HTTPStatus: Int {
case ok = 200
case created = 201
case accepted = 202
case badRequest = 400
case unauthorized = 401
case forbidden = 403
case notFound = 404
case serverError = 500

var isSuccess: Bool {
return self.rawValue < 300
}

var description: String {
switch self {
case .ok: return "OK"
case .created: return "Created"
case .accepted: return "Accepted"
case .badRequest: return "Bad Request"
case .unauthorized: return "Unauthorized"
case .forbidden: return "Forbidden"
case .notFound: return "Not Found"
case .serverError: return "Internal Server Error"
}
}

var responseMessage: String {
if isSuccess {
return "Success: \(description) (\(rawValue))"
} else {
return "Error: \(description) (\(rawValue))"
}
}
}

let status = HTTPStatus.ok
print(status.isSuccess) // Output: true
print(status.responseMessage) // Output: Success: OK (200)

let errorStatus = HTTPStatus.notFound
print(errorStatus.isSuccess) // Output: false
print(errorStatus.responseMessage) // Output: Error: Not Found (404)

This example shows how computed properties can derive values from the raw value and provide additional functionality to make the enum more usable.

Real-world Applications

Let's look at some practical examples of using enum properties in real-world scenarios.

Example 1: Currency Converter

swift
enum Currency {
case usd(Double)
case eur(Double)
case gbp(Double)
case jpy(Double)

var usdValue: Double {
switch self {
case .usd(let amount):
return amount
case .eur(let amount):
return amount * 1.09 // Conversion rate as of 2023
case .gbp(let amount):
return amount * 1.25 // Conversion rate as of 2023
case .jpy(let amount):
return amount * 0.0067 // Conversion rate as of 2023
}
}

func convert(to currency: CurrencyType) -> Currency {
let usdAmount = self.usdValue

switch currency {
case .usd:
return .usd(usdAmount)
case .eur:
return .eur(usdAmount / 1.09)
case .gbp:
return .gbp(usdAmount / 1.25)
case .jpy:
return .jpy(usdAmount / 0.0067)
}
}

var formattedString: String {
switch self {
case .usd(let amount):
return "$\(String(format: "%.2f", amount))"
case .eur(let amount):
return "€\(String(format: "%.2f", amount))"
case .gbp(let amount):
return \(String(format: "%.2f", amount))"
case .jpy(let amount):
return \(String(format: "%.0f", amount))"
}
}

enum CurrencyType {
case usd, eur, gbp, jpy
}
}

let euros = Currency.eur(100)
print(euros.formattedString) // Output: €100.00

let dollars = euros.convert(to: .usd)
print(dollars.formattedString) // Output: $109.00

let yen = euros.convert(to: .jpy)
print(yen.formattedString) // Output: ¥16269

This currency enum demonstrates how methods, computed properties, and nested enums can create a comprehensive domain model with useful functionality.

Example 2: Card Game

swift
enum Card {
enum Suit: String, CaseIterable {
case hearts, diamonds, clubs, spades

var symbol: String {
switch self {
case .hearts: return "♥️"
case .diamonds: return "♦️"
case .clubs: return "♣️"
case .spades: return "♠️"
}
}

var color: String {
switch self {
case .hearts, .diamonds:
return "Red"
case .clubs, .spades:
return "Black"
}
}
}

enum Rank: Int, CaseIterable {
case two = 2, three, four, five, six, seven, eight, nine, ten
case jack, queen, king, ace

var symbol: String {
switch self {
case .jack: return "J"
case .queen: return "Q"
case .king: return "K"
case .ace: return "A"
default: return "\(self.rawValue)"
}
}
}

case card(rank: Rank, suit: Suit)

var description: String {
switch self {
case .card(let rank, let suit):
return "\(rank.symbol)\(suit.symbol)"
}
}

var value: Int {
switch self {
case .card(let rank, _):
return rank.rawValue
}
}

static var fullDeck: [Card] {
var deck = [Card]()
for suit in Suit.allCases {
for rank in Rank.allCases {
deck.append(.card(rank: rank, suit: suit))
}
}
return deck
}
}

let myCard = Card.card(rank: .ace, suit: .spades)
print(myCard.description) // Output: A♠️
print(myCard.value) // Output: 14

print("Number of cards in a deck: \(Card.fullDeck.count)") // Output: 52

This card game example shows how nested enums with properties can create a rich domain model. The Suit and Rank nested enums have their own properties, while the main Card enum combines them and adds additional functionality.

Summary

Swift enum properties significantly enhance the power and flexibility of enumerations:

  • Computed properties allow enums to derive values based on cases
  • Methods provide behavior related to the enum
  • Mutating methods can change the enum value
  • Static properties and methods apply to the enum type itself
  • Initializers enable flexible enum instance creation
  • Combining raw values with computed properties creates powerful API

By leveraging these features, Swift enums can be transformed from simple value types to comprehensive domain models that encapsulate both data and behavior. This makes your code more expressive, type-safe, and maintainable.

Exercises

  1. Create a TrafficLight enum with cases red, yellow, and green. Add a mutating method advance() that changes the light to the next color in the cycle.

  2. Create a Shape enum with cases for different shapes (circle, square, triangle). Add computed properties to calculate area and perimeter for each shape.

  3. Implement a DayOfWeek enum with cases for each day. Add computed properties to check if it's a weekday or weekend, and methods to get the next and previous days.

  4. Create a Temperature enum with cases for Celsius and Fahrenheit. Add methods to convert between the two and a computed property to determine if it's freezing or not.

Further Resources

Happy coding!



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