SwiftUI : displaying a formatted dictionary from string.

I'm using the following :
(https://developer.apple.com/forums/thread/670303)
Code Block Swift
var data = "Key0:Value\nKey1:Value\nKey2:Value\nKey3:Value\n"
var deciphered = data.split(separator: "\n").reduce(into: [String: String]()) {
let str = $1.split(separator: ":")
if let first = str.first, let value = str.last {
$0[String(first)] = String(value)
}
}

The little issue I'm facing now, is to display the dictionary. I'm using SwiftUI for my app UI.

Here is the code:
Code Block Swift
struct NFCMobileView: View {
@Binding var data: String
var body: some View {
var deciphered = data.split(separator: "\n").reduce(into: [String: String]()) {
let str = $1.split(separator: ":")
if let first = str.first, let value = str.last {
$0[String(first)] = String(value)
}
}
HStack {
Text("Last Name")
TextField("", text: deciphered["lastName"]) /* error */
}
}
}

This is the error I'm having: Cannot convert value of type 'String?' to expected argument type 'Binding<String>'

If I unwrap this way :
Code Block Swift
TextField("", text: deciphered["lastName"] ?? "")

I get this error: Cannot convert value of type 'String' to expected argumument type 'Binding<String>'

I'm getting the binding value data from what is on the NDEF tag
Answered by OOPer in 654585022

If I would need to access different value from my dictionary (like the code snippet here-under)

Thanks for showing your pseudo code, that makes your intention clearer.

First of all, you need to pass Binding<String> to text: in TextField.init(_:_text:).

deciphered["lastName"] is of type String?, and deciphered["lastName"] ?? "" is of type String.
Neither of them are of type Binding<String>.

And one more important thing, the original value storage for the passed Binding should not be a local variable.

Please try this:
Code Block
struct NFCMobileView: View {
@Binding var data: String
@State var deciphered: [String: String] = [:] //<- Make this a Dictionary type
var body: some View {
//Text(deciphered.description) //Uncomment for debugging
Form {
Group {
HStack {
Text("Last name")
TextField("", text: _deciphered.binding("lastName")) //<-
}
HStack {
Text("First name")
TextField("", text: _deciphered.binding("firstName")) //<-
}
HStack {
Text("Gender")
TextField("", text: _deciphered.binding("gender")) //<-
}
HStack {
Text("Age")
TextField("", text: _deciphered.binding("Key3")) //<- (`"Key3"` is the right key?
}
}
}
.onAppear {
//↓ No `var` here
deciphered = data.split(separator: "\n").reduce(into: [String: String]()) {
let str = $1.split(separator: ":")
if let first = str.first, let value = str.last {
$0[String(first)] = String(value)
}
}
}
}
}



Sorry, almost forgotten. You need the following extension to create a Binding<String> from an @State dictionary.

Code Block
extension State
where Value == Dictionary<String, String> {
/// Returns Binding for non-Optional
func binding(_ key: String) -> Binding<String> {
return Binding<String>(
get: {self.wrappedValue[key] ?? ""},
set: {self.wrappedValue[key] = $0}
)
}
}


You have to pass a State var as argument.

I did not test this completely:
Code Block
struct ContentView: View {
@Binding var data: String
@State var decipheredString: String
var body: some View {
let deciphered = data.split(separator: "\n").reduce(into: [String: String]() {
let str = $1.split(separator: ":")
if let first = str.first, let value = str.last {
$0[String(first)] = String(value)
}
}
decipheredString = deciphered["lastname"] ?? ""
return HStack {
Text("Last Name")
TextField("", text: $decipheredString)
}
}
}

If I would need to access different value from my dictionary (like the code snippet here-under), will I need to return n times my HStack or my Form ?

Code Block Swift
struct NFCMobileView: View {
    @Binding var data: String
    @State var decipheredString: String
    var body: some View {
        var deciphered = data.split(separator: "\n").reduce(into: [String: String]()) {
            let str = $1.split(separator: ":")
            if let first = str.first, let value = str.last {
                $0[String(first)] = String(value)
            }
        }
        Form {
            Group {
                HStack {
                    Text("Last name")
                    TextField("", text: deciphered["lastName"] ?? "")
                }
                HStack {
                    Text("First name")
                    TextField("", text: deciphered["firstName"] ?? "")
                }
                HStack {
                    Text("Gender")
                    TextField("", text: deciphered["gender"] ?? "")
                }
                HStack {
                    Text("Age")
                    TextField("", text: deciphered["Key3"] ?? "")
                }
}
        }
}
}

Does the code you show compile ?
Code Block
HStack {
Text("Last name")
TextField("", text: deciphered["lastName"] ?? "")
}


You could create a struct :
Code Block
struct ItemData {
  var firstName: String = ""
  var lastname: String = ""
  var gender: String = ""
  var age = 0
}

Set a Binding

Code Block
    @Binding var itemData: ItemData

and use in TextField
Code Block
                TextField("firstName", text: $itemData.firstName)

See another example here:
https://developer.apple.com/forums/thread/669852
Accepted Answer

If I would need to access different value from my dictionary (like the code snippet here-under)

Thanks for showing your pseudo code, that makes your intention clearer.

First of all, you need to pass Binding<String> to text: in TextField.init(_:_text:).

deciphered["lastName"] is of type String?, and deciphered["lastName"] ?? "" is of type String.
Neither of them are of type Binding<String>.

And one more important thing, the original value storage for the passed Binding should not be a local variable.

Please try this:
Code Block
struct NFCMobileView: View {
@Binding var data: String
@State var deciphered: [String: String] = [:] //<- Make this a Dictionary type
var body: some View {
//Text(deciphered.description) //Uncomment for debugging
Form {
Group {
HStack {
Text("Last name")
TextField("", text: _deciphered.binding("lastName")) //<-
}
HStack {
Text("First name")
TextField("", text: _deciphered.binding("firstName")) //<-
}
HStack {
Text("Gender")
TextField("", text: _deciphered.binding("gender")) //<-
}
HStack {
Text("Age")
TextField("", text: _deciphered.binding("Key3")) //<- (`"Key3"` is the right key?
}
}
}
.onAppear {
//↓ No `var` here
deciphered = data.split(separator: "\n").reduce(into: [String: String]()) {
let str = $1.split(separator: ":")
if let first = str.first, let value = str.last {
$0[String(first)] = String(value)
}
}
}
}
}



Sorry, almost forgotten. You need the following extension to create a Binding<String> from an @State dictionary.

Code Block
extension State
where Value == Dictionary<String, String> {
/// Returns Binding for non-Optional
func binding(_ key: String) -> Binding<String> {
return Binding<String>(
get: {self.wrappedValue[key] ?? ""},
set: {self.wrappedValue[key] = $0}
)
}
}


Happy New year !

Thank you for your help !
I'm having an issue updating the dictionary now ... 😬

Do you have any idea ?

The goal now is to reformat the data from the dictionary into string.

I'm doing it like so :
Code Block Swift
Button(action: {
                self.allowModifications = true
                print("\(String(describing: self.deciphered["lastName"])")) /* this line prints -> Optional("Paul") */
            }) {
                Text("Edit")
            }
nfcWriteButton(data: $data)
                .onTapGesture {
                    self.data = "lastName:\(self.deciphered["lastName"])\ngender:\(self.deciphered["gender"])\nfirstName:\(self.deciphered["firstName"])\nage:\(self.deciphered["age"])\n"
                }

I also tried to silence the warning with String(describing: as Xcode suggested it but it does not change...

I also tried to silence the warning with String(describing:  as Xcode suggested it but it does not change... 

Including Optional("...") is the only issue now?

As noted, the type of self.deciphered["lastName"] is String?, aka Optional<String>.
You need to use non-Optional value, when you want to remove Optional("...").
(Unfortunately, sometimes Xcode suggest completely useless alternatives...)

Nil coalescing operator (??) seems to be a better way to handle this case.
Code Block
Button(action: {
self.allowModifications = true
print("\(self.deciphered["lastName"] ?? "")")
}) {
Text("Edit")
}
nfcWriteButton(data: $data)
.onTapGesture {
self.data = """
lastName:\(self.deciphered["lastName"] ?? "")
gender:\(self.deciphered["gender"] ?? "")
firstName:\(self.deciphered["firstName"] ?? "")
age:\(self.deciphered["age"] ?? "")
"""
}


But you should better consider adopting struct as suggested by Claude31.
So I tried earlier @Claude31 solution and I couldn't get it to work because I was changed it to class and also I did not really know how to bind it through multiple views -- It is fix now. :)

I'm using the structure now and still using the Dictionary method you gave @OOPer.

The issue I have, is not about the Optional(...) I guess.

Let me show a piece of code:
Here is what I gave you and will have to put in place :
Code Block Swift
nfcWriteButton(data: $data)
                .onTapGesture {
                    self.data = "lastName:\(self.deciphered["lastName"])\ngender:\(self.deciphered["gender"])\nfirstName:\(self.deciphered["firstName"])\nage:\(self.deciphered["age"])\n"
                }


I was trying this at the same time :
Code Block Swift
            Button(action: {
                print("\(self.nfcData.lastName)") /* 1 */
print("\(String(describing: self.deciphered["lastName"])")) /* 1 */
                print(self.data) /* 2 */
            }) {
                Text("UPDATE - test")
            }
                .onTapGesture {
                    self.data = "lastName:\(self.deciphered["lastName"])\ngender:\(self.deciphered["gender"])\nfirstName:\(self.deciphered["firstName"])\nage:\(self.deciphered["age"])\n"
print("self.data")
                }

I noticed that (1) printed the updated value. Whereas (2) prints the data string as it got it, without the update made in the onTapGesture
Then I also noticed that the print un the onTapGesture is never called.

Is is normal because I'm doing this onTapGesture for the button and it won't accept it because there is already the action in the first part of the Button?
Will it - the onTapGesture method - work for nfcWriteButton ?

Here is the code of this button:
Code Block Swift
struct nfcWriteButton : UIViewRepresentable {
    @Binding var data: String
    func updateUIView(_ uiView: UIButton, context: Context) {    }
    func makeUIView(context: UIViewRepresentableContext<nfcWriteButton>) -> UIButton {
        let button = UIButton()
        button.setTitle("Write on Tag", for: .normal)
        button.addTarget(context.coordinator, action: #selector(context.coordinator.beginScan(_:)), for: .touchUpInside)
        return button
    }
    func makeCoordinator() -> nfcWriteButton.Coordinator {
        return Coordinator(data: $data)
    }
}

Will it - the onTapGesture method - work for nfcWriteButton ?

Don't you think it is too far from the topic shown in the opening post of this thread?

You should better start a new thread or go back to another old thread of yours.

Please keep one thread one topic.
SwiftUI : displaying a formatted dictionary from string.
 
 
Q