Segue Shenanigans With SwiftUI
By: Mark Lucking
Tags: Programming, iOS, Swiftui, Swift, Combine

Multiple Views and More Concurrently

We’re not quite finished yet. You see, there is a requirement here that we haven’t touched on yet — a requirement that is nevertheless very real: the need to change more than one view at the same time. Indeed, the need to kick other processes off when you change views. Yes, you can include all your views that need to be shown into the same view, but we’ll hit the scalability fence pretty quickly with that approach.

Obviously, you can extend any of the methods I just covered. Both the SwiftUI observed and environmental objects feel like the most scalable to me, so I’ll focus on them for now. Let’s work through an example with the observed object and add a second view to our Page 1/Page 2 views — a shadow view that will change when we change the first:

The simplest solution to doing this would be to add a second state variable or observed object or environmental variable and simply change it at the same time. It’ll work, but is it the optimal solution? Is it the most scalable or can we do it differently? Let’s use this as a precursor to looking at something else.

Along with SwiftUI, Apple announced a peer framework at WWDC2019 called Combine. It is a framework that brings Swift up close with a paradigm called functional reactive programming (or simply FRP). Combine works with two players: a publisher and a consumer. Their implementations are closely linked to the job you need to be done. Let’s use it to solve our problem of synchronizing views without having to do so quite as blatantly in the headline code.

Let’s start with subscriptions. The idea is to create a subscription for our shadow view that will load in parallel to our first. It is a great solution because you can implement it in the background. Let’s use it with our model based on ObservedObjects . Most of the changes are to the model class within which I declare the interested parties. You can see here that I have declared a second model and a method to map it to the first. I implemented some code to set up the subscription to bring it all together:

The idea is simple: When you change the first page to the second, the subscription will trigger the shadow to follow suit. Obviously, we need to add a little more to the SwiftView code to make this work. It now looks like this:

This works well but is also a great example of one of those tutorials that fall short in the real world. You see, this will do the change instantly. It’s great should the brief call to snap from one view to another. But briefs rarely if ever do that. They normally call for for your view to do a merry dance around the screen before transforming into the next screen so subtly the users hardly notice the change.

Ironically, we’re back to those animation and transformation articles I alluded to at the beginning. Let’s start by looking at a shortcoming of the subscription we set up by adding a transition to the Page1/Page2 flip:

.onTapGesture {

withAnimation(.linear(duration: 4)) {

self.model.state = .page1

}

}.transition(AnyTransition.opacity)

I’ll show you the problem. As you change the Page1 to Page2, it fades out as intended, but the shadow just flips because it is being trigged by the subscription:

How do we fix this problem? A quick fix would be to add the state change for the shadow animation in the body of the code — the rationale for having a subscription in the first place.

What we need to do here is introduce more states to make this work. We need an entry state and an exit state for every view. We can then implement our code to transform between views within the views. The model code looks like this:

With the main SwiftUIView code now looking like this:

If you’ve been following along, you’ll appreciate that this isn’t a small jump. The jury is out as to whether this is a commercially acceptable solution. Sure, it is super scalable, but it is a great deal of code for such a small detail. Maybe this isn’t the best solution for concurrently displaying views.

Can we do better? Within the Combine framework, there is another version of the publisher and consumer. It isn’t a subscription but will work the same sort of way. Let’s look at that. First, here is the model file. As you see, without the subscription code, it is far shorter. We have in it just two states again:

And this is the main SwiftUI code using the alternative publisher/consumer mechanism:

It is considerably fewer lines (less than half the code with the subscription/fix for the animation). Clearly, this is a better solution in some ways.