如何更改子模型实体更新 SwiftUI

发布于 2025-01-20 05:55:36 字数 4533 浏览 4 评论 0原文

我正在尝试为经典的父母(主尾巴等)模型实现UI。当我直接引用孩子们的观点时,所有这些都可以正常工作,但是当我为孩子们介绍一个视图模型时,在更新孩子时,UI停止更新。我很确定,当我介绍儿童视图模型时,我正在创建儿童struct的副本,这就是为什么UI未更新的原因。我怀疑我应该使用但尚未发现的常见模式。

我正在使用Xcode 13.3,因此,我相信Swift 5.5。

这是我用来尝试解决问题的代码。此代码不使用子视图模型,并且UI已正确更新:

工作代码

模型

import Foundation

struct Parent {
    private(set) var children: Array<Child>
    private(set) var numberTimes: Int
    
    init(numberOfChildren: Int) {
        numberTimes = 0
        children = []
        for index in 0..<numberOfChildren {
            children.append(Child(value: index))
        }
        
        toggleRandom()
    }
    
    mutating func choose(child: Child) {
        if let chosenIndex = children.firstIndex(where: { $0.value == child.value }) {
            children[chosenIndex].toggleIsSelected()
        }
    }
    
    mutating func pressMe() {
        numberTimes += 1
    }
    
    mutating func toggleRandom() {
        children[Int.random(in: 0..<children.count)].toggleIsSelected()
    }
}

struct Child: Identifiable {
    let id: Int
    private(set) var value: Int
    private(set) var isSelected = false
    
    init(value: Int) {
        self.id = value
        self.value = value
    }
    
    mutating func toggleIsSelected() {
        isSelected.toggle()
    }
}

使用原始儿童的

import SwiftUI

class ParentViewModel: ObservableObject {
    @Published var parent: Parent
    
    init(numberOfChildren: Int) {
        parent = Parent(numberOfChildren: numberOfChildren)
    }
    
    var children: Array<Child> {
        parent.children
    }
    
    var parentText: String {
        "'Press Me' pressed \(parent.numberTimes)"
    }
    
    func pressMe() {
        parent.pressMe()
    }
    
    func toggleRandom() {
        parent.toggleRandom()
    }
}

原始儿童视图的

import SwiftUI

struct ContentView: View {
    @ObservedObject var parentViewModel: ParentViewModel
    
    var body: some View {
        VStack {
            Text(parentViewModel.parentText).padding()
            Button { parentViewModel.pressMe() }
                label: { Text("Press Me") }
            
            ForEach(parentViewModel.children, id: \.id) { child in
                Text(String(child.value))
                    .foregroundColor(child.isSelected ? .white : .black)
                    .background(child.isSelected ? .black : .white)
            }
            
            Button { parentViewModel.toggleRandom() }
                label: { Text("Toggle Random") }
        }
    }
}

ViewModel这是对儿童数据元素使用视图模型修改的代码。该模型与上面相同,因此只有视图模型和视图代码不同。此代码继续正确地更新父文本,但是当孩子值更改时不会更新UI。

不使用的代码ViewModel

与儿童查看模型

import SwiftUI

class ParentViewModel: ObservableObject {
    @Published var parent: Parent
    private(set) var children: Array<ChildViewModel>
    
    init(numberOfChildren: Int) {
        parent = Parent(numberOfChildren: numberOfChildren)
        children = []
        for child in parent.children {
            children.append(ChildViewModel(child: child))
        }
    }
    
    var parentText: String {
        "'Press Me' pressed \(parent.numberTimes)"
    }
    
    func pressMe() {
        parent.pressMe()
    }
    
    func toggleRandom() {
        parent.toggleRandom()
    }
}

class ChildViewModel: ObservableObject, Identifiable {
    @Published var child: Child
    
    init(child: Child) {
        self.child = child
    }
    
    var value: Int {
        child.value
    }
    
    var isSelected: Bool {
        child.isSelected
    }
}

视图与儿童视图模型

import SwiftUI

struct ContentView: View {
    @ObservedObject var parentViewModel: ParentViewModel
    
    var body: some View {
        VStack {
            Text(parentViewModel.parentText).padding()
            Button { parentViewModel.pressMe() }
                label: { Text("Press Me") }
            
            ForEach(parentViewModel.children, id: \.id) { child in
                ChildView(childViewModel: child)
            }
            
            Button { parentViewModel.toggleRandom() }
                label: { Text("Toggle Random") }
        }
    }
}

struct ChildView: View {
    @ObservedObject var childViewModel: ChildViewModel
    var body: some View {
        Text(String(childViewModel.value))
            .foregroundColor(childViewModel.isSelected ? .white : .black)
            .background(childViewModel.isSelected ? .black : .white)

    }
}

I'm trying to implement a UI for a classic parent-children (master-details, etc.) model. All works fine when I have a direct reference to the children in the view, but when I introduce a view-model for the children the UI stops updating when children are updated. I'm pretty sure that when I introduce the child view model I am creating a copy of the child struct and that is why the UI is not being updated. I suspect there is a common pattern I should be using but have not yet discovered.

I'm using Xcode 13.3 and so, I believe, Swift 5.5.

Here's code I'm using to try to solve the problem. This code does not use a child view model and the UI is correctly updated:

Working Code

Model

import Foundation

struct Parent {
    private(set) var children: Array<Child>
    private(set) var numberTimes: Int
    
    init(numberOfChildren: Int) {
        numberTimes = 0
        children = []
        for index in 0..<numberOfChildren {
            children.append(Child(value: index))
        }
        
        toggleRandom()
    }
    
    mutating func choose(child: Child) {
        if let chosenIndex = children.firstIndex(where: { $0.value == child.value }) {
            children[chosenIndex].toggleIsSelected()
        }
    }
    
    mutating func pressMe() {
        numberTimes += 1
    }
    
    mutating func toggleRandom() {
        children[Int.random(in: 0..<children.count)].toggleIsSelected()
    }
}

struct Child: Identifiable {
    let id: Int
    private(set) var value: Int
    private(set) var isSelected = false
    
    init(value: Int) {
        self.id = value
        self.value = value
    }
    
    mutating func toggleIsSelected() {
        isSelected.toggle()
    }
}

ViewModel with raw children

import SwiftUI

class ParentViewModel: ObservableObject {
    @Published var parent: Parent
    
    init(numberOfChildren: Int) {
        parent = Parent(numberOfChildren: numberOfChildren)
    }
    
    var children: Array<Child> {
        parent.children
    }
    
    var parentText: String {
        "'Press Me' pressed \(parent.numberTimes)"
    }
    
    func pressMe() {
        parent.pressMe()
    }
    
    func toggleRandom() {
        parent.toggleRandom()
    }
}

View with raw children

import SwiftUI

struct ContentView: View {
    @ObservedObject var parentViewModel: ParentViewModel
    
    var body: some View {
        VStack {
            Text(parentViewModel.parentText).padding()
            Button { parentViewModel.pressMe() }
                label: { Text("Press Me") }
            
            ForEach(parentViewModel.children, id: \.id) { child in
                Text(String(child.value))
                    .foregroundColor(child.isSelected ? .white : .black)
                    .background(child.isSelected ? .black : .white)
            }
            
            Button { parentViewModel.toggleRandom() }
                label: { Text("Toggle Random") }
        }
    }
}

Here's the code modified to use a view model for the child data elements. The model is the same as above, so only the view model and view code is different. This code continues to update the parent text correctly, but does not update the UI when child values change.

Not Working Code

ViewModel with child view model

import SwiftUI

class ParentViewModel: ObservableObject {
    @Published var parent: Parent
    private(set) var children: Array<ChildViewModel>
    
    init(numberOfChildren: Int) {
        parent = Parent(numberOfChildren: numberOfChildren)
        children = []
        for child in parent.children {
            children.append(ChildViewModel(child: child))
        }
    }
    
    var parentText: String {
        "'Press Me' pressed \(parent.numberTimes)"
    }
    
    func pressMe() {
        parent.pressMe()
    }
    
    func toggleRandom() {
        parent.toggleRandom()
    }
}

class ChildViewModel: ObservableObject, Identifiable {
    @Published var child: Child
    
    init(child: Child) {
        self.child = child
    }
    
    var value: Int {
        child.value
    }
    
    var isSelected: Bool {
        child.isSelected
    }
}

View with child view model

import SwiftUI

struct ContentView: View {
    @ObservedObject var parentViewModel: ParentViewModel
    
    var body: some View {
        VStack {
            Text(parentViewModel.parentText).padding()
            Button { parentViewModel.pressMe() }
                label: { Text("Press Me") }
            
            ForEach(parentViewModel.children, id: \.id) { child in
                ChildView(childViewModel: child)
            }
            
            Button { parentViewModel.toggleRandom() }
                label: { Text("Toggle Random") }
        }
    }
}

struct ChildView: View {
    @ObservedObject var childViewModel: ChildViewModel
    var body: some View {
        Text(String(childViewModel.value))
            .foregroundColor(childViewModel.isSelected ? .white : .black)
            .background(childViewModel.isSelected ? .black : .white)

    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文