Options
The swiftier OptionSet
. With the help of enums.
The Problem
Let's say that you have some model in your code, for example named Card
. This model has a property color
which aims to represent its color. Let's also say that all possible card colors are white, blue, black, red, green and all possible combinations of those 5, including none of them.
The standard way this can be handled now in Swift is to create an OptionSet
that has flags assigning all colors the numbers 1, 2, 4, 8, 16, ... (so that they can be easily combined with the bitwise operator |
), like so:
struct CardColor: OptionSet {
let rawValue: Int
init(rawValue: Int) {
self.rawValue = rawValue
}
static let white = CardColor(rawValue: 1 << 0)
static let blue = CardColor(rawValue: 1 << 1)
static let black = CardColor(rawValue: 1 << 2)
static let red = CardColor(rawValue: 1 << 3)
static let green = CardColor(rawValue: 1 << 4)
}
The code snippet above shows the bare minimum for creating an OptionSet
that would suit our needs. Although rather simple and straightforward, it still contains quite some boilerplate for just 5 flags: a stored property, an initializer, as well as all literal raw values for all flags manually written.
Our "swifty" intuition tells us that we can do better, can't we?
The Solution
Create a regular enum with all options. They don't need to have a raw value, the only condition is that the enum should conform to CaseIterable
(because the way they are arranged in the .allCases
array will determine their raw values).
enum CardColor: CaseIterable {
case white
case blue
case black
case red
case green
}
Now in order to use the enum like an OptionSet
we just need to use the Options
struct provided in this library:
let colorless: Options<CardColor> = []
let onlyWhite: Options<CardColor> = [.white]
let bluAndRed: Options<CardColor> = [.blue, .red]
No need for boilerplates and supplying the raw values of all flags.
Extras
The following convenience properties and methods exist:
let allCombined: Options<CardColor> = .all // Equivalent to Options<Topping>([.white, .blue, .black, .red, .green])
let allPossibleBlueColorCombinations = Options<CardColor>.allContaining(.blue) // returns a list of all elements which contain `[.blue]`, sorted ascendingly by their raw values
let favoriteColorCombo: Options<CardColor> = [.white, .blue, .red]
let allPossibleFavoriteColorCombinations = Options<CardColor>.allContaining(favoriteColorCombo) // returns a list of all elements which contain `[.white, .blue, .red]`, sorted ascendingly by their raw values
let sameAsAbove = favoriteColorCombo.allCombinationsContainingSelf
let favoriteComboArray = favoriteColorCombo.decomposed // this is a [CardColor] array containing [.white, .blue, .red]
Limitations
- The square brackets should be present even if creating an instance just by using one option, for example:
Options<CardColor>.all.contains(.white) // this will not compile
Options<CardColor>.all.contains([.white]) // this will solve it
- The supplied
enum
needs to have a maximum of 64 cases.