SwiftUI:ObservableObject 不会在重绘时保持其状态

Kon*_*ann 16 declarative swift swiftui property-wrapper

问题

为了实现应用程序代码的简洁外观,我为每个包含逻辑的视图创建了 ViewModel。

一个普通的 ViewModel 看起来有点像这样:

class SomeViewModel: ObservableObject {

    @Published var state = 1

    // Logic and calls of Business Logic goes here
}
Run Code Online (Sandbox Code Playgroud)

并像这样使用:

struct SomeView: View {

    @ObservedObject var viewModel = SomeViewModel()

    var body: some View {
        // Code to read and write the State goes here
    }
}
Run Code Online (Sandbox Code Playgroud)

当视图父级未更新时,这可以正常工作。如果父级的状态发生变化,这个视图会被重绘(在声明性框架中很正常)。但是ViewModel 也会被重新创建,并且之后不会保存状态。与其他框架(例如:Flutter)相比,这是不寻常的。

在我看来,ViewModel 应该保留,或者 State 应该保留。

如果我用一个@State属性替换 ViewModel并int直接使用(在本例中)它会保持持久化并且不会重新创建

struct SomeView: View {

    @State var state = 1

    var body: some View {
        // Code to read and write the State goes here
    }
}
Run Code Online (Sandbox Code Playgroud)

这显然不适用于更复杂的状态。如果我为@State(如 ViewModel)设置一个类,越来越多的事情无法按预期工作。

  • 有没有办法不每次都重新创建 ViewModel?
  • 有没有办法为 复制@StatePropertywrapper @ObservedObject
  • 为什么@State 在重绘时保持状态?

我知道通常,在内部视图中创建 ViewModel 是不好的做法,但可以通过使用 NavigationLink 或 Sheet 来复制这种行为。
有时,当您想到一个非常复杂的 TableView 时,将状态保留在ParentViewModel 中并使用绑定是没有用的,其中单元格本身包含很多逻辑。
对于个别情况总是有一种解决方法,但我认为如果不重新创建 ViewModel 会更容易。

重复问题

我知道有很多关于这个问题的问题,都在谈论非常具体的用例。在这里我想谈谈一般问题,而不是深入讨论自定义解决方案。

编辑(添加更详细的示例)

当有一个状态改变的 ParentView 时,比如来自数据库、API 或缓存的列表(想想一些简单的事情)。通过 aNavigationLink您可能会到达一个详细信息页面,您可以在其中修改数据。通过更改数据,反应性/声明性模式会告诉我们也更新 ListView,然后“重绘” NavigationLink,这将导致重新创建 ViewModel。

我知道我可以将 ViewModel 存储在 ParentView / ParentView 的 ViewModel 中,但这是 IMO 的错误做法。并且由于订阅被销毁和/或重新创建 - 可能会有一些副作用。

Kon*_*ann 12

最后,苹果提供了一个解决方案:@StateObject.

通过更换@ObservedObject@StateObject我最初的帖子中提到的一切工作。

不幸的是,这仅在 ios 14+ 中可用。

这是我来自 Xcode 12 Beta 的代码(2020 年 6 月 23 日发布)

struct ContentView: View {

    @State var title = 0

    var body: some View {
        NavigationView {
            VStack {
                Button("Test") {
                    self.title = Int.random(in: 0...1000)
                }

                TestView1()

                TestView2()
            }
            .navigationTitle("\(self.title)")
        }
    }
}

struct TestView1: View {

    @ObservedObject var model = ViewModel()

    var body: some View {
        VStack {
            Button("Test1: \(self.model.title)") {
                self.model.title += 1
            }
        }
    }
}

class ViewModel: ObservableObject {

    @Published var title = 0
}

struct TestView2: View {

    @StateObject var model = ViewModel()

    var body: some View {
        VStack {
            Button("StateObject: \(self.model.title)") {
                self.model.title += 1
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如您所见,在StateObject重绘父视图时保留它的值,同时ObservedObject正在重置。


小智 5

我同意你的看法,我认为这是 SwiftUI 的许多主要问题之一。这就是我发现自己在做的事情,尽管它很恶心。

struct MyView: View {
  @State var viewModel = MyViewModel()

  var body : some View {
    MyViewImpl(viewModel: viewModel)
  }
}

fileprivate MyViewImpl : View {
  @ObservedObject var viewModel : MyViewModel

  var body : some View {
    ...
  }
}
Run Code Online (Sandbox Code Playgroud)

您可以就地构建视图模型或将其传入,它会为您提供一个视图,该视图将在整个重建过程中维护您的 ObservableObject。