As a declarative UI framework, SwiftUI handles almost all of our UI and data interactions, freeing us from the need to focus on the logic of refreshing the UI when the data changes and updating the data after the user interacts.

In order to bind data to the UI, we need to use some of Swift’s attribute wrappers to describe their relationship to SwiftUI, so let’s get started.

Go to the official account [iOS Development Stack] to learn more SwiftUI, iOS development related content.

State Properties @State

In the previous article, when we added or removed elements to the array, the list automatically responded to the change, precisely because we used @state to mark the Model in the View.

struct ContentView: View {
    @State private var title: String = ""
    var body: some View {
        VStack {
            Text("\(title)")
            TextField("Please Enter Title", text: $title)}}}Copy the code

Variables wrapped with @state are values that can be read by SwiftUI. These values are usually constants such as strings or numbers.

When a property wrapped by State changes, SwiftUI recalculates and redraws the entire View hierarchy that uses the property. This usually means that the Body of the variable View is redrawn, in this case, the Body of the ContentView.

Variables wrapped by @state must be private and can only be used in the body of the current view and its children.

Just like the $title in TextField, we can bind the variable to another view with the *$* prefix, so that the variable can be modified in another view. The following code uses a Toggle to turn Wi-Fi on and off:

struct ContentView: View {
    @State private var isOn = false
    var body: some View {
        VStack {
            Text("Wi-Fi State: \(isOn ? "On" : "Off")")
            Image(systemName: "\(isOn ? "wifi" : "wifi.slash")")
            Toggle("Wi-Fi State", isOn: $isOn)}}}Copy the code

As shown in the code above, we establish a binding between the isOn property and the Toggle control. Toggle can modify the isOn value. In addition, when isOn changes, the content of Text and Image will change.

State Binding State Binding

A property wrapped with @state is only used inside the view to which it belongs, so an @Binding should be used when its child views want to access the property. Just like the Toggle used in the above example, we put the Text and Image into a custom View.

struct WiFiView: View {
    @Binding var isOn: Bool
    
    var body: some View {
        Text("Wi-Fi State: \(isOn ? "On" : "Off")")
        Image(systemName: "\(isOn ? "wifi" : "wifi.slash")")}}Copy the code

Here we use @binding to create a dependency between the data and the interface. Unlike @state, the Binding property is not held by the current view, and the Binding value can be exported by the State property value.

In this case, changing @binding to @state would give the WiFiView and its superview their respective isOn properties, and one change would not affect the other, which is obviously not what we want.

useCombineFramework of thePublisher

Properties wrapped with @state can only be used within the current View or its child views, and the State property is temporary — since the state-wrapped property belongs to the View, the corresponding State property disappears when the View is destroyed. This is obviously not enough. In addition, we also need to deal with some non-interface information during the development process, such as Timer, Notification, etc. The information carried by them usually also needs to update the interface. In this case, Publisher in Combine is used.

Combine was introduced in iOS13 to handle event messages in apps. If you’ve worked with RxSwift or ReactiveCocoa before, you should be familiar with this concept, which is based on the pattern of publisher and subscriber.

class Contact: ObservableObject {
    @Published var name: String
    @Published var age: Int
    
    init(name: String.age: Int) {
        self.name = name
        self.age = age
    }
}

struct ContentView: View {
    @ObservedObject var xiaowang = Contact(name: "xiaowang", age: 21)
    var body: some View {
        VStack {
            Text("Wang:\(xiaowang.name)")
            
            // This is just an example, and Publisher is not normally modified here
            Button("Modify Contact") {
                xiaowang.name = "Wang"}}}}Copy the code

We first create an ObservableObject associated human, then add a variable wrapped in ObservedObject to the SwiftUI view, use this variable in the body, and when the variable wrapped in * @published * changes, The body is reloaded with the new value.

If you are looking at WWDC2019 Introducing Combile while on a video BindableObject/didChange send/onReceive (), the content has now been removed.

Only class can comply with the ObservableObject protocol, {% label danger@Non-class type ‘Contact’ cannot conform to class protocol ‘ObservableObject’ %}

In iOS14, a new * @stateobject * has been introduced to enrich this usage scenario. It differs from ObservedObject in that properties wrapped by ObservedObject are reset to their original values when the view is refreshed, while properties used by StateObject are not.

Except in some cases where you need to use ObservedObject, this works for the StateObject.

Environment variables Evironment Objects

In addition to the scenarios listed above, suppose our app needs to jump from one page to another, which is a very common scenario, and the latter page uses some of the attributes of the previous page. This is usually done:

NavigationLink(destination: nextView(aModel: aModel)) {
    Text("Detail")}Copy the code

NavigationLink NavigationLink NavigationLink destination is the page to pop up, initialized with a property of the current page.

There’s no big problem with that, but if you have too many levels, you have too many new levels behind you, and you have to pass values backwards, it can be very complicated and error-prone — just like with UIKit. To solve this problem, SwiftUI introduced **Evironment Objects**.

// DataSource.swift

class DataSource: ObservableObject {
    @Published var counter = 0
}

// ContentView.swift

struct ContentView: View {
    let dataSource = DataSource(a)var body: some View {
        NavigationView {
            VStack {
                Button("Click") {
                    dataSource.counter + = 1
                }
                NavigationLink(
                    destination: ContactView()) {
                    Text("Enter Next Page")
                }
            }
        }
        .environmentObject(dataSource)
    }
}

// ContactView.swift

struct ContactView: View {
    @EnvironmentObject var dataSource: DataSource
    var body: some View {
        Text("\(dataSource.counter)")}}Copy the code

Environment Object and ObservedObject/StateObject usage is very similar, first of all, the DataSource observe ObservableObject agreement, to observe the properties of the counter use Publisher packaging.

Properties wrapped by @environmentobject will change with the Publised property and the view will be reloaded.

.environmentobject is a Modifier that injects a property into an environment variable, unless you use @environmentobject to inject the property into the environment variable, Will pack the wrong {% label danger @ MissingEnvironmentObjectError: Missing EnvironmentObject %}

Go to the official account [iOS Development Stack] to learn more SwiftUI, iOS development related content. Reply to “Jane letter” to get interview questions from major companies.

The article was first posted on my personal blog