如何在NavigationLink中为目标视图重用视图模型实例

时间:2019-07-31 10:55:43

标签: ios swift swiftui

在下面提供的代码中,我遇到一个问题:正在重新创建DetailViewModel。发生这种情况是因为ContentView更新了,它也重新创建了所有NavigationLink和目的地。因此,DetailViewModel中的状态被重置。

这是一些示例代码:

import SwiftUI
import Combine

struct ContentView: View {

    let items = ["Item A", "Item B", "Item C"]

    @State var contentViewUpdater = 0

    var body: some View {
        NavigationView {
            VStack {
                Button("Update ContentView: \(contentViewUpdater)") {
                    self.contentViewUpdater += 1
                }
                List(items, id: \.self) { item in
                    // How to prevent DetailViewModel from recreating after this ContentView receives an update?
                    NavigationLink(destination: DetailView(model: DetailViewModel(item: item))) {
                        Text(item)
                    }
                }
            }
        }
    }
}

final class DetailViewModel: ObservableObject {
    let item: String
    @Published var counter = 0

    init(item: String) {
        self.item = item
    }
}

struct DetailView: View {
    @ObservedObject var model: DetailViewModel

    var body: some View {
        VStack {
            Text("Counter for \(model.item): \(model.counter)")
            Button("Increase counter") {
                self.model.counter += 1
            }
        }
    }
}

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

这里是问题的屏幕录像。如果DetailViewModel.counter更新,则会重置ContentView变量。

screenrecording

如何在父视图更新时阻止DetailViewModel中的状态重置?

2 个答案:

答案 0 :(得分:2)

对不起,我的代码已从您的代码改编而来,因为我尚未更新到最新的beta,但这对我有用。我使用了"Lifting State Up" from React的概念,并将模型数据移入了Master视图本身。

在操场上

import SwiftUI
import PlaygroundSupport


final class ItemViewModel : BindableObject {
    let willChange = PassthroughSubject<Void, Never>()

    var name: String {
        willSet { willChange.send() }
    }
    var counter: Int = 0 {
        willSet { willChange.send() }
    }

    init(name: String) {
        self.name = name
    }
}


struct ContentView : View {
    let items = [
        ItemViewModel(name: "Item A"),
        ItemViewModel(name: "Item B"),
        ItemViewModel(name: "Item C")
    ]

    @State var contentViewUpdater = 0

    var body: some View {
        NavigationView {
            VStack {
                Button("Update ContentView: \(contentViewUpdater)") {
                    self.contentViewUpdater += 1
                }
                List(items) { model in
                    NavigationLink(destination: DetailView(model: model)) {
                        Text(model.name)
                    }
                }
            }
        }
    }
}


struct DetailView : View {
    @ObjectBinding var model: ItemViewModel

    var body: some View {
        let name = model.name
        let counter = model.counter
        return VStack {
            Text("Counter for \(name): \(counter)")
            Button("Increase counter") {
                self.model.counter += 1
            }
        }
    }
}


PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
PlaygroundPage.current.needsIndefiniteExecution = true

Screen recording from Playground

答案 1 :(得分:1)

您的视图不必知道SwiftUI是否重新生成视图。就您而言,我认为您必须改变模型的布局方式。

在您的情况下,我将采用两种方法:

  1. 具有一个包含所有项目数据的模型(首选)。
  2. 或者,如果您需要为每个项目使用不同的模型,请对其进行建模,以使其始终保持分配状态(而不必考虑视图的生命周期)。也许您可以使用环境对象,或者由State变量持有一个DetailViewModel()模型的数组。请记住,即使重新生成视图,也要分配状态变量。

我认为第一种选择是:拥有一个单一模型会更好。但是为了说明我的第二点,这是一个可能的实现:

import SwiftUI
import Combine

struct Item: Identifiable {
    let id = UUID()
    let model: DetailViewModel

    init(name: String) {
        self.model = DetailViewModel(item: name)
    }
}

struct ContentView: View {

    @State private var items = [Item(name: "Item A"), Item(name: "Item B"), Item(name: "Item C")]
    @State var contentViewUpdater = 0

    var body: some View {
        NavigationView {
            VStack {
                Button("Update ContentView: \(contentViewUpdater)") {
                    self.contentViewUpdater += 1
                }
                List(items, id: \.id) { item in
                    NavigationLink(destination: DetailView(model: item.model)) {
                        Text(item.model.item)
                    }
                }
            }
        }
    }
}

final class DetailViewModel: ObservableObject {
    let item: String
    @Published var counter = 0

    init(item: String) {
        self.item = item
    }
}

struct DetailView: View {
    @ObservedObject var model: DetailViewModel

    var body: some View {
        VStack {
            Text("Counter for \(model.item): \(model.counter)")
            Button("Increase counter") {
                self.model.counter += 1
            }
        }
    }
}