就像@Mark 指出的那样,您不应该@StateObject
在初始化期间处理任何地方。这是因为在@StateObject
View.init() 之后以及在调用主体之前/之后稍微初始化了。
关于如何将数据从一个视图传递到另一个视图,我尝试了很多不同的方法,并提出了一个适合简单和复杂视图/视图模型的解决方案。
版本
Apple Swift version 5.3.1 (swiftlang-1200.0.41 clang-1200.0.32.8)
此解决方案适用于 iOS 14.0 以上版本,因为您需要.onChange()
视图修饰符。该示例是在 Swift Playgrounds 中编写的。如果您需要onChange
较低版本的 like 修饰符,您应该编写自己的修饰符。
主视图
主视图@StateObject viewModel
处理所有视图逻辑,例如按钮点击和“数据” (testingID: String)
-> 检查 ViewModel
struct TestMainView: View {
@StateObject var viewModel: ViewModel = .init()
var body: some View {
VStack {
Button(action: { self.viewModel.didTapButton() }) {
Text("TAP")
}
Spacer()
SubView(text: $viewModel.testingID)
}.frame(width: 300, height: 400)
}
}
主视图模型 (ViewModel)
viewModel 发布一个testID: String?
. 此 testID 可以是任何类型的对象(例如,配置对象 aso,您可以命名它),对于本示例,它只是子视图中也需要的字符串。
final class ViewModel: ObservableObject {
@Published var testingID: String?
func didTapButton() {
self.testingID = UUID().uuidString
}
}
因此,通过点击按钮,我们ViewModel
将更新testID
. 我们也希望这一点testID
,SubView
如果它发生变化,我们也希望我们SubView
能够识别和处理这些变化。通过ViewModel @Published var testingID
我们能够发布对我们视图的更改。现在让我们看看我们的SubView和SubViewModel。
子视图
所以SubView
有自己@StateObject
的处理自己的逻辑。它与其他视图和 ViewModel 完全分离。在此示例中,SubView
仅显示其 中的 testID MainView
。但请记住,它可以是任何类型的对象,例如数据库请求的预设和配置。
struct SubView: View {
@StateObject var viewModel: SubviewModel = .init()
@Binding var test: String?
init(text: Binding<String?>) {
self._test = text
}
var body: some View {
Text(self.viewModel.subViewText ?? "no text")
.onChange(of: self.test) { (text) in
self.viewModel.updateText(text: text)
}
.onAppear(perform: { self.viewModel.updateText(text: test) })
}
}
为了“连接”我们testingID
发布的我们MainViewModel
,我们SubView
用@Binding
. 所以现在我们testingID
的SubView
. 但是我们不想直接在视图中使用它,而是需要将数据传递给我们的SubViewModel
,记住我们的 SubViewModel 是一个@StateObject
处理所有逻辑的。而且我们不能@StateObject
在视图初始化期间将值传递给我们,就像我在开始时写的那样。此外,如果我们的数据(testingID: String
)发生变化MainViewModel
,我们SubViewModel
应该识别并处理这些变化。
因此我们使用两个ViewModifiers
.
改变
.onChange(of: self.test) { (text) in
self.viewModel.updateText(text: text)
}
onChange修饰符订阅我们@Binding
属性的更改。因此,如果它发生变化,这些变化就会传递给我们的SubViewModel
. 请注意,您的属性必须是Equatable。如果你传递一个更复杂的对象,比如 a ,请Struct
确保在你的.Struct
出现
我们需要onAppear
处理“第一个初始数据”,因为 onChange 在您的视图第一次初始化时不会触发。它只是为了改变。
.onAppear(perform: { self.viewModel.updateText(text: test) })
好的,这里是SubViewModel,我猜没有什么可以解释的了。
class SubviewModel: ObservableObject {
@Published var subViewText: String?
func updateText(text: String?) {
self.subViewText = text
}
}
现在您的数据在MainViewModel和SubViewModel之间是同步的,并且这种方法适用于具有许多子视图和这些子视图的子视图的大型视图等。它还使您的视图和相应的视图模型具有高度的可重用性。
工作示例
GitHub 上的游乐场:
https ://github.com/luca251117/PassingDataBetweenViewModels
补充说明
为什么我使用onAppear
andonChange
而不是 only onReceive
:似乎将这两个修饰符替换为onReceive
会导致连续数据流SubViewModel updateText
多次触发。如果您需要流式传输数据以进行演示,这可能很好,但如果您想处理网络呼叫,例如,这可能会导致问题。这就是为什么我更喜欢“两个修饰符方法”。
个人注意:请不要在相应视图范围之外修改 stateObject。即使以某种方式可能,这也不是它的意思。