SwiftUI picker selection

Hello,

I am beginning with swift and I'm stuck with a picker issue.

I need to fill a JSON with the selected choice. And it keeps returning me the array.

Here is the Class:
Code Block Swift
class NewPatient: ObservableObject, Codable {
enum CodingKeys: String, CodingKey {
        case lastName, firstName, age, phoneNumber, emailAddr = "email", address, streetNumber, street, typeStreetNumber, typeStreet
    }
    @Published var firstName: String = ""
    @Published var lastName: String = ""
    @Published var age: Int?
static let typeStreetNbr = ["", "bis", "ter"]
    @Published var typeStreetNumber: String = ""
    @Published var indexStreetNbr = 0
    static let typeStrt = ["rue", "boulevard", "avenue", "chemin"]
    @Published var typeStreet: String = ""
    @Published var indexStreet = 0
   init() {    }
required init(from decoder: Decoder) throws {
lastName = try container.decode(String.self, forKey: .lastName)
firstName = try container.decode(String.self, forKey: .firstName)
age = try container.decode(Int.self, forKey: .age)
let address = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .address)
typeStreetNumber = try address.decode(String.self, forKey: .typeStreetNumber)
typeStreet = try address.decode(String.self, forKey: .typeStreet)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(firstName, forKey: .firstName)
try container.encode(lastName, forKey: .lastName)
try container.encode(age, forKey: .age)
var address = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .address)
try address.encode(typeStreetNumber, forKey: .typeStreetNumber)
try address.encode(typeStreet, forKey: .typeStreet)
}
}

Here is the View code:
Code Block Swift
struct SignUpClient: View {
    @State private var backPressed: Bool = false
    var body: some View {
        if backPressed {
            return AnyView(PreSignUp())
        } else {
            return AnyView(SignUpClientView(backPressed: $backPressed, patient: NewPatient()))
        }
    }
}
struct SignUpClientView: View {
    @Binding var backPressed: Bool
    @State private var patientAddressInfo: Bool = false
    @ObservedObject var patient: NewPatient
var body: some View {
Form {
Section {
Picker("Street desc.", selection: $patient.indexStreet) {
ForEach (0 ..< NewPatient.typeStrt.count) {
Text(NewPatient.typeStrt[$0])
}
}.pickerStyle(SegmentedPickerStyle())
}
}
}

I would like to store ether assign e.g. typeStreet variable with the result of the picker selection or just return the selected one.

Thank you for your help,

P.
Answered by OOPer in 646823022

I would like to store ether assign e.g. typeStreet variable with the result of the picker selection or just return the selected one.

Seems your code is too simplified to guess when or where you want to use the value.

But anyway, with this line of code:
Code Block
            return AnyView(SignUpClientView(backPressed: $backPressed, patient: NewPatient()))

You create a new instance of NewPatient, and disposing it (which may contain the result of selection) immediately.

You may need to keep the instance somewhere till you use all the result inside it.
Code Block
struct SignUpClient: View {
@State private var backPressed: Bool = false
@StateObject var newPatient = NewPatient() //<- Keep the newly created instance in an `@StateObject`.
var body: some View {
VStack {
Text("\(newPatient.indexStreet)")
if backPressed {
PreSignUp()
} else {
SignUpClientView(backPressed: $backPressed, patient: newPatient)
}
}
}
}

(Assuming you use Xcode 12. If you are using an older version, please tell us.)
Accepted Answer

I would like to store ether assign e.g. typeStreet variable with the result of the picker selection or just return the selected one.

Seems your code is too simplified to guess when or where you want to use the value.

But anyway, with this line of code:
Code Block
            return AnyView(SignUpClientView(backPressed: $backPressed, patient: NewPatient()))

You create a new instance of NewPatient, and disposing it (which may contain the result of selection) immediately.

You may need to keep the instance somewhere till you use all the result inside it.
Code Block
struct SignUpClient: View {
@State private var backPressed: Bool = false
@StateObject var newPatient = NewPatient() //<- Keep the newly created instance in an `@StateObject`.
var body: some View {
VStack {
Text("\(newPatient.indexStreet)")
if backPressed {
PreSignUp()
} else {
SignUpClientView(backPressed: $backPressed, patient: newPatient)
}
}
}
}

(Assuming you use Xcode 12. If you are using an older version, please tell us.)
Hello @OOPer,

Thank for your quick reply.
It doesn't fix my issue yet :
Code Block
typeStreetNumber: Binding<String>(transaction: SwiftUI.Transaction(plist: []), location:
SwiftUI.LocationBox<SwiftUI.ObservableObjectLocation<Healthsafe.NewPatient, Swift.String>>, _value: "")
typeStreet: Binding<String>(transaction: SwiftUI.Transaction(plist: []), location:
SwiftUI.LocationBox<SwiftUI.ObservableObjectLocation<Healthsafe.NewPatient, Swift.String>>, _value: "")

The thing is I need it to submit my JSON request.
Here is what is needed:
Code Block
{
"lastName": "Appleseed",
"firstName": "John",
"age": 20,
"phoneNumber": [
"+33658893939",
"0638495959",
"0139384458"
],
"address": {
"streetNumber": 3,
"typeStreetNumber": [
"",
"bis",
"ter",
"quater"
],
"typeStreet": [
"rue",
"avenue",
"boulevard",
"chemin"
],
"street": "des paradis",
"zipCode": 95170,
"city": "Paris",
"country": "France"
},
"email": "test@gmail.com",
"password": "oldboy",
"confirmationPassword": "oldboy"
}


Unless for the typeStreet I can do a switch case where I assign the selected value directly to NewPatient.typeStreet 🤔

Thank you,

P.

It doesn't fix my issue yet :

Sorry, but I cannot see what is your current issue.

I would be able to help if you could show enough code. (Not too simplified, please.)
Hi,

Here is the entire view file:
Code Block Swift
struct SignUpClient: View {
    @State private var backPressed: Bool = false
    @StateObject var newPatient = NewPatient()
    var body: some View {
        if backPressed {
            return AnyView(PreSignUp())
        } else {
            return AnyView(SignUpClientView(backPressed: $backPressed, patient: newPatient))
        }
    }
}
struct SignUpClientView: View {
    @Binding var backPressed: Bool
    @State private var patientPersonnalInfo: Bool = true
    @State private var patientAddressInfo: Bool = false
    @State private var patientContactInfo: Bool = false
    @State private var patientPasswdInfo: Bool = false
    @ObservedObject var patient: NewPatient
    var body: some View {
        Button(action: {
            self.backPressed = true
            }) {
            Image(systemName: "chevron.backward")
                .frame(alignment: .topLeading)
                .foregroundColor(.blue)
            Text("Back")
                .frame(width: 325, alignment: .topLeading)
        }
            if patientPersonnalInfo {
                Group {
                    Group {
                        Form {
                            Section {
                                TextField("First name", text: $patient.firstName)
                                TextField("Last Name", text: $patient.lastName)
                                TextField("Age", value: $patient.age, formatter: NumberFormatter())
                                    .keyboardType(.numberPad)
                            }
                        }
                    }
                    Button (action: {
                        self.patientPersonnalInfo = false
                        self.patientAddressInfo = true
                    }){
                        Text("Suivant") //next
                    }
                }.visibility(hidden: $patientPersonnalInfo)
            } else if patientAddressInfo {
                Group {
                    Group {
                        Form {
                            Section {
                                    TextField("Building number", value: $patient.streetNumber, formatter: NumberFormatter())
                                        .keyboardType(.numberPad)
                                  Picker("Number ext.", selection: $patient.indexStreetNbr) {
                                        ForEach (0 ..< NewPatient.typeStreetNbr.count) {
                                            Text(NewPatient.typeStreetNbr[$0])
                                        }
                                    }.pickerStyle(SegmentedPickerStyle())
                                  Picker("Street desc.", selection: $patient.indexStreet) {
                                        ForEach (0 ..< NewPatient.typeStrt.count) {
                                            Text(NewPatient.typeStrt[$0])
                                        }
                                    }.pickerStyle(SegmentedPickerStyle())
                                    TextField("Street name", text: $patient.street)
                              HStack {
                                    TextField("Post code", value: $patient.zipCode, formatter: NumberFormatter())
                                       .frame(width: 100.0, height: 30)
                                      .keyboardType(.numberPad)
                                    Text(" | ")
                                        .foregroundColor(Color.black)
                                    TextField("City", text: $patient.city)
                                        .frame(height: 30)
                                }
                                TextField("Country", text: $patient.country)
                            }
                        }
                    }
                HStack {
                    Button (action: {
                        self.patientPersonnalInfo = true
                        self.patientAddressInfo = false
                    }){
                      Text("Précédent") //Previous
                    }
                    Button (action: {
                        self.patientAddressInfo = false
                        self.patientPasswdInfo = true
                    }){
                        Text("Suivant") //next
                    }
                }
            }.visibility(hidden: $patientAddressInfo)
        } else if patientPasswdInfo {
            Group {
                Group {
                    Form {
                        Section {
                            SecureField("Password", text: $patient.password)
                                .modifier(FormTextFieldStyle())
                            SecureField("Confirm passsword", text: $patient.confirmationPassword)
                                .modifier(FormTextFieldStyle())
                        }
                    }
                }
                HStack {
                    Button(action: {
                        self.patientPasswdInfo = false
                        self.patientContactInfo = true
                    }){
                        Text("Précédent") //previous
                            .modifier(ButtonFormStyleSecondary())
                    }
                    Button(action: {
                        self.submit()
                    }) {
                        Text("Submit")
                            .modifier(ButtonFormStyle())
                    }
                }
            }.visibility(hidden: $patientPasswdInfo)
        }
    }
    func handleServerError(_ res: URLResponse?) {
        print("ERROR: Status Code: \(res!): the status code MUST be between 200 and 299")
    }
    func submit() {
// here under is the URLSession
}

You can also find the project on my GitHub https://github.com/Paul-Rouillard/iOS-Healthsafe, it might be easier to watch [ugly 😅] the code.

Here is the entire view file:

Thanks for showing your code. But where are you trying to submit the JSON request?
In this function :
Code Block Swift
func submit() {
        guard let encoded = try? JSONEncoder().encode(patient) else {
            print("Fail to encode newpatient")
            return
        }
        let url = URL(string: "my/api/signin")!
        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"
        request.httpBody = encoded
        print(String(data: encoded, encoding: .utf8)!)
        URLSession.shared.dataTask(with: url) { data, res, error in
            guard let httpResponse = res as? HTTPURLResponse,
                    (200...299).contains(httpResponse.statusCode) else {
                    self.handleServerError(res)
                return
            }
            if let data = data {
                let decoder = JSONDecoder()
                if let json = try? decoder.decode(NewPatient.self, from: data) {
                    print(json)
                }
                else {
                    let dataString = String(decoding: data, as: UTF8.self)
                    print("Invalid response \(dataString)")
                }
            }
        }.resume()
    }

In this function : 

Sorry, but showing only a function does not make sense. What is patient? When do you call submit()?
I guess the function is a method of some type.
Please show the whole definition of the type, including all relevant things used in it.


I'd like to share the entire file, but it is to log for here ...
This is why I liked my GitHub, where the project is.

With all the code block, you have the overall file.

You have the important elements of the class NewPatient, and the view SignUpClientView.
SignUpClient is just a view controller depending on what the user click.


This is why I liked my GitHub, where the project is. 

You can, at least, show in which file the method or the property is defined, on which line the method is called...
Seems you expect all readers explore the whole repository to find them.

my bad sorry.
here you will have the APIRequests.swift file where are the class declaration.
https://github.com/Paul-Rouillard/iOS-Healthsafe/tree/master/Healthsafe/src

under src/view/SignUpClient.swift is the source code above.
Thanks for showing the direction. That saves plenty of time.

So, this is not taken from your source files, but it is an output from your print statement inside submit(), OK?
Code Block
typeStreetNumber: Binding<String>(transaction: SwiftUI.Transaction(plist: []), location:
SwiftUI.LocationBox<SwiftUI.ObservableObjectLocation<Healthsafe.NewPatient, Swift.String>>, _value: "")
typeStreet: Binding<String>(transaction: SwiftUI.Transaction(plist: []), location:
SwiftUI.LocationBox<SwiftUI.ObservableObjectLocation<Healthsafe.NewPatient, Swift.String>>, _value: "")

If you are confused with this weird output, you should better know it is because you put $ in the String interpolation of the print.
If you want to show the content of each member, remove that $.
Code Block
typeStreetNumber: \(patient.typeStreetNumber)
typeStreet: \(patient.typeStreet)



But still, I do not understand what sort of JSON you are trying to generate.

Here is what is needed:

Is that really the JSON you need to send to the API?
It does not match the structure of NewClient.
  • The JSON has an entry keyed with "address", but NewClient has no member named address.

  • The value for "phoneNumber" in the JSON is an array, but the type of phoneNumber in NewClient is a String, not an Array.

  • The value for "typeStreetNumber" and "typeStreet" in the JSON are arrays, and each array seems containing all possible values. Do you really need to send such all possible values to the API?

Good evening,


The value for typeStreetNumber and typeStreet in the JSON are arrays, and each array seems containing all possible values. Do you really need to send such all possible values to the API?

The arrays are the possibilities the user can sent to the API.
In this case, the API waits for ether one of the possibilities.
Code Block
"phoneNumber": [
"+33658893939",
"0638495959",
"0139384458"
]

I just need to send the user selection in the JSON.

I just need to send the user selection in the JSON. 

Then you can send the value held in patient. No problems left.
Sorry but it doesn't work.

Do tou think I can use this :
Code Block
Picker("Number ext.", selection: $patient.indexStreetNbr) {
ForEach (0 ..< NewPatient.typeStreetNbr.count) {
Text(NewPatient.typeStreetNbr[$0])
}
}
.pickerStyle(SegmentedPickerStyle())
.onReceive([NewPatient.typeStreetNbr].publisher.first, perform: { _ in
self.patient.checkAddr()
})


And call a function I create in the class like so :
Code Block Swift
func checkAddr() {
switch indexStreet {
case 0:
self.typeStreet = "rue"
    case 1:
self.typeStreet = "boulevard"
   case 2:
self.typeStreet = "avenue"
   case 3:
self.typeStreet = "chemin"
default:
self.typeStreet = "rue"
}
}


Sorry but it doesn't work.

Sorry, but it is completely unclear.
  • What is it which doesn't work?

  • What do you mean by doesn't work?

It does not compile? It compiles but causes runtime errors? It compiles and runs without errors but shows unexpected results?

Do tou think I can use this : 

NO. Using publisher for an Array literal does not work as expected in almost all cases.

SwiftUI picker selection
 
 
Q