如何在 NavigationLink 中为目标视图重用视图模型实例
How to reuse view model instance for destination view in a NavigationLink
在下面提供的代码中,我遇到了 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
这是问题的屏幕录像。如果 ContentView
更新,DetailViewModel.counter
var 将重置。
如何防止 DetailViewModel
中的状态在父视图更新时重置?
您的视图不必知道 SwiftUI 是否重新生成视图。在你的情况下,我认为你必须改变你布置模型的方式。
针对您的情况,我会采用两种方法:
- 拥有一个包含所有项目数据的模型(首选)。
- 或者,如果您需要为每个项目使用不同的模型,请使其始终保持分配状态(无需考虑视图的生命周期)。也许您可以使用环境对象,或者拥有一个由 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
}
}
}
}
抱歉,我的代码改编自您的代码,因为我还没有更新到最新的测试版,但这对我有用。我使用了 "Lifting State Up" from React 的概念,并将模型数据移动到主视图本身。
来自游乐场:
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
在下面提供的代码中,我遇到了 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
这是问题的屏幕录像。如果 ContentView
更新,DetailViewModel.counter
var 将重置。
如何防止 DetailViewModel
中的状态在父视图更新时重置?
您的视图不必知道 SwiftUI 是否重新生成视图。在你的情况下,我认为你必须改变你布置模型的方式。
针对您的情况,我会采用两种方法:
- 拥有一个包含所有项目数据的模型(首选)。
- 或者,如果您需要为每个项目使用不同的模型,请使其始终保持分配状态(无需考虑视图的生命周期)。也许您可以使用环境对象,或者拥有一个由 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
}
}
}
}
抱歉,我的代码改编自您的代码,因为我还没有更新到最新的测试版,但这对我有用。我使用了 "Lifting State Up" from React 的概念,并将模型数据移动到主视图本身。
来自游乐场:
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