Visualize Combine Magic with SwiftUI Part 1

Create your Combine Playground in SwiftUI

Kevin Cheng
4 min readOct 21, 2019

Create your Combine playground

After a few years of enjoying working with RxSwift, in WWDC 2019, Combine was introduced as an essential framework that empowers SwiftUI.

Immediately, I decided to create a StreamView like below.

With that, I’ll be able to visualize basic reactive behaviors in Combine such as Subscribe and Cancel. Moreover, if we put 2 StreamViews side by side. We can observe behaviors resulted from the manipulation of operators like Map, FlatMap, Scan, Merge, Append, Zip and CombineLatest.

Before we get to play with Combine, we’ll have to build our playground first. SwiftUI is a great framework for this job.

Let’s start by creating a SwiftUI view named CircularTextView that draws a circular text view with basic styling, white foreground, green background, bold text, and default paddings.

CircularTextView

Simple, right?

Secondly, we need a container view that holds a list of CircularTextView in sequence. Let’s name it TunnelView since it looks like a list of circular views passing through a tunnel.

Note a few things here:

@Binding var streamValues: [String]

We create an array that represents a list of values in the tunnel. There are 2 requirements here. First, we want this array to hold the source of truth of values and sequence passing in the tunnel. Whenever this array is updated, UI should be updated too. This can be achieved by applying one of the 2 property wrappers, State or Binding.

Second, TunnelView’s parent view should provide the source of truth (streamValues array) and be able to persist values and make changes. Therefore, we choose Binding property wrapper:

Section {            
TunnelView(streamValues: .constant(["1"])) TunnelView(streamValues: .constant(["1", "2"])) TunnelView(streamValues: .constant(["1", "2", "3"])) }.previewLayout(.sizeThatFits)

Now, we have a queue of circular texts in the tunnel. Whenever you add a String to this array, streamValues, a new circular text will appear in the back of the queue.

Sweet~ Now, let’s add 2 buttons so that we can add/remove values to this stream dynamically.

Now. Let’s test if it works as expected:

Let’s Combine

We have built ourselves a basic playground to work with. Now, let’s do some Combine. With Combine’s magic, we can easily create a publisher that emits values over time. Then, we can subscribe to this publisher and cancel a subscription at any time.

(1...5).map { String($0) }            
.map { Just($0).delay(for: .seconds(1), scheduler: DispatchQueue.main).eraseToAnyPublisher() } return publishers[1...].reduce(publishers[0]) { Publishers.Concatenate(prefix: $0, suffix: $1).eraseToAnyPublisher() } }

Toward the bottom of this struct, there seems a lot going on in this method intervalValuePublisher. Basically, all it does is to emits one value every second.

let publishers = (1...5).map { String($0) }
.map { Just($0).delay(for: .seconds(1), scheduler: DispatchQueue.main).eraseToAnyPublisher() }

Publishers represent an array of Publisher that emits a single String value and then waits for a second before it completes.

return publishers[1...].reduce(publishers[0]) {                 Publishers.Concatenate(prefix: $0, suffix: $1).eraseToAnyPublisher()        }

We use Combine’s convenient Concatenate feature to connect all publishers together.

Now we can subscribe to this publisher and add values to our streamValues array.


self.cancellable = self.invervalValuePublisher().sink { self.streamValues.append($0) }

We use the sink method to receive published value and keep its cancellable reference.

Button("Cancel") {                        
self.cancellable = nil
}

While the publisher is emitting new values, we can cancel the subscription by removing cancellable’s reference or by calling its cancel method.

Note that there is only one cancellable existing in the picture no matter how many times you press Subscribe button. This way we ensure every subscription is cancelled before a new subscription starts.

Here is the final result. During subscription, we can press cancel button to stop receiving values. After cancellable or subscription completes, we can clear values in the queue.

Final thoughts

We have built a basic playground in SwiftUI that displays basic Combine behaviors. Where do we go from here?

There are 2 directions.

First, we can keep improving this playground. It won’t take long for you to realize there are several flaws in this playground. For one, the circular texts are sorted automatically by its text value (id: \.self) but what we actually want is to sort them by added time. For another, the animations seem odd. What we want is for the circular texts to appear from one side of the tunnel and disappear on the other side. For the other, when the stream values pile up and overflow your screen, we lose sight of what is going on with newly added circular texts. There are certainly more issues here. We will address them one by one in the following articles.

Second, we can start to display all the behaviors of basic operators like map, scan, filter, and drop. see Visualize Combine Magic with SwiftUI Part 2

Moreover, between operators, we can put them side by side for comparison, such as the differences between Merge and Append (see Visualize Combine Magic with SwiftUI Part 3) or differences between Zip and CombineLatest (see Visualize Combine Magic with SwiftUI Part 4). You’ll see a lot of interesting visualization here.

I’ll try to keep all the source code in one place. If you are interested, feel free to download or clone from here.

There are certainly more I can learn than share. Please drop a line if you resonate or disagree with anything you see here.

--

--