SwiftUI:如何更改@Published对象的值。更改已发布变量的值不起作用

时间:2019-11-18 14:33:48

标签: ios swift swiftui

我正在做一个页面,其中包含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)
                        }
                    }
                }
            }
        }
    }
}

The page looks like this

3 个答案:

答案 0 :(得分:2)

解决方案

DCViewModel更改为struct

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


说明

@Published是一个属性包装程序,仅在设置了包装的值 时,才发布更改消息。

  

值类型和引用类型之间的区别:当您更改一个持有值类型(结构)的变量时,实际上已为其设置了一个新值。但是,当您更改引用类型(类)的值时,指向该对象的变量将不会更改。

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

答案 1 :(得分:1)

有两个选项。

  1. DCViewModel class更改为struct
  2. 手动制作DCListViewModel class进行发布。

请参见@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)
                        }
                    }
                }
            }
        }
    }
}

答案 2 :(得分:0)

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