Data in SwiftUI, Part 2: Views as a function of data

SwiftUI iOS Data

Part 2 in a series on understanding data in SwiftUI. We will talk about the key that makes principles in part 1 possible in SwiftUI. And how this resulting in a reduction of the complexity of UI development.

  1. Data flow in SwiftUI, Part 1: Data
  2. Data flow in SwiftUI, Part 2: Views as a function of data
  3. Data flow in SwiftUI, Part 3: Tools

We know from part 1 that data is the main focus for SwiftUI, but without cooperation from a view and framework, it might not be a pleasant experience implementing it.

Views as a function of state

Even though you set the data right, you have a single source of truth, and you set dependency correctly. Doing this in UIKit might still result in a big chunk of code. That's because, by nature, UIKit is an event-driven user interface; it needs someone to coordinate all of these events and glue them together. That is why we need UIViewController in UIKit.

Here is an example from WWDC 2019 session[1]. It is a music player app with a PlayerView as a parent view, which contains PlayerButton showing play/pause image based on isPlaying state.

Music player app
A music player app - Pausing
Music player app
A music player app - Playing
UIKit diagram
An overview diagram of interactions between each view in UIKit

The above diagram should familiar to you, PlayerViewController holds a reference of PlayerView and PlayButton. It keeps a single source of truth (isPlaying) in sync between PlayerView and PlayButton. PlayButton holds a reference of isPlaying, change the image (play/pause) based on this state and fire an action when got tapped. PlayerViewController listens to button action and changes the state accordingly. Once playing state change, it changes the color of the song title in PlayView.

// An example of some implementation of PlayerViewController

var isPlaying: Bool = false {
didSet {
// change song label color
}
}

func viewDidLoad() {
configureView()
loadData()
}

private func configureView() {
// setup song label color
// setup playButton state based on isPlaying
}

@objc func playButtonDidTap(sender: Any) {
// toggle isPlaying
// configure playButton
}

As you can see, the fact that UIKit communicates using events makes it difficult to know what the resulting view would be at a given time. And there are so many places that can go wrong.

Views are a function of state, not of a sequence of events.
– WWDC 2019, session 226[1:1]

If UIKit said to be an event-driven user interface, SwiftUI would be data-driven. The above quote from WWDC 2019 session[1:2] sums the situation of UIKit and SwiftUI nicely.

View in UIKIt usually a result of sequences of events, whether target-action, delegate, or notification. View in SwiftUI, on the other hand, designed to depend solely on data.

Let see some example.

struct PlayerView: View {
let episode: Episode
@State private var isPlaying: Bool = false

var body: some View {
VStack {
Text(episode.title).foregroundColor(isPlaying ? .white: .gray)
Text(episode.showTitle).font(.caption).foregroundColor(.gray)
PlayButton(isPlaying: $isPlaying)
}
}
}
struct PlayButton: View {
@Binding var isPlaying: Bool

var body: some View {
Button(action: {
self.isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle": "play.circle")
}
}
}

You might not understand some of the syntaxes, but we can all agree that a SwiftUI view is quite easy to understand. We can declare views in the form of function that takes data as an arugment. PlayerView's episode title color change according to isPlaying. PlayButton image showing play/pause based on isPlaying. We know exactly how it looks based on a given data, isPlaying.

Following is the same view interaction diagram, but this time for SwiftUI.

SwiftUI diagram
An overview diagram of interactions between each view in SwiftUI

Declarative UI

SwiftUI said to be declarative UI. It has a way to declare all principles we learn in this declarative world. We can declare data as a source of truth or dependency, and view as a function of those data. SwiftUI will do take care of the rest for us.

The declarative UI make SwiftUI shine, the framework handle everything that we have to do it manually in UIKit. It makes the concept of view controller not relevant anymore in SwiftUI.

The purpose of the view controller is to manage views and their interaction. Most of the codes in view controller are for responding to user interaction from its views. Setting up target-action to a button, listen for a delegate. Observe to model change and response to that by reload your table view or change some labels. You have to do all of this manually, which is error-prone, and the complexity grows as your app grows.

SwiftUI does all the heavy lifting, which allows us to focus on things that matter, manage the data of your app by defining the right source of truth and its dependency.

The whole purpose of view controller is to keep your data in sync with your view

The fact that we don't have to manage data ourselves makes the complexity in UI development significantly reduce.

What's next

In the following article, we will go through all the tools available, including the one shown in the example @State and @Binding.

Data flow in SwiftUI, Part 1: Data
Data flow in SwiftUI, Part 3: Tools
Data Flow Through SwiftUI

References


  1. WWDC 2019 session 226, Data Flow Through SwiftUI https://developer.apple.com/videos/play/wwdc2019/226/ ↩︎ ↩︎ ↩︎

If you enjoy this article, you can subscribe to the weekly newsletter.

Every Friday, you’ll get a quick recap of all articles and tips posted on this site — entirely for free.


← Home