繁体   English   中英

SwiftUI 在第二个视图中更改@Binding object 的问题

[英]SwiftUI issue with changing @Binding object in the second view

我想从第二个屏幕更改绑定中的 object,但它没有像我想的那样工作。 我可以更改 object 的属性,但不能更改实际引用。 如果有人可以解释为什么会这样,实现这一目标的最佳方法是什么? 根据我的阅读,@Binding 在 View 之外不能很好地工作。 我还包括一个解决方法,我使用第二个发布的属性来更新 ui,但在我看来这不是一个好的解决方案。我是做错了什么还是@Binding 是如何工作的? (您可以复制并粘贴代码以了解我的意思)

class TestModel {
   var text:String?
   
   init(text: String?) {
       self.text = text
   }
}

class FirstViewVM: ObservableObject {
   @Published var model:TestModel?
   @Published var showSecondView = false
}

struct FirstView: View {
   @StateObject var viewModel = FirstViewVM()
   
   var body: some View {
       VStack {
           Text(viewModel.model?.text ?? "n/a")
               .padding()
           Button("New model") {
               viewModel.model = TestModel(text: "New model from screen 1")
           }
           .padding()
           Button("Show screen 2") {
               viewModel.showSecondView = true
           }
           .padding()
       }
       .sheet(isPresented: $viewModel.showSecondView) {
           NavigationView {
               //First check the problem.. then use the workaround view to fix it....
               SecondView(viewModel: SecondViewVM(model: $viewModel.model))
//                SecondViewWorkaround(viewModel: SecondViewVMWorkaround(model: $viewModel.model))
           }
       }
   }
}


class SecondViewVM: ObservableObject {
   @Binding var model:TestModel?
   
   init(model: Binding<TestModel?>) {
       _model = model
   }
   
}

struct SecondView: View {
   @StateObject var viewModel:SecondViewVM
   
   var body: some View {
       VStack {
           Text(viewModel.model?.text ?? "n/a")
               .padding()
           Button("Edit model") {
               viewModel.objectWillChange.send()
               viewModel.model?.text = "Edited from screen 2"
           }
           .padding()
           //This will not update the UI
           Button("New model") {
               viewModel.objectWillChange.send()
               viewModel.model = TestModel(text: "New model from screen 2")
           }
           .padding()
       }
   }
}


//Workaround....First check the views above..
class SecondViewVMWorkaround: ObservableObject {
   @Binding var model:TestModel? {
       didSet{
           modelUsedForUI = model //Passing the changes to the other model that is used for ui
       }
   }
   @Published var modelUsedForUI:TestModel? //Added another published var that will update the ui
   
   init(model: Binding<TestModel?>) {
       _model = model
       modelUsedForUI = model.wrappedValue
   }
   
   
}

struct SecondViewWorkaround: View {
   @StateObject var viewModel:SecondViewVMWorkaround
   
   var body: some View {
       VStack {
           Text(viewModel.modelUsedForUI?.text ?? "n/a")
               .padding()
           Button("Edit model") {
               viewModel.objectWillChange.send()
               viewModel.model?.text = "Edited from screen 2"
           }
           .padding()
           Button("New model") {
               viewModel.objectWillChange.send()
               viewModel.model = TestModel(text: "New model from screen 2")
           }
           .padding()
       }
   }
}

我认为当您创建第二个视图 model 以传递给第二个视图并尝试链接两个视图模型而不是链接视图时,您的代码有些过于复杂。 @Binding是传递@State变量时在View中使用的包装器。 您而是在两个视图模型之间使用它,我认为它无法按您的预期工作。

我建议采用不同的方法:

  1. 仅使用一个视图 model,它集成了您需要的所有功能。
  2. 在第二个视图中创建一个@ObservedObject
  3. 将视图 model 从您的第一个视图传递到第二个视图。

下面的示例有效,您想尝试一下:

1.只有一个视图model


class TestModel {
   var text:String?
   
   init(text: String?) {
       self.text = text
   }
}

class FirstViewVM: ObservableObject {
   @Published var model:TestModel?
   @Published var showSecondView = false
    
    // Merge here the functionalities of your second view model, or
    // just create an instance, like:
    // @Published var secondVM = SecondViewModel()
}

2.和3.将同一个view model从第一个view传递给第二个view

struct FirstView: View {
    @StateObject var viewModel = FirstViewVM()
    
    var body: some View {
        VStack {
            Text(viewModel.model?.text ?? "n/a")
                .padding()
            Button("New model") {
                viewModel.model = TestModel(text: "New model from screen 1")
            }
            .padding()
            Button("Show screen 2") {
                viewModel.showSecondView = true
            }
            .padding()
        }
        .sheet(isPresented: $viewModel.showSecondView) {
            NavigationView {
                
                // Just pass the same model from one view to another
                SecondView(viewModel: viewModel)
            }
        }
    }
}

struct SecondView: View {
    @ObservedObject var viewModel: FirstViewVM      // Read your model here
   
   var body: some View {
       VStack {
           Text(viewModel.model?.text ?? "n/a")
               .padding()
           Button("Edit model") {
               viewModel.objectWillChange.send()
               viewModel.model?.text = "Edited from screen 2"
           }
           .padding()
           
           // Now it updates
           Button("New model") {
               viewModel.objectWillChange.send()
               viewModel.model = TestModel(text: "New model from screen 2")
           }
           .padding()
       }
   }
}

是的,你做错了什么。 对于 Swift 和 SwiftUI,我们利用值类型的优势来解决您面临的一致性问题。 所以你的 model 应该是一个结构,例如

struct TestModel {
   var text: String
   
   init(text: String) {
       self.text = text
   }
}

然后使用 object 管理 model 结构的生命周期和副作用,例如

class Model: ObservableObject {
   @Published var testModel: TestModel?

   // funcs to load and save etc.
    func newTestModel(text: String) {
         testModel = TestModel(text: text)
    }
}

同样在 SwiftUI 中,我们不基于屏幕设计我们的视图,我们根据他们需要的数据将它们分解。 所以他们应该是这样的:

struct ContentView: View {
    @StateObject var model = Model()
    @State var showSecondView = false

    var body: some View {
        VStack {
            if let testModel = model.testModel {
                ContentView2(testModel: testModel)
            }
            Button("New model") {
                model.newTestModel(text: "from screen 1")
            }
            .padding()
            Button("Show screen 2") {
                showSecondView = true
            }
            .padding()
        }
        .sheet(isPresented: $showSecondView) {
            NavigationView {
                
                // Just pass the same model from one view to another
                SecondView(model: model)
            }
        }
    }
}

struct ContentView2: View {
    let testModel: TextModel

    var body: some View {
        Text(testModel.text ?? "n/a")
        .padding()
    }
}

struct SecondView: View {
    @ObservableObject var model: Model
   
   var body: some View {
       VStack {
           if let testModel = model.testModel {
               Button("Edit model") {
                   testModel.text = "Edited from screen 2"
               }
               .padding()
           }
               // Now it updates
           Button("New model") {
               model.newTestModel(text: "New model from screen 2")
           }
           .padding()
       }
   }
}

注意:当我们想在工作表上显示数据时,我们使用sheet(item:)而不是 boolean 版本。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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