SwiftUI: controlling View updates

What this article is about

In this article, we’ll make an Updater protocol. Updaters will enforce a set of rules that dictate when a view should update. In particular, we’ll make three separate updaters to suit different situations:

  1. Updaters.Nonduplicate — updates when its equality is broken

Updaters will be assigned in the same way that ViewModifiers are assigned.

view.modifier(Updaters.Nonduplicate(equatable))view.modifier(Updaters.True(boolean))view.modifier(Updaters.Event(publisher))

How views are updated

Views propagate changes by recalculating their body, which in turn tells each subview to recalculate its body, so on and so forth. Views tend to be computationally cheap so this process is usually instantaneous. Imagine, however, that you have a computationally expensive view and you want to manually control when it updates, then this behavior is no longer suitable.

SwiftUI comes with EquatableView. It wraps another view that conforms to equatable and recalculates its body only when it receives a view that breaks the equality. This allows us to cancel updates, either because we deem them unnecessary or because we want specialized behavior.

It appears, currently, that a view conforming to Equatable is automatically interpreted as an EquatableView, and wrapping it inside an EquatableView only duplicates the equality check. In this article, we’ll assume that this is intended and therefore omit EquatableView in favor of only conforming to Equatable.

Updater

While it’s possible to naively make your views conform to Equatable, it’s not always appropriate. Imagine you only want to update a view in response to a published event. Should the view know about the event? Probably not. It’s usually better to keep your views ignorant.

Updater facilitates our separation of concerns. Rather than conforming views to Equatable, an updater will wrap it inside another view that conforms to Equatable and contains all the necessary information to enforce its updating behavior. Each updater will belong to an enum called Updaters, similar to how Combine does with Publishers.

It’s worth noting that Updater doesn’t conform to ViewModifier. The reason is simple: ViewModifier doesn’t work in this situation (I’ve tried).

It works somewhat like a ViewModifier.

1. Nonduplicate

Our first updater takes a value and compares it to its previous value. It only updates its view when the equality is broken.

It gives your views the powers of Equatable without actually conforming to Equatable.

2. True

Our second updater takes a boolean and only updates its view when the boolean equates to true. It does this by evaluating its latest (rhs) value in the equality check.

It’s a toggle switch.

3. Event

Our third updater is a bit more interesting. It takes a publisher and only updates the view when it receives a value from the publisher. It does this in two steps: first it blocks all updates in the equality check, then it tells SwiftUI that its state has changed whenever the publisher emits a value.

The equality check is only triggered by external changes.

The result

With only a few lines of code we’ve made it possible, and even easy, to control your view’s updating behavior in a wide range of scenarios—without making any changes to the view itself.

Here’s an image of an example app where it’s possible to cycle through a collection of colors. All squares are assigned the same color but, because they are updated differently, they all have different colors.

The current color is red.

Links

Package/source code: https://github.com/oscbys/OBEUpdaters

I tried to be normal once.