【问题标题】:SwiftUI: How to change @Published object value. Changing a value of a published variable is not workingSwiftUI:如何更改 @Published 对象值。更改已发布变量的值不起作用
【发布时间】:2020-03-13 23:02:06
【问题描述】:

我正在制作一个包含 5 个带有单选按钮的选项的页面。当我们点击一​​个选项时,元素的颜色应该会改变。我正在从 API 响应中获取这些选项。我正在使用 MVVM 模型。我附上下面的代码。

我正在苦苦挣扎的是,当我更改布尔值时,它并没有改变!!!

查看模型代码

import Foundation
import Combine

class DCListViewModel: ObservableObject {
    @Published var DCList = [DCViewModel]()

    init() {
        fetchDcs()
    }

    func fetchDcs() {
        ARMServices().getAllDc { (dcArr) in
            if let dcArr = dcArr {
                for dc in dcArr{
                    self.DCList.append(DCViewModel(dc: dc, isSelected: false))
                }
            }
        }
    }
}


class DCViewModel {
    var id = UUID()
    var dcStruct: DC
    var isSelected: Bool

    init(dc: DC, isSelected: Bool) {
        self.dcStruct = dc
        self.isSelected = isSelected
    }

    var url: String {
        return self.dcStruct.url  ?? "empty URL"
    }

    var dc: String {
        return self.dcStruct.dc ?? "empty dc name"
    }
}

查看页面代码

struct SelectTableView: View {

    var body: some View {
        NavigationView {

                VStack {
                    DCListView(dcList: self.dcListVM.DCList)
                        .padding(.horizontal)
                }
            .navigationBarTitle("Select the Data Centre")
        }
    }
}


struct DCListView: View{
    var dcList: [DCViewModel]

    init(dcList: [DCViewModel]) {
        self.dcList = dcList
    }

    var body: some View {
        ForEach(self.dcList, id: \.id) { dc in
            Button(action: {
                print("Tapped")
                dc.isSelected.toggle()
            }){
                ZStack {
                    RoundedRectangle(cornerRadius: 8)
                        .stroke(dc.isSelected ? Color.init("borderSelected"): Color.init("border"))
                        .frame(height: 56)
                        .foregroundColor(.clear)

                    HStack {
                        Text(dc.dc)
                            .font(.custom("Montserrat", size: 16))
                            .fontWeight(.medium)
                            .foregroundColor(dc.isSelected ? Color.init("borderSelected") : .white)
                            .padding()

                        Spacer()

                        ZStack {
                            Circle()
                                .stroke(dc.isSelected ? Color.init("borderSelected") : Color("circleBorder"))
                                .frame(width: 18, height: 18)
                                .padding()

                            Circle()
                                .frame(width: 10, height: 10)
                                .foregroundColor(dc.isSelected ? Color.init("borderSelected"): Color.clear)
                        }
                    }
                }
            }
        }
    }
}

【问题讨论】:

    标签: ios swift swiftui


    【解决方案1】:

    解决方案

    DCViewModel 更改为struct

    或者,如果您必须将其保留为class,则使DCViewModel 符合ObservableObject ,并在每个View 中保留一个引用。 (这意味着您应该将该行作为单个 View 减去)


    说明

    @Published 是一个属性包装器,仅当包装的值已设置时才发布更改消息。

    值类型和引用类型的区别:当你改变一个持有值类型(结构)的变量时,它实际上已经被设置了一个新值。但是当你改变一个引用类型(类)的值时,指向那个对象的变量不会改变。

    这就是为什么,在您的情况下,当您向 Array(值类型)添加新的 DCViewModel 时,您总是为该数组设置一个新值,因此 @Published 生效。但是,包装的值是DCViewModel,它是一个类(引用类型)。当您更改DCViewModel 的值时,包装值的setter 将不会被调用,@Published 也不会发布消息。

    【讨论】:

    • 感谢@ribilynn 解释这个场景。这很好用。
    【解决方案2】:

    有两种选择。

    1. DCViewModelclass更改为struct
    2. 手动使DCListViewModelclass发布。

    @kontiki 's answer

    DCViewModel class 更改为struct

    struct DCViewModel {
        var id: UUID
        var dcStruct: DC
        var isSelected: Bool
    
        init(dc: DC, isSelected: Bool) {
            self.id = UUID()
            self.dcStruct = dc
            self.isSelected = isSelected
        }
    
        var url: String {
            return self.dcStruct.url  ?? "empty URL"
        }
    
        var dc: String {
            return self.dcStruct.dc ?? "empty dc name"
        }
    }
    
    struct SelectTableView: View {
        // dcListVM missed
        // I assume superview of SelectTableView exist.
        @ObservedObject var dcListVM: DCListViewModel
    
        var body: some View {
            NavigationView {
                    VStack {
                        DCListView(dcList: $dcListVM.DCList)
                            .padding(.horizontal)
                    }
                .navigationBarTitle("Select the Data Centre")
            }
        }
    }
    
    struct DCListView: View{
        @Binding var dcList: [DCViewModel] // Use @Binding for two-way binding
    
        var body: some View {
            // You can't use `ForEach(data: self.dcList, id: \.id ) { dc in`
            // Because `dc` is immutable
    
            ForEach(0..<self.dcList.count) { dc in
                Button(action: {
                    print("Tapped")
                    self.dcList[dc].isSelected.toggle()
                }){
                    ZStack {
                        RoundedRectangle(cornerRadius: 8)
                            .stroke(self.dcList[dc].isSelected ? Color.red: Color.black)
                            .frame(height: 56)
                            .foregroundColor(.clear)
    
                        HStack {
                            Text(self.dcList[dc].dc)
                                .font(.system(size: 16))
                                .fontWeight(.medium)
                                .foregroundColor(self.dcList[dc].isSelected ? Color.red : Color.black)
                                .padding()
    
                            Spacer()
    
                            ZStack {
                                Circle()
                                    .stroke(self.dcList[dc].isSelected ? Color.red : Color.black)
                                    .frame(width: 18, height: 18)
                                    .padding()
    
                                Circle()
                                    .frame(width: 10, height: 10)
                                    .foregroundColor(self.dcList[dc].isSelected ? Color.red: Color.clear)
                            }
                        }
                    }
                }
            }
        }
    }
    
    

    手动创建DCListViewModel 的实例以发布。

    struct SelectTableView: View {
        // dcListVM missed
        // I assume superview of SelectTableView exist.
        var dcListVM: DCListViewModel()
    
        var body: some View {
            NavigationView {
    
                    VStack {
                        DCListView(dcList: self.dcListVM) // DCListViewModel instead of [DCViewModel]
                            .padding(.horizontal)
                    }
                .navigationBarTitle("Select the Data Centre")
            }
        }
    }
    
    
    struct DCListView: View {
        @ObservedObject var dcListVM: DCViewModel // DCListViewModel instead of [DCViewModel]
    
        // See "Memberwise Initializers for Structure Types" in docs.swift.org
        // init(dcListVM: DCViewModel) {
        //     self.dcList = dcList
        // }
    
        var body: some View {
            ForEach(self.dcList.DCList, id: \.id) { dc in
                Button(action: {
                    print("Tapped")
                    dc.isSelected.toggle()
                    self.dcList.objectWillChange.send()  // Manually make dcList to publish
                }){
                    ZStack {
                        RoundedRectangle(cornerRadius: 8)
                            .stroke(dc.isSelected ? Color.init("borderSelected"): Color.init("border"))
                            .frame(height: 56)
                            .foregroundColor(.clear)
    
                        HStack {
                            Text(dc.dc)
                                .font(.custom("Montserrat", size: 16))
                                .fontWeight(.medium)
                                .foregroundColor(dc.isSelected ? Color.init("borderSelected") : .white)
                                .padding()
    
                            Spacer()
    
                            ZStack {
                                Circle()
                                    .stroke(dc.isSelected ? Color.init("borderSelected") : Color("circleBorder"))
                                    .frame(width: 18, height: 18)
                                    .padding()
    
                                Circle()
                                    .frame(width: 10, height: 10)
                                    .foregroundColor(dc.isSelected ? Color.init("borderSelected"): Color.clear)
                            }
                        }
                    }
                }
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

      您只是将 dclist 的副本“提供”给您的视图,因此视图不知道发生了什么变化。将@binding 添加到您的视图到您的变量 dclist。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-11-26
        • 2020-10-18
        • 2019-07-13
        • 1970-01-01
        • 2021-09-07
        • 2023-02-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多