SwiftUI: how to use dictionary as @Binding?

I'm add a row of tag buttons as a filter. Each button have two state, selected and unselected. It always reports error for dictionary binding. Please see bellow comments in code.

struct TagButton: View {
    var body: some View {
        Button(action: {
            self.selected.toggle()
        }) {
            if selected {
                Text(name)
                .underline()
            } else {
                Text(name)
            }
        }
        .padding()
    }
    @Binding var selected: Bool
    let name: String
}

struct TagsView: View {
    @Binding var tags: [String:Bool]
    
    var body: some View {
        ScrollView(.horizontal) {
            HStack {
                ForEach(tags.keys.sorted(), id: \.self) {k in   // I don't need  "sorted" here, but need a key array
                    TagButton(selected: self.$tags[k], name: k)   // [k] error: Missing argument label 'dynamicMember:' in subscript
                }
            }
        }
    }
}
1 Like

The problem, I believe, is that self.$tags[k] is a Binding<Bool?>, not a Binding<Bool>.

I tried self.$tags[k]! but that doesn't fix the error. I also tried self.$tags[k, default: false], but that also didn't fix the error.

You could manually create a Binding<Bool> instead, like this:

struct TagsView: View {
    @Binding var tags: [String:Bool]

    var body: some View {
        ScrollView(.horizontal) {
            HStack {
                ForEach(tags.keys.sorted(), id: \.self) { k in   // I don't need  "sorted" here, but need a key array
                    TagButton(selected: self.binding(for: k), name: k)   // [k] error: Missing argument label 'dynamicMember:' in subscript
                }
            }
        }
    }

    private func binding(for key: String) -> Binding<Bool> {
        return .init(
            get: { self.tags[key, default: false] },
            set: { self.tags[key] = $0 })
    }
}

Or you could change TagButton to use a Binding<Bool?> like this:

struct TagButton: View {
    var body: some View {
        Button(action: {
            self.selected?.toggle()
        }) {
            if selected ?? false {
                Text(name)
                .underline()
            } else {
                Text(name)
            }
        }
        .padding()
    }

    @Binding var selected: Bool?
    let name: String
}

If you change TagButton to take a Binding<Bool?>, then self.$tags[k] works.

2 Likes

Thanks, Rob! The second method works. But I don't feel comfortable with it. From design perspective, selected of TagButton can not be optional. Further more, if I use view like Toggle, it does not accept optional Binding.

The first method failed here.

This is my complete playground. It compiles without complaint in Xcode 11.4:

import SwiftUI

struct TagButton: View {
    var body: some View {
        Button(action: {
            self.selected.toggle()
        }) {
            if selected {
                Text(name)
                .underline()
            } else {
                Text(name)
            }
        }
        .padding()
    }

    @Binding var selected: Bool
    let name: String
}

struct TagsView: View {
    @Binding var tags: [String:Bool]

    var body: some View {
        ScrollView(.horizontal) {
            HStack {
                ForEach(tags.keys.sorted(), id: \.self) { k in   // I don't need  "sorted" here, but need a key array
                    TagButton(selected: self.binding(for: k), name: k)   // [k] error: Missing argument label 'dynamicMember:' in subscript
                }
            }
        }
    }

    private func binding(for key: String) -> Binding<Bool> {
        return .init(
            get: { self.tags[key, default: false] },
            set: { self.tags[key] = $0 })
    }
}
3 Likes

Ah, my fault. I overlooked the binding function. It's kind of magic to me. How is the Binding instance created by us working for subscriber/publisher? I assuming @State is the publisher and views that uses it via @Binding are subscribers. Would you help me understand how Binding and State are linked?

I don't know. You'd have to ask someone who helped implement those.