Small knowledge, big challenge! This article is participating in the “Essentials for Programmers” creative activity. This article has participated in the “Digitalstar Project” to win a creative gift package and challenge the creative incentive money

Basic principles of data processing

Interfaces in SwiftUI are strictly data-driven: Interface changes at runtime can only be performed indirectly by modifying data, rather than directly.

  • Data Access as a Dependency: In SwiftUI, once Data is used, it will become a Dependency of the view, that is, when Data changes, the view presentation will follow the change, instead of constantly synchronizing the state changes between Data and view as in MVC mode.

  • A Single Source Of Truth: Keep a single data source. In SwiftUI, if different views want to access the same data, they do not need to hold their own data, but directly share the same data source. The advantage of this method is that there is no need to manually handle the synchronization of views and data.

fiveData flow tool

You can use them to establish data and view dependencies

  • Property
  • @State
  • @Binding
  • ObservableObject
  • @EnvironmentObject

Note: The last four are a Property decorator syntax sugar (decorator/decorator) implemented using Property Wrapper, a new feature in Swift 5.1

What is the propertyWrapper

The above identifiers all start with @, which means they are generated from @propertyWrapper, so to understand what these things do you need to know about propertyWrapper

Use @propertywrapper to generate a propertyWrapper: @xxx, which wraps the property

Before wrapping, I want to show you a few properties that you need to implement when creating a wrapper: wrappedValue (wrappedValue), projectedValue (rendered value)

@propertyWrapper struct Wrapper name {var wrappedValue: type {get {} set {}} var projectedValue: type {return} Initialization method (optional)}Copy the code
  • Where wrappedValue is the value that needs to be wrapped, direct access, and project Value is what you might wantSome additional value is rendered by wrapping, $+ attribute name access (examples below)
// I want to create a temperature wrapper where the variables are always less than 46.5 and greater than 35 (assuming the limit of the thermometer is 35-46.5 degrees), @propertywrapper struct Temperature {var celcius = 35.0// Celsius var wrappedValue: Double { get { return celcius } set { celcius = newValue <= 35 ? 35: min(46.5, newValue) Double {return celcius + 273.15// celcius + 273.15}} struct People {@temperature (celcius: 36.5) var templete} let people = people () print(" temp :",people. Templete ",people.Copy the code

Operating result Temperature: 36.5K degree: 309.65

Property

  • The simplest form of this is inViewTo define constants or variables and then use them internally
import SwiftUI struct Model { var title: String var info: String } struct ContentView : View { let model = Model(title: "WWDC 2019", info: "SwiftUI is a new UI framework ") Var Text(model.title).font(.title) Text(model.info)}}}Copy the code

@State

Just remember one thing about this: when the value wrapped by @state changes, the UI changes as well

Import SwiftUI // another string HelloWorld, press the button to modify the variable x wrapped by State, so UI also changes with it struct TestView: View { @State var x = "HelloWorld" var body: some View { VStack{ Text(x) Button(action:{self.x = "Hello UI"}){ Text("ChangeValue") } } } } struct TestView_Previews: PreviewProvider { static var previews: some View { TestView() } }Copy the code

@Binding

Address the wrapped value instead of passing the value.

Import SwiftUI // Text1 and Text2 are two unrelated views, and they share the same data. When the data changes, in addition to telling Text1 to update the UI with @state, Text2 should also be told to update the number. Text2 struct Text1: View {@state var x = 5 var body: some View {HStack{Text(String(x)) Button(action: {self.x = 10}){ Text("change to 10") } Text2(y: $x) } } } struct Text2: View { @Binding var y: Int var body: some View { Text(String(y)) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { Text1() } }Copy the code

@obServedobject — ObservableObject — Can observe (other people’s) objects

Note that ⚠️ : ObservableObject is not an attribute wrapper, but rather a protocol that satisfies properties that can be changed using the @Publish tag in the protocol’s Class.

Use @publish to mark the properties of the Class you want to listen on

Wrap an instance of this Class with @observedobject. When some of the attributes of this instance are changed by @Publish, the Publisher broadcasts them to the UI(data subscribers) before the instance is changed.

By default, ObservableObject synthesizes an objectWillChange publisher.

Struct ContentView: View {@observedobject var John = Human(name: "John ", age: 24) var body: some View { VStack { Text(john.name) Button(action:{ self.john.happyBirthday() }){ Text(String(john.age)) } } } } class Human: ObservableObject { @Published var name: String @Published var age: Int init(name: String, age: Int) { self.name = name self.age = age } func happyBirthday() { self.name = "Happy birthday" self.age += 1 } } struct ContentView_Previews: PreviewProvider { static var previews: The ObservableObject protocol indicates that this class is listening for some properties. // @publish age says that these attributes are being listened on // @observedobject var John says that John object is being listened on // Click self.john.happyBirthday(), Changed John's name and age //UI received a broadcast from Publisher and automatically updated the interfaceCopy the code

If you need to share data across multiple views, @state may not be a good choice. If you also need to manipulate data outside of the View, @state is not even optional. For a value type with a few member variables, @state might be a good idea. But for more complex cases, such as types with many properties and methods, where only a few properties need to trigger UI updates, or where the properties are related to each other, we should choose reference types and a more flexible customizable approach.

@EnvironmentObject

A property wrapper type for an Observable object supplied by A parent or ancestor view.

The type of property wrapper for an observable provided by a parent or ancestor view.

Note ⚠️ : Emphasize observables (classes should satisfy the ObservableObject protocol), emphasize objects (must be classes and not Struct or Enum)

Struct ContentView: View {@observedobject var John = Human(name: "John ", age: 24) var body: struct ContentView: View {@observedobject var John = Human(name:" John ", age: 24) var body: some View { VStack { Text(john.name) Button(action:{ self.john.happyBirthday() }){ SubView() .environmentObject(john) } } } } struct SubView: View { @EnvironmentObject var john: Human var body: some View { Text(john.name) } } class Human: ObservableObject { @Published var name: String @Published var age: Int init(name: String, age: Int) { self.name = name self.age = age } func happyBirthday() { self.name = "Happy birthday" self.age += 1 } } struct ContentView_Previews: PreviewProvider { static var previews: Some View {ContentView()}} // The object observed in the parent View(John wrapped with @observedobject) is used in the child View, Just wrap variables of the same type in the child View with @enviromentobject and use an explicit description of.environmentobject (John) when calling the child ViewCopy the code

conclusion

Public data between views uses @state + @binding.

Common data between multiple Views and classes: Use @observedobject for views, and make the Class ObservableObject compliant.

The parent View uses @observedobject and the child View uses @environmentobject. Class complies with the ObservableObject protocol