Data flow in SwiftUI
SwiftUI is a whole new way to develop Apple apps. This time, we'll explore the data flow and lifecycle in SwiftUI. There are WWDC19 and WWDC20 videos from Apple about SwiftUI dataflow. Apple's official documentation for SwiftUI dataflow also exists.
Lifecycle in SwiftUI
In SwiftUI, there are only two functions that represent the state of a View, namely, its lifecycle. Instead, there are various property wrappers that indicate the state, which can respond to different states of the data flow.
.onAppear {
print("View appeared")
}
.onDisappear {
print("View disappeared")
}
@State
In general, structs are value types, so the values in the struct cannot be changed. SwiftUI provides @State
to allow you to change values within a struct.
Views in SwiftUI are structs, which are destroyed or recreated at any time. That's why we use @State
to create persistently mutable variables. However, @State
is recommended only for value types such as String, Int, and Bool. Typically, @State
variables are declared private and are not shared with other views. If you want to share values with other views, you can use @Binding
or @ObservedObject
.
struct ContentView: View {
@State private var number = 0
}
@Binding
@Binding
allows bidirectional binding of values such as @State
of the parent view. In the code below, isPresented binds showAddView
to change the value.
struct ContentView: View {
@State private var showAddView = false
var body: some View {
VStack {
Text("Hello World.")
}
}
.sheet(isPresented: $showAddView) {
AddView(isPresented: self.$showAddView)
}
}
struct AddView: View {
@Binding var isPresented: Bool
var body: some View {
Button("Dismiss") {
self.isPresented = false
}
}
}
ObservableObject
ObservableObject
is a Protocol and is part of the Combine framework. To use it, just follow the Protocol and use @Published
. Using @Published
lets the View know that a variable's value has been added or deleted. ObservableObject
is a good protocol to apply to the ViewModel of the MVVM architecture.
class MyViewModel: ObservableObject {
@Published var dataSource: MyModel
init(dataSource: MyModel) {
self.dataSource = dataSource
}
}
@StateObject
At WWDC 2020, Apple further revealed @StateObject
. It works in much the same way as @ObservedObject
. However, when SwiftUI re-renders the View
, it prevents it from being canceled.
struct ContentView: View {
@StateObject var user = User()
}
@ObservedObject
SwiftUI allows views to detect external objects via @ObservedObject
. In the code below, the User
class conforms to ObservableObject
and has a @Published
variable. @ObservedObject
the user variable holds this User class object. SwiftUI refreshes the view when the value of the @Published
variable of these user objects changes.
class User: ObservableObject {
@Published var name = "Hohyeon Moon"
}
struct ContentView: View {
@ObservedObject var user = User()
var body: some View {
VStack {
Text("Your name is \(user.name).")
}
}
}
@EnvironmentObject
@EnvironmentObject
is usually used for data shared across apps. @EnvironmentObject
can pass values through .environmentObject()
. The object you pass must conform to the ObservableObject
protocol. As in the code below, if you provide a root view, it can be used in any view.
// MySettings.swift
class Settings: ObservableObject {
@Published var version = 0
}
// SceneDelegate.swift
var settings = UserSettings()
window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(settings))
// MyViews.swift
struct ContentView: View {
@EnvironmentObject var settings: UserSettings
var body: some View {
NavigationView {
VStack {
Button(action: {
self.settings.version += 1
}) {
Text("Increase version")
}
NavigationLink(destination: DetailView()) {
Text("Show Detail View")
}
}
}
}
}
struct DetailView: View {
@EnvironmentObject var settings: UserSettings
var body: some View {
Text("Version: \(settings.version)")
}
}
Overall
Image by Chris Eidhof at objc.io
