Home > Mobile >  How to provide an editing interface for a dictionary in SwiftUI?
How to provide an editing interface for a dictionary in SwiftUI?

Time:11-12

I have data in a dictionary which is [String: String]. What I want to provide is an interface to the user to edit the values in the dictionary, while the keys remain fixed. I can see how to display the values, but putting them into a TextField is what I want, and haven't been able to find how to do.

Here is the code:

struct dictionaryEditor: View {
    @Binding var entries: [String: String]

    var body: some View {
        List {
            ForEach(entries.sorted(by: <), id: \.key) { key, value in
                HStack {
                    Text(key)
                    TextField("", text: $entries[key])
                }
            }
        }
    }
}

This doesn't compile, with no fewer than three errors on the TextField line:

Cannot convert value of type 'Slice<Binding<[String : String]>>' to expected argument type 'Binding'
Cannot convert value of type 'String' to expected argument type 'Range<Binding<[String : String]>.Index>'
Referencing subscript 'subscript(_:)' on 'Binding' requires that '[String : String]' conform to 'MutableCollection'

So obviously I am doing things incorrectly, but I am lost trying to find what the correct way would be, and haven't been able to find an answer in an internet search. Can anyone point me in the right direction?

CodePudding user response:

you could try this simple approach:

struct dictionaryEditor: View {
    @Binding var entries: [String: String]

    var body: some View {
        List {
            ForEach(entries.keys.sorted(by: <), id: \.self) { key in
                HStack {
                    Text(key)
                    TextField("", text: Binding(
                        get: { entries[key]! },
                        set: { entries[key] = $0 }
                     ))
                }
            }
        }
    }
}

struct ContentView: View {
    @State var entries: [String: String] = ["key1":"val1", "key2":"val2", "key3":"val3", "key4":"val4"]
    
    var body: some View {
        dictionaryEditor(entries: $entries)
        Button(action: { print("----> entries: \(entries)") }) {
            Text("print entries")
        }
    }
}

CodePudding user response:

The problem is that entries[key] returns an optional String value while the text parameter of TextField expects a Binding of non optional String.

You can create an optional binding extension and then you can use it safely:

extension Binding where Value == String? {
    var optionalBind: Binding<String> {
        .init(
            get: {
                wrappedValue ?? ""
            }, set: {
                wrappedValue = $0
            }
        )
    }
}

Then you can just add the optionalBind to your code:

struct dictionaryEditor: View {
    @Binding var entries: [String: String]

    var body: some View {
        List {
            ForEach(entries.sorted(by: <), id: \.key) { key, value in
                HStack {
                    Text(key)
                    TextField("", text: $entries[key].optionalBind) // <--
                }
            }
        }
    } 
}
  • Related