简体   繁体   中英

SwiftUI: Using different property wrappers for the same variable

in iOS13 I do the following to bind my View to my model:

class MyModel: ObservableObject {
    @Published var someVar: String = "initial value"
}

struct MyView: View {
    @ObservedObject var model = MyModel()
    
    var body: some View {
        Text("the value is \(model.someVar)")
    }
}

in iOS14 there is a new property wrapper called @StateObject that I can use in the place of @ObservedObject , I need this snippet of code to be compatible with iOS13 and iOS14 while leveraging iOS14's new feature, how can I do that with @StateObject for the same variable ?

Different property wrappers generate different types of hidden properties, so you cannot just conditionally replace them. Here is a demo of possible approach.

Tested with Xcode 12 / iOS 14 (deployment target 13.6)

struct ContentView: View {
    var body: some View {
        if #available(iOS 14, *) {
            MyNewView()
        } else {
            MyView()
        }
    }
}

class MyModel: ObservableObject {
    @Published var someVar: String = "initial value"
}

@available(iOS, introduced: 13, obsoleted: 14, renamed: "MyNewView")
struct MyView: View {

    @ObservedObject var model = MyModel()

    var body: some View {
        CommonView().environmentObject(model)
    }
}

@available(iOS 14, *)
struct MyNewView: View {

    @StateObject var model = MyModel()

    var body: some View {
        CommonView().environmentObject(model)
    }
}

struct CommonView: View {
    @EnvironmentObject var model: MyModel

    var body: some View {
        Text("the value is \(model.someVar)")
    }
}

ObservableObject and @Published are part of the Combine framework and you should only use those when you require a Combine pipeline to assign the output to the @Published var. What you should be using for your data is @State use it as follows:

struct MyView: View {
    @State var text = "initial value"
    
    var body: some View {
        VStack{
            Text("the value is \(text)")
            TextField("", text: $text)
        }
    }
}

If you have multiple vars or need functions then you should refactor these into their own struct. Multiple related properties in their own struct makes the View more readable, can maintain invariance on its properties and be tested independently. And because the struct is a value type, any change to a property, is visible as a change to the struct (Learn this in WWDC 2020 Dataflow Through SwiftUI ). Implement as follows:

struct MyViewConfig {
    var text1 = "initial value"
    var text2 = "initial value"

    mutating func reset(){
        text1 = "initial value"
        text2 = "initial value"
    }
}

struct MyView: View {
    @Binding var config: MyViewConfig
    
    var body: some View {
        VStack{
            Text("the value is \(config.text1)")
            TextField("", text: $config.text1)
            Button("Reset", action: reset)
        }
    }

    func reset() {
        config.reset()
    }
}

struct ContentView {
    @State var config = MyViewConfig()
    var body: some View {
        MyView(config:$config)
    }
}

SwiftUI is designed to take advantage of value semantics where all the data is in structs which makes it run super fast. If you unnecessarily create objects then you are slowing it all down.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM