1

I will need to display a collapsed menu in SwiftUI, it is possible to pass one single bool value as binding var to subviews but got stuck when trying to pass that value from a dictionary.

see code below:

struct MenuView: View {
    @EnvironmentObject var data: APIData
    @State var menuCollapsed:[String: Bool] = [:]
    @State var isMenuCollapsed = false;

// I am able to pass self.$isMenuCollapsed but self.$menuCollapsed[menuItem.name], why?

var body: some View {

            if data.isMenuSynced {
                List() {
                    ForEach((data.menuList?.content)!, id: \.name) { menuItem in
                        TopMenuRow(dataSource: menuItem, isCollapsed: self.$isMenuCollapsed)
                            .onTapGesture {
                                if menuItem.isExtendable() {
                                    let isCollapsed = self.menuCollapsed[menuItem.name]
                                    self.menuCollapsed.updateValue(!(isCollapsed ?? false), forKey: menuItem.name)
                                } else {
                                    print("Go to link:\(menuItem.url)")
                                }

                            }
                    }
                }
            }else {
                Text("Loading...")
            }
        }

}

in ChildMenu Row:

struct TopMenuRow: View {
    var dataSource: MenuItemData
    @Binding var isCollapsed: Bool

    var body: some View {
        ChildView(menuItemData)
            if self.isCollapsed {
                //display List of child data etc
            }
        }
    }
}

If I use only one single bool as the binding var, the code is running ok, however, if I would like to use a dictionary to store each status of the array, it has the error of something else, see image blow: enter image description here

if I use the line above, it's fine.

Any idea of how can I fix it?

Thanks

matee
  • 91
  • 1
  • 10
  • 1
    Dictionary is not a RandomAccessCollection, so not supported in ForEach and Binding, use instead view model with Array of MenuItem struct containing title & selected (as I understood) properties. Don't hit into the wall - think different. =) – Asperi Mar 30 '20 at 12:33
  • you can create a bindable dictionary https://stackoverflow.com/questions/56978746/how-do-i-bind-a-swiftui-element-to-a-value-in-a-dictionary – Hai Feng Kao Dec 09 '20 at 12:30

1 Answers1

4

How to use dictionary as a storage of mutable values with State property wrapper?

As mentioned by Asperi, ForEach requires that source of data conforms to RandomAccessCollection. This requirements doesn't apply to State property wrapper!

Let see one of the possible approaches in the next snippet (copy - paste - run)

import SwiftUI

struct ContentView: View {

    @State var dict = ["alfa":false, "beta":true, "gamma":false]

    var body: some View {
        List {
            ForEach(Array(dict.keys), id: \.self) { (key) in
                HStack {
                    Text(key)
                    Spacer()
                    Text(self.dict[key]?.description ?? "false").onTapGesture {
                        let v = self.dict[key] ?? false
                        self.dict[key] = !v
                    }.foregroundColor(self.dict[key] ?? false ? Color.red: Color.green)
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

with the following result

enter image description here

user3441734
  • 16,722
  • 2
  • 40
  • 59