How to append an identifiable struct list? - swift

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.

Related

Making variable for each iteration in foreach loop SwiftUI

I have a nestled foreach loop and I want to modify data for each element in the nestled loop. Like this:
struct trainingView: View {
#ObservedObject var workout: Workout
#State private var reps: Int = 0
var body: some View {
ForEach(workout.exercises, id: \.self) { exercise in
Text(exercise.nameOfExercise)
ForEach(0..<exercise.amountOfSets, id: \.self) { Set in
Text("\(Set+1) Set")
Text(reps)
Stepper("Reps", value: $reps , in: 0...30)
}
}
}
}
I would like the variable rep to be independent from changes in an another elements stepper. If you need better examples of the code just ask.
This does not work as one change affects all elements. I have also tried making an #State object in the nestled loop but this doesn't work either. I have also tried making an array to store the data but this seemed complicated since the size of the array can change from different times. I have tried to search for the information on here but haven't found anyone answering it. I am fairly new to swiftUI and in need of help.
The Stepper in your example takes a Binding to a numeric value. If you create an array-of-arrays, you can create a Binding by accessing each row and element within it via their indices, e.g.:
struct Exercise: Identifiable, Equatable, Hashable {
let id = UUID()
let name: String
var sets: [Set]
}
struct Set: Identifiable, Equatable, Hashable {
let id = UUID()
let name: String
let requiredReps: Int = 10
var reps: Int = 0
}
class Workout: ObservableObject {
#Published var exercises: [Exercise]
init(exercises: [Exercise]) {
self.exercises = exercises
}
}
struct ContentView: View {
#StateObject var workout = Workout(exercises: [
Exercise(name: "Squats", sets: [Set(name: "Set 1"), Set(name: "Set 2"), Set(name: "Set 3")]),
Exercise(name: "Lunges", sets: [Set(name: "Set 1"), Set(name: "Set 2"), Set(name: "Set 3")]),
Exercise(name: "Burpees", sets: [Set(name: "Set 1"), Set(name: "Set 2"), Set(name: "Set 3")])
])
var body: some View {
List {
ForEach(workout.exercises) { exercise in
Section(exercise.name) {
let rowIndex = workout.exercises.firstIndex(of: exercise)!
HStack {
ForEach(exercise.sets) { set in
let objectIndex = exercise.sets.firstIndex(of: set)!
VStack {
Text(set.name + ": ") + Text(set.reps, format: .number) + Text("/") + Text(set.requiredReps, format: .number)
Stepper("Reps", value: $workout.exercises[rowIndex].sets[objectIndex].reps, in: 0...set.requiredReps)
.labelsHidden()
}
}
}
}
}
.padding()
}
}
}

SwiftUI TextField Binding to a simple Model (non-class sturct)

I have a simple struct, that is decodable / codable and hashable.
public struct Field: Codable, Hashable {
let key: String
enum CodingKeys: String, CodingKey {
case key
}
}
In my SwiftUI, I'm creating an array, where I want to add and remove and Bind to the Struct values. But I am getting errors, that Struct is not Binding.
let decodableJSON = """
{
"key": ""
}
"""
let settableInit: Field = try! JSONDecoder().decode(Field.self, from: decodableJSON.data(using: .utf8)!)
struct Test_view: View {
#State
var settableFields: [Field] = [settableInit]
var body: some View {
ForEach(settableFields, id: \.key) { (settableField: Field) in
TextField("Key", text: settableField.key)
}
But I get an error, that settableField is not Binding. I have tried adding the settableInit as an #ObservableObject to the main Swift View, but it still doesn't work.
Is there a way, to have the View bind to Struct properties, and have TextField change these properties? It feels like something so trivial, but for some reason undoable for me.
Thank you for any pointers!
In Xcode13 you can use the new element binding syntax:
public struct Field: Codable, Hashable {
var key: String
enum CodingKeys: String, CodingKey {
case key
}
}
struct Demo: View {
#State var settableFields: [Field] = [Field(key: "1"), Field(key: "2")]
var body: some View {
ForEach($settableFields, id: \.key) { $settableField in
TextField("Key", text: $settableField.key)
}
}
}
In earlier versions of Xcode you could use the indices of the array:
struct Demo: View {
#State var settableFields: [Field] = [Field(key: "1"), Field(key: "2")]
var body: some View {
ForEach(settableFields.indices, id: \.self) { index in
TextField("Key", text: $settableFields[index].key)
}
}
}

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)
}
}

Create a Picker that returns a String

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)
}
}

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