Create a Picker that returns a String - swift

Hello everyone
I am creating a form that allows me to modify the data of #EnvironmentObject variable.
Therefore, I would like to be able to create a Picker that returns a String. However, after many unsuccessful attempts, I have the impression that a Picker cannot return a String.
Anyone would have an idea for a Picker that returns a String (maybe through UIKit ?).
Here's my code :
struct UserStruct {
var firstName: String
var lastName: String
var birthDate: Int
var city: String
var postalCode: Int
var street: String
var streetCode: String
var country: String
}
class User: ObservableObject {
#Published var userProfile = UserStruct()
// Other stuff here
}
// Then in my FormView:
// I declare the object as #EnvironmentObject
#EnvironmentObject var userStore: User
// I declare an array which contains all the country for the picker
let country = ["France", "Russie", "USA"]
// In my var body: some View...
// Trying to change the value of country of the userStore object
Picker(selection: $userStore.userProfile.country, label: Text("Make a choice")) {
ForEach(0 ..< country.count) { index in
Text(self.country[index]).tag(index)
}
Thank you all for your help.

you could try something like this:
let country = ["France", "Russie", "USA"]
#State var countrySelection = 0
Picker(selection: Binding(get: {
self.countrySelection
}, set: { newVal in
self.countrySelection = newVal
self.userStore.userProfile.country = self.country[self.countrySelection]
}), label: Text("Make a choice")) {
ForEach(0 ..< country.count) { index in
Text(self.country[index]).tag(index)
}
}

Related

How to match id's between data in Swift?

The screen representing user profile. I want to move to the profile of chosen friend (from 'friends' list). I suppose that I have to match between "id"s. This is how I've tried, but it doesn't work. How can I match "id"s?
// This didn't work
user: session.users.first(where: { $0.id == friend.id })
.
struct DetailView: View {
var user: User
var session: Session
...
...
var body: some View {
...
// ... user profile ...
// Friends section
ForEach(user.friends, id: \.self.id) { friend in
NavigationLink(destination:
DetailView(user: session.users.first(where: { $0.id == friend.id }),
session: session)) {
HStack {
Text(friend.name)
}
}
}
}
}
this is the data organization.
class Session: ObservableObject {
#Published var users = [User]()
}
struct User: Codable {
var id: String
var isActive: Bool
var name: String
var age: Int16
var company, email, address, about: String
var registered: Date
var tags: [String]
var friends: [Friend]
}
struct Friend: Codable {
var id: String
var name: String
}
EDIT
If there is a better way to match between "id"s. How social networks do this?
// added exclamation mark (!) and it worked out
session.users.first(where: { $0.id == friend.id })!
I don't know why you do this but your Details view does not have an optional User type and you have passed an optional user (Array.first gives you an optional object). So you need to handle this or add an exlemetion sign at the end. like this
session.users.first(where: { $0.id == friend.id })!

how to check key of json data exists or not in SwiftUI?

Could you help me, please?
How to check a key of json data “subtitle” exits or not ?
//Model.swift
import Foundation
var data: [Post] = load("song.json”)
......
//Post.swift
struct Post: Codable, Hashable{
var id: Int
var title: String
var subtitle: String
var body: String
}
I got json data value like this:
Text(data[index].title)
.
[
{
“id”:1,
“title”:”Title Value”,
“body”:”Body Value"
}
,
{
“id”: 1,
“title”: ”Title Value”,
“subtitle”:” SubTitle Value”,
“body”: “Body Value"
} ,etc..
]
Make subtitle optional in model and use it conditionally in view, like
struct Post: Codable, Hashable{
var id: Int
var title: String
var subtitle: String? // << this !!
var body: String
}
and somewhere in body:
VStack(alignment: .leading) {
Text(data[index].title)
if let subtitle = data[index].subtitle {
Text(subtitle)
}
}

How to append an identifiable struct list?

struct Gg: Identifiable{
let id: Int
let task: String
}
struct ContentView: View {
#State private var items = [Gg(id: 1, task:"take the trash out"), Gg(id: 2, task:"Go for a run")]
var body: some View {
AddTaskUIView(title: "Add Item", isShown: $isPresented, text: $text, onDone: { text in
self.items.append(text)
self.text = ""
})
}}
I am getting an error
Cannot convert value of type 'String' to expected argument type 'Gg'
how to I append to the task of the structure Gg?
I am new to swift so I would appriacte any help thanks.
struct Gg: Identifiable{
let id: UUID = UUID()
let task: String
init(_ task: String) {
self.task = task
}
}
...
#State private var items = [Gg("take the trash out"), Gg("Go for a run")]
...
self.items.append(Gg(text))
Heres a solution that does not have the problem of keeping track of the id yourself.

Generic parameter 'Parent' could not be inferred

I'm working on my first Swift/SwiftUI project (other than the tutorials) and have run into what I think is a common problem -- the error Generic parameter 'Parent' could not be inferred -- toward the top of the view when the problem is actually with a List I'm trying to generate lower down in the form.
The app I'm building is a simple invoicing app: the user fills out the form fields and sends the invoice. An invoice can have multiple line items that the user enters one at a time and then are appended to a dictionary that should display inside the form.
This is the relevant variables from the top of the struct and the beginning of the view, where I hope I'm declaring the variables for line items correctly to modify them based on user input.
*Edited following #asperi's advice below.
#State private var lineItems = [LineItem]()
#State private var lineItem = LineItem()
struct LineItem: Codable, Hashable {
var productSku: String = ""
var productName: String = ""
var quantity: Double = 0
var unitPrice: Double = 0
}
func addLineItem(lineItem: LineItem){
lineItems.append(lineItem)
}
...
var body: some View {
NavigationView {
Form {
Section(header: Text("Customer Information")) { <-- error appears here
TextField("Customer Name", text: $customerName)
TextField("Customer Email", text: $customerEmail)
}
Here's the relevant portion of the form, where I'm trying to list all current line items and then allow the user to insert additional line items. I don't get any error until I add the List code, so I'm pretty sure I'm doing something wrong there.
Section(header: Text("Items")) {
List(lineItems, id: \.self) { item in
LineItemRow(lineItem: item)
Text(item.productName)
}
TextField("Product SKU", text: $productSKU)
TextField("Poduct Name", text: $productName)
TextField("Unit Price", text: $unitPrice, formatter: DoubleFormatter())
Picker("Quantity", selection: $quantity) {
ForEach(0 ..< 10) {
Text("\($0)")
}
}
Button(action: {
self.addLineItem(lineItem: LineItem(productSku:$productSKU,
productName:$productName,
quantity:$unitPrice,
unitPrice:$quantity))
print($lineItems)
}, label: {
Text("Add line item")
})
}
I've tested the button functionality in the console and it does seem to be appending to the dictionary correctly, but I need it to display as well.
I'm probably getting something very basic wrong with the List. Any advice?
For reference, here's the whole view:
struct AddInvoiceForm: View {
#State private var invoiceNumber: String = ""
#State private var _description: String = ""
#State private var dueDate: Date = Date()
#State private var sendImmediately: Bool = true
#State private var currency = 0
#State private var paymentType = 0
#State private var customerName: String = ""
#State private var customerEmail: String = ""
#State private var productSKU: String = ""
#State private var productName: String = ""
#State private var quantity: Int = 0
#State private var unitPrice: String = ""
#State private var lineItems = [LineItem]()
#State private var lineItem = LineItem()
struct LineItem: Codable, Hashable {
var productSku: String = ""
var productName: String = ""
var quantity: Double = 0
var unitPrice: Double = 0
}
func addLineItem(lineItem: LineItem){
lineItems.append(lineItem)
}
#State private var totalAmount: Double = 0.0
static let currencies = ["USD","GBP"]
var body: some View {
NavigationView {
Form {
Section(header: Text("Customer Information")) {
TextField("Customer Name", text: $customerName)
TextField("Customer Email", text: $customerEmail)
}
Section(header: Text("Invoice Information")) {
TextField("Invoice Number", text:$invoiceNumber)
TextField("Description", text:$_description)
DatePicker(selection: $dueDate, in: Date()..., displayedComponents: .date) {
Text("Due date")
}
Picker("Currency", selection: $currency) {
ForEach(0 ..< Self.currencies.count) {
Text(Self.currencies[$0])
}
}
}
Section(header: Text("Items")) {
List(lineItems, id: \.self) { item in
LineItemRow(lineItem: item)
Text(item.productName)
}
TextField("Product SKU", text: $productSKU)
TextField("Poduct Name", text: $productName)
TextField("Unit Price", text: $unitPrice, formatter: DoubleFormatter())
Picker("Quantity", selection: $quantity) {
ForEach(0 ..< 10) {
Text("\($0)")
}
}
Button(action: {
self.addLineItem(lineItem: LineItem(productSku:$productSKU,
productName:$productName,
quantity:$unitPrice,
unitPrice:$quantity))
print($lineItems)
}, label: {
Text("Add line item")
})
}
Section(header: Text("Totals")) {
Text("\(totalAmount)")
}
Section {
Button(action: {
print(Text("Send"))
}, label: {
Text("Send invoice")
})
}
}.navigationBarTitle("Invoice Details")
}
}
}
List(lineItems, id: \.productSku) { item in <-- I get the error when I add this
Your item is dictionary, but dictionary does not have .productSku key path, so the error.
I assume most simple & correct would be to make Item as struct
struct LineItem {
var productSku: String
...
}
...
#State private var lineItems = [LineItem]()
#State private var lineItem = LineItem()
...
List(lineItems, id: \.productSku) { item in

Delegate #State value to a child component

A Swift new guy here. Trying to understand SwiftUI.
I'm trying to create a "Field" component that wraps a Text and TextField. The idea is to have less code and have a control that can display the field title and its corresponding value.
I can't figure out how to assign the value of the model on my control.
This is my model:
import Foundation
class EmployeeModel {
var FullName: String = "John Doe"
var JobStartDate: String = ""
var BornDate: String = ""
var DepartmentId: Int = 0
var DepartmentName: String = ""
var isBossDepartment: Bool = false
var JobPositionId: Int = 0
var JobPositionName: String = ""
var PersonalDocNumber: String = ""
var Password: String = ""
init() {
}
}
In some part of the view...
struct EmployeeView : View {
#State private var Employee = EmployeeModel()
var body : some View {
Field("Full Name", $Employee.FullName)
}
}
This is my custom component that want to implement.
struct Field : View {
private var caption: String = ""
#State private var controlValue: String = ""
init(caption: String, value: String) {
self.caption = caption
controlValue = value
}
var body : some View {
VStack {
Text(self.caption)
TextField($controlValue)
.textFieldStyle(.roundedBorder)
}
}
}
Currently I got a message of
'Binding' is not convertible to 'String' on my Field implementation of the EmployeeView
Before going into the details of your problem, please be advised that by convention, types (classes, structs, enums, etc) should begin with a capital letter, while objects and values should start with a lower case (or underscore). By not following that convention, you make the code hard to read for others, as everyone is expecting it.
Now, on your code, there are several improvements:
controlValue must be declared as #Binding.
#Binding properties should not have an initial value, as they are supposed to be passed by the caller.
If you declare your properties non-private, you wouldn't need an initializer. Nothing wrong with that, but if you do use an initializer, there are multiple changes you need to perform (see the code below).
Your TextField is using a deprecated and discontinued initializer in beta 5.
struct EmployeeModel {
var fullName: String = "John Doe"
var jobStartDate: String = ""
var bornDate: String = ""
var departmentId: Int = 0
var departmentName: String = ""
var isBossDepartment: Bool = false
var jobPositionId: Int = 0
var jobPositionName: String = ""
var personalDocNumber: String = ""
var password: String = ""
init() {
}
}
struct EmployeeView : View {
#State private var employee = EmployeeModel()
var body : some View {
Field(caption: "Full Name", value: $employee.fullName)
}
}
struct Field : View {
private var caption: String = ""
#Binding private var controlValue: String
init(caption: String, value: Binding<String>) {
self.caption = caption
self._controlValue = value
}
var body : some View {
VStack {
Text(self.caption)
TextField("", text: $controlValue)
.textFieldStyle(.roundedBorder)
}
}
}