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

标签 swift swiftui declarative property-wrapper

问题

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

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

class SomeViewModel: ObservableObject {

    @Published var state = 1

    // Logic and calls of Business Logic goes here
}

并像这样使用:
struct SomeView: View {

    @ObservedObject var viewModel = SomeViewModel()

    var body: some View {
        // Code to read and write the State goes here
    }
}

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

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

如果我用 @State 替换 ViewModel属性(property)和使用int (在本例中)直接保持不变,不会重新创建 :
struct SomeView: View {

    @State var state = 1

    var body: some View {
        // Code to read and write the State goes here
    }
}

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

问题
  • 有没有办法不每次都重新创建 ViewModel?
  • 有没有办法复制 @State @ObservedObject 的属性包装器?
  • 为什么@State 将状态保留在重绘上?

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

    重复的问题

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

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

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

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

    最佳答案

    最后,苹果提供了一个解决方案:@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
                }
            }
        }
    }
    
    如您所见,StateObject在重绘父 View 时保持它的值(value),而 ObservedObject正在重置。

    关于SwiftUI:ObservableObject 在重绘时不会保持其状态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61676823/

    相关文章:

    ios - 如何阻止 TextField 未对齐元素?

    python - SQLAlchemy 子查询,用于从另一个表中求和值

    ios - 在 Swift 中隐藏 TabbedBarController 上的后退按钮

    swiftui - 带有NotificationCenter发布者的SwiftUI

    ios - 如何删除 UITableViewCell 第一行的垂直间距

    xcode - 简单的 SwiftUI 转换似乎不起作用

    python - SQLAlchemy 自定义查询列

    sqlalchemy - PyCharm 无法识别没有冗余 __init__ 的 SqlAlchemy 声明性子类签名

    swift - View 如何使用 url 连接

    objective-c - 在 Swift 中如何配置 AutoreleasingUnsafeMutablePointer?