如何让我的 Button 反映我的 EnvironmentObject 的 Bool?

How can I get my Button to reflect the Bool of my EnvironmentObject?

我正在尝试使用 @EnvironmentObject 来跟踪一些对象数组的 Bool 状态,但我遇到了一个超出范围的索引并且无法弄清楚原因。

我正在尝试跟踪在三个容器之间进行选择的“查询”。每个容器都有一定数量的“框架”,我需要切换这些框架并更改按钮标签的颜色:

    import SwiftUI

    class Query: ObservableObject {
        
        @Published var selectedContainer: Int = 2
        @Published var frames: [Bool] = [true, true, true, true, true, true, true, true]
        
        func resetFrames() {
            switch selectedContainer {
                case 0:
                    self.frames = [true, true, true, true, true]
                    print("Boxes")
                case 1:
                    self.frames = [true, true, true]
                    print("Bags")
                case 2:
                    self.frames = [true, true, true, true, true, true, true, true]
                    print("Barrels")
                default:
                    self.frames = [true, true, true]
            }
        }
            
        func satisfiedFrames() {
            let shouldReset = frames.allSatisfy { [=10=] == false }
            print(shouldReset)
            if shouldReset == true {
                resetFrames()
            }
        }
     }

    enum Container {
        case box
        case bag
        case barrel
        
        var tag: Int {
            switch self {
                case .box:
                    return 0
                case .bag:
                    return 1
                case .barrel:
                    return 2
            }
        }
        
        var name: String {
            switch self {
                case .box:
                    return "Box"
                case .bag:
                    return "Bag"
                case .barrel:
                    return "Barrel"
            }
        }
    }

我也有我的颜色数组:

let dvColors: [Color] = [
    Color.red,
    Color.orange,
    Color.yellow,
    Color.green,
    Color.blue,
    Color.indigo,
    Color.purple,
    Color.pink
]

然后我有一个选择器可以像这样在容器之间切换:

    @EnvironmentObject var query: Query
    var container: [Container] = [.box, .bag, .barrel]
    
    var body: some View {
        Picker(selection: $query.selectedContainer, label: Text("Container")){
            ForEach(0..<container.count) { index in
                Text(self.container[index].name)
                    .tag(index)
            }
        }
        .onChange(of: query.selectedContainer, perform: changeContainer)
    }
    
    func changeContainer(_ tag: Int) {
        print("CHANGE CONTAINER ON PICKER")
        print("TAG: \(tag)")
        query.selectedContainer = tag
        print("QUERY CONTAINER: \(query.selectedContainer)")
        query.resetFrames()
        print("FRAMES COUNT:  \(query.frames.count)")
    }
}

最后,这是我的内容视图:

import SwiftUI

struct ContentView: View {
    @StateObject var query = Query()
    var body: some View {
        NavigationView {
            ZStack {
                ScrollView {
                    Text("FRAME COUNT: \(query.frames.count)")
                    Text("Container: \(query.selectedContainer)")
                    Spacer()
                }
                .navigationBarTitleDisplayMode(.inline)
                .toolbar(){
                    ToolbarItem(placement: .principal, content: {
                        ContainerPicker()
                    })
                }
                VStack {
                    Spacer()
                    TheHStack()
                }
            }
        }
        .environmentObject(query)
    }
}

struct TheHStack: View {
    
    @EnvironmentObject var query: Query
    
    var body: some View {
        print(query.frames)
        return HStack (spacing: 10) {
            ForEach(query.frames.indices, id: \.self) { value in
                ColouredText(value: value)
            }
        }
    }
}

struct ColouredText: View {
    
    @EnvironmentObject var query: Query
    
    var value: Int
    
    var body: some View {
        
        Button(action: {
            query.frames[value].toggle()
            print(query.frames)
            print("\(value)")
        }, label: {
            Text("\(value + 1)")
                .foregroundColor(query.frames[value] ? dvColors[value] : .gray) // THIS LINE FAILS, I GUESS WHEN LOOKING UP THE COLOUR?
            
        })
    }
}

我希望做的是,当我使用选择器进行选择时,容器的布尔值都设置为 TRUE。然后点击按钮切换该容器中的框架。而当用picker选择另一个容器时,新容器的frame都是TRUE。此外,如果所有布尔值都变为 FALSE,则它们会自动重置为全部 TRUE。

如果我将三元色更改为一种颜色,一切正常。我的打印报表显示了正确的更改。但是我无法让 UI 正确更新。

方法一

您可以快速检查一下是否会出现越界错误。 SwiftUI 有时会保持视图的时间稍长,这可能会导致这样的问题。

ColouredText中,更改:

.foregroundColor(query.frames[value] ? dvColors[value] : .gray)

收件人:

.foregroundColor(value < query.frames.count && query.frames[value] ? dvColors[value] : .gray)

方法二

解决这个问题的另一种方法(但我可能不会这样做,因为你正在消除视图的分裂)是直接将 ColouredTextbody 放在它所在的位置需要:

struct TheHStack: View {
    @EnvironmentObject var query: Query

    var body: some View {
        print(query.frames)
        return HStack (spacing: 10) {
            ForEach(query.frames.indices, id: \.self) { value in
                Button(action: {
                    query.frames[value].toggle()
                    print(query.frames)
                    print("\(value)")
                }, label: {
                    Text("\(value + 1)")
                        .foregroundColor(query.frames[value] ? dvColors[value] : .gray)
                })
            }
        }
    }
}

方法三

您也可以通过在 value 的同时传入 query 来解决此问题。这意味着它们永远不会不同步。

ForEach(query.frames.indices, id: \.self) { value in
    ColouredText(value: value, query: query)
}
struct ColouredText: View {
    let value: Int
    let query: Query

    var body: some View {
        /* ... */
    }
}