How to Use OptionSet in Swift with code samples
Imagine we're building a to-do app with different task lists. Each list displays tasks using the same UI component but with different features enabled (filter, search, sort).
We need a way to define these options for different screens and conveniently read and change them. We could probably use an enum
with some tweaks, but Swift provides a better alternative: OptionSet
, which was built for exactly this purpose. We'll start our exploration with the OptionSet
and compare it to the enum
approach in the end.
What is OptionSet
OptionSet
is a protocol that represents options as bits (a bitmask). It also gives your type the set‑like operations such as union and intersection. OptionSet
allows you to combine multiple values. It's a great solution whenever you need to provide some options, settings, permissions, styles, etc.
Let's write some code:
struct TasksListOptions: OptionSet {
let rawValue: Int
static let showFilter = TasksListOptions(rawValue: 1 << 0)
static let showSearch = TasksListOptions(rawValue: 1 << 1)
static let showSort = TasksListOptions(rawValue: 1 << 2)
static let showLayoutSelector = TasksListOptions(rawValue: 1 << 3)
}
Each option’s rawValue
is a single bit in the same integer: 1 << 0
is 0001
, 1 << 1
is 0010
, 1 << 2
is 0100
, and so on. When you combine options, Swift merges their bits with the bitwise OR operator (|
), so showFilter
+ showSort
becomes 0101
. This combines multiple options into one compact number, with each bit cleanly representing a single option.
As mentioned, a great feature of OptionSet
is the ability to combine options. One common scenario is to declare default presets with combined values, for example, you could create an all
option to represent all options. In our example, we can create presets for different types of lists:
struct TasksListOptions: OptionSet {
...
static let today: TasksListOptions = [.showFilter, .showSearch]
static let allTasks: TasksListOptions = [.showFilter, .showSearch, .showSort, .showLayoutSelector]
}
Now your view model can use one of these presets to enable or disable some features.
@Observable
final class TasksListViewModel {
private let options: TasksListOptions
}
Working with OptionSet
Since OptionSet
conforms to SetAlgebra
protocol, we can use a lot of operations out of the box like unions, intersections, and subtractions. The most common operations we need are contains(_:)
, insert(_:)
and remove(_:)
. Here is a quick example of how you might use them:
var isSortEnabled: Bool {
options.contains(.showSort)
}
func enable(_ option: TasksListOptions) {
options.insert(option)
}
func disable(_ option: TasksListOptions) {
options.remove(option)
}
As you can see, with the OptionSet
we effectively solved our task of configuring the view model with different options based on the list type. With no extra work from us we were able to check and change our options using a set-like API. All in all OptionSet
is a very effective and appropriate tool for such tasks.
Comparing with Enums
As we see OptionSet
is a great solution, but it's less common than an enum
. So let's explore what it would mean for us to solve the same task using enum
and compare both approaches.
Let's declare an enum with our options:
enum TasksListOptions {
case showFilter
case showSearch
case showSort
case showLayoutSelector
}
The problem arises when we want to combine our options. One approach is to add more cases like case all
, case showFilterAndSearch
, etc. This already seems like not a good idea and it's not scalable. Another approach is to wrap our enum
options into a Set
. We'll need to add a Hashable
conformance to our enum
and then we can do something like this:
@Observable
final class TasksListViewModel {
private var options: Set<TasksListOptions>
}
Next, we'll need to add our presets. This is a bit more involved since we want the convenience of dot notation when assigning them to our options. Here is how it might look like:
enum TasksListOptions: Hashable {
...
static let today: Set<TasksListOptions> = [.showFilter, .showSearch]
static let allTasks: Set<TasksListOptions> = [.showFilter, .showSearch, .showSort, .showLayoutSelector]
}
extension Set where Element == TasksListOptions {
static let today: Set<TasksListOptions> = .today
static let allTasks: Set<TasksListOptions> = .allTasks
}
Since we now work with a set directly, we can use the same API to work with our options:
var isSortEnabled: Bool {
options.contains(.showSort)
}
func enable(_ option: TasksListOptions) {
options.insert(option)
}
func disable(_ option: TasksListOptions) {
options.remove(option)
}
By wrapping our enum
in a Set
, we achieve the same results as with the OptionSet
solution. However, OptionSet
is more efficient and provides all this functionality out of the box. While we can meet our requirements with enum
s, that approach is more involved and there’s no need to reinvent an already existing, effective solution.
Conclusion
Using OptionSet
in Swift gives you a concise, performant way to bundle multiple options into a single value. You get bitmask efficiency, a clear API for combining and testing options, and set operations with no extra boilerplate. While you can emulate the same behaviour with enum
+ Set
, that route is less efficient and requires extra code for presets and operations.
In most cases where you need configuration for features, styles, or permissions, use OptionSet
- it’s the right tool for the job. Only consider the enum
+ Set
pattern if you have a compelling reason to avoid integer bitmasks (for example, when options must be exhaustive, case‑iterable, or carry associated values).
I hope you enjoyed this article. If you have any questions, suggestions, or feedback, please let me know on Twitter.
Thanks for reading!