Easy SwiftUI View bindings Using OptionSet and Sourcery
Tags: swift, ios, swiftui, sourcery
Published: 2021-05-07 08:32:16

![Photo by Alexander Sinn](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jxeeewnpoj19563ntfwv.jpg)
  

Have you ever had a SwiftUI view with lots of toggles?

```swift
struct LotsOfStateView: View {
	//1
	@State var showBackground = false
	//2
	@State var showBorder = false
	//3
	@State var reduceMotionEnabled = false
	//4
	@State var largeFont = false
	//5
	@State var receiveEmailNotifications = false

	var body: some View {
		VStack {
			Toggle(isOn: $showBackground) {
				Text(LocalizedStringKey("Show Background"))
			}
			Toggle(isOn: $showBorder) {
				Text(LocalizedStringKey("Show Border"))
			}
			Toggle(isOn: $reduceMotionEnabled) {
				Text(LocalizedStringKey("Reduce Motion Enabled"))
			}
			Toggle(isOn: $largeFont) {
				Text(LocalizedStringKey("Large Font"))
			}
			Toggle(isOn: $receiveEmailNotifications) {
				Text(LocalizedStringKey("Receive Notifications"))
			}
		}
		.padding()
		.font(.system(size: 22, weight: .light, design: .rounded))
	}
}
```

Dealing with those toggles is pretty straight forward task, but it can become cumbersome the more you add.

A technique of reducing the amount of Bool values is to compact them into one storage unit. We can essentially convert them into a series of zeros and ones. Sounds familiar? 

> There are only 10 types of people in the world – those who understand binary, and those who don’t.

So if we were to convert the @State above to something more manageable we could use Swift's `OptionSet`. They are very similar to `enums`.

I'm sure you have set a reminder before. If you set the frequency of the reminder (Monday, Wednesday and Saturday) for example. You are essentially creating a set of 3 values.

This basic idea of being able to create a combination of desired values is perfect to remove the amount of boilerplate state we have in your view.

If we were to convert the above @State into an OptionSet we could do this: 

```swift
struct Options: OptionSet {
	var rawValue: UInt
	static let none = Self([])
	static let showBackground = Self(rawValue: 1 << 0)
	static let showBorder = Self(rawValue: 1 << 1)
	static let reduceMotionEnabled = Self(rawValue: 1 << 2)
	static let largeFont = Self(rawValue: 1 << 3)
	static let receiveEmailNotifications = Self(rawValue: 1 << 4)
}
```

Then in our views we could remove all the @State in favour of one.

```swiftui
@State var viewOptions = Options.none
```

If you look at the rawValue of the OptionSet, it's an UInt. You are essentially storing the union of values into 1 single storage value. (Series of 101001, on/off)

This is what your view would look like.

```swift
struct LessStateVariablesView: View {
	@State var viewOptions = Options.none

	var body: some View {
		ZStack {
			if $viewOptions.bindValue(.showBackground) {
				Color.red.edgesIgnoringSafeArea(.all)
			}
			VStack {
				Toggle(isOn: $viewOptions.bind(.showBackground)) {
					Text(LocalizedStringKey("Show Background"))
				}
				Toggle(isOn: $viewOptions.bind(.showBorder)) {
					Text(LocalizedStringKey("Show Border"))
				}
				Toggle(isOn: $viewOptions.bind(.reduceMotionEnabled)) {
					Text(LocalizedStringKey("Reduce Motion Enabled"))
				}
				Toggle(isOn: $viewOptions.bind(.largeFont)) {
					Text(LocalizedStringKey("Large Font"))
				}
				Toggle(isOn: $viewOptions.bind(.receiveEmailNotifications)) {
					Text(LocalizedStringKey("Receive Notifications"))
				}
			}
			.padding()
			.font(.system(size: 22, weight: .light, design: .rounded))
		}
	}
}
```

As you can see, we've shifted all the previous @State and encapsulated into an OptionSet. The behaviour is exactly the same but instead of having 5 bools, we have 1 and all the true and false is stored as 1 single UInt.

#Converting an OptionSet into Binding<T??>

Now that we have our options, we need a way of interacting with them in our SwiftUI View. If you wanted to use a toggle for example we need a `Binding`.

> A property wrapper type that can read and write a value owned by a source of truth.

Binding is a struct with a Generic value `Value`, so we could write an extension in order to interact with our new OptionSet.

```swift
extension Binding where Value: OptionSet, Value == Value.Element {
	func bindedValue(_ options: Value) -> Bool {
		return wrappedValue.contains(options)
	}

	func bind(
		_ options: Value,
		animate: Bool = false
	) -> Binding<Bool> {
		return .init { () -> Bool in
			self.wrappedValue.contains(options)
		} set: { newValue in
			let body = {
				if newValue {
					self.wrappedValue.insert(options)
				} else {
					self.wrappedValue.remove(options)
				}
			}
			guard animate else {
				body()
				return
			}
			withAnimation {
				body()
			}
		}
	}
}
```

By doing so we can now plug it in our SwiftUI view.

We have now access to `bindedValue` to pull out its Bool representation and also `bind` where you can pass to the Toggle to update its value. There is also a convenient `animate` option in order to animate its changes.

# Removing Boiler Plate (Sourcery to the rescue)

Writing an OptionSet is fairly trivial, but I often forget the syntax and the correct `rawValue` needed for each item.

I have created an `Stencil` template file which can be used with Sourcery in order to make creating those OptionSet very simple and easy.

> Sourcery is a code generator for Swift language, built on top of Apple's own SwiftSyntax. It extends the language abstractions to allow you to generate boilerplate code automatically.
[Sourcery](https://github.com/krzysztofzablocki/Sourcery)

What we can do now is to create an enum with your cases and make sure you conform to `OptionsBinding`.

```swift
protocol OptionsBinding {}

enum MyEnum: OptionsBinding {
	case showBackground
	case showBorder
	case reduceMotionEnabled
	case largeFont
	case receiveEmailNotifications
}
```

Sourcery will then extend that enum and add the equivalent OptionSet for you.

```swift
extension MyEnum {
	struct Options: OptionSet {
		var rawValue: UInt
		static let none = Self([])
		static let showBackground = Self(rawValue: 1 << 0)
		static let showBorder = Self(rawValue: 1 << 1)
		static let reduceMotionEnabled = Self(rawValue: 1 << 2)
		static let largeFont = Self(rawValue: 1 << 3)
		static let receiveEmailNotifications = Self(rawValue: 1 << 4)
	}
}
```

Now you are free to use MyEnum.Options wherever you need.

I hope this will help some folks to reduce some of the Bool boilerplate in your views and also make it easy to reason with your options by having a nice enum to use.

# Stencil template

```stencil
{% for type in types.enums where type.cases.count > 0 and type.based.OptionsBinding or type|annotated:"OptionsBinding" %}
extension {{ type.name }} {
	struct Options: OptionSet {
		var rawValue: UInt
		static let none = Self([])
		{% for i in 0...type.cases.count %}
			{% if not forloop.last %}
		static let {{type.cases[i].name}} = Self(rawValue: 1 << {{i}})
	  {% endif %}
	{% endfor %}
	}
}
{% endfor %}

extension Binding where Value: OptionSet, Value == Value.Element {
	func bindValue(_ options: Value) -> Bool {
		return wrappedValue.contains(options)
	}

	func bind(
		_ options: Value,
		animate: Bool = false
	) -> Binding<Bool> {
		return .init { () -> Bool in
			self.wrappedValue.contains(options)
		} set: { newValue in
			let body = {
				if newValue {
					self.wrappedValue.insert(options)
				} else {
					self.wrappedValue.remove(options)
				}
			}
			guard animate else {
				body()
				return
			}
			withAnimation {
				body()
			}
		}
	}
}
```

# References / Useful Links

[Source Code](https://gist.github.com/vibrazy/79d407cf2eac2b0e65a61ab07f584105)
[Custom OptionSet](https://www.hackingwithswift.com/example-code/language/how-to-create-a-custom-optionset)
[OptionSet](https://nshipster.com/optionset/)
[Sourcery](https://github.com/krzysztofzablocki/Sourcery)