riczhao
(Richard Zhao)
1
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
mayoff
(Rob Mayoff)
2
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
riczhao
(Richard Zhao)
3
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.
mayoff
(Rob Mayoff)
4
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
riczhao
(Richard Zhao)
5
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?
mayoff
(Rob Mayoff)
6
I don't know. You'd have to ask someone who helped implement those.