How to separate input values for each Textfield? - swiftui

I stored the user's input into a dictionary, but the variables name and amount seems to not be a separate value for each Textfield rows. I tried adding self. to name and amount, but that seemed to not do anything. How can I implement this?
#Binding var numPeople: Int
#State var dict: [String : Float] = [:]
#State var name: String = ""
#State var amount: Float = 0.00
.
.
.
ForEach(1...numPeople, id:\.self) { stack in
HStack {
TextField("Name", text: $name)
.padding()
Text("Amount in $:")
TextField("", value: $amount, formatter: NumberFormatter())
.keyboardType(.numberPad)
.onReceive(Just(amount)) { _ in
dict[name] = amount
}
.padding()
}
}
Thank you!

In your code you are using the same variables name and amount for all rows that you iterate with ForEach. If you want to have each row with their own fields managed separately, you need to separate the views.
Here below, a very schematic example of how it works:
In the parent view, the ForEach will call a subview:
#Binding var numPeople: Int
// Make #State vars private
#State private var dict: [String : Float] = [:]
// Note that you don't use the variables name and amount here
.
.
.
ForEach(1...numPeople, id:\.self) { stack in
// Pass the dictionary, it will be updated by the subview
SubView(dict: $dict)
}
Create a subview that will separately manage each name/ amount:
struct SubView: View {
#Binding var dict: [String : Float]
#State private var name: String = ""
#State private var amount: Float = 0.00
var body: some View {
HStack {
TextField("Name", text: $name)
.padding()
Text("Amount in $:")
TextField("", value: $amount, formatter: NumberFormatter())
.keyboardType(.numberPad)
// I don't know why you need this, if the amount is
// updated in this view. Maybe you can just use
// dict[name] = amount, dropping the .onReceive()...
// ... but it depends on your code
.onReceive(Just(amount)) { _ in
dict[name] = amount
}
.padding()
}
}
private func whatToDoWithNameAndAmount() {
// Do whatever else you need with these variables
}
}

Related

in SwifUI how to define variables which depends of an input Entity in the init of a view and then populate different pickers

I have a CoreData entity called PokSession which contains multiple attributes (date, currency, period, nbheure).
I want to design a view which display those informations (and allow me to modify them) for a given entity.
So basically from a previous view, I call an other view like this:
#FetchRequest(entity: PokSession.entity(), sortDescriptors: [
NSSortDescriptor(keyPath: \PokSession.date, ascending: false)
]) var poksessions: FetchedResults<PokSession>
ForEach(poksessions, id: \.date) { session in
DetailSessionPokUIView (session: session)
}
.onDelete(perform: deleteSessions)
which leads to the following view:
struct DetailSessionPokUIView: View {
#Environment(\.managedObjectContext) var moc
#ObservedObject var session: PokSession
#State private var date: Date
#State private var currency: String
#State private var periode: String
#State private var nbheure: Double
let liste_currency = ["CAD", "EUR", "USD", "GBP"]
let liste_periode = ["matinée", "après-midi", "soirée"]
init(session: PokSession) {
date = session.date!
currency = session.currency!
periode = session.periode!
nbheure = session.nbheure
}
var body: some View {
NavigationView {
Form {
Section {
DatePicker(selection: $date , displayedComponents: .date){
Text("Date")
}
HStack{
Picker("Devise", selection: $currency) {
ForEach(liste_currency, id: \.self) { currency in
Text(currency)
}
}
}
HStack{
Picker("Période de jeu", selection: $periode) {
ForEach(liste_periode, id: \.self) { periode in
Text(periode)
}
}
}
HStack{
Text("Temps de jeu (h)")
.multilineTextAlignment(.leading)
Slider(value: $nbheure, in: 0...24, step: 1)
Text("\(nbheure, specifier: "%.0f")")
Image(systemName: "clock")
}
}
} // Form
} // NavigationView
}
}
But I am having error message inside my init(), saying " Variable 'self.session' used before being initialized".
I dont really understand why as "session" is an input in my init().
How can I use attributes of the selected PokSection entity to populate my DatePicker and my other Pickers default value
It shouldn't be difficult I guess but I am struggling...
Basically, I just want to have my Pickers set with the value coming from the selected PokSession.
And I want to see it in a Picker because I want to be able to modify it.
Thanks for your help

When optional starts as nil, changing value doesn't work

I'm using MVVM with Swift UI. I have the following Struct, ViewModel and View
struct Thing: Identifiable, Codable, Hashable {
var id: String = UUID().uuidString
var price: Double?
}
class MyViewModel: ObservableObject {
//****
#Published var things : [Thing] = [Thing(id: "abc1234")]{
didSet{
print(things)
}
}
}
struct MyView: View {
#StateObject var myViewModel: MyViewModel = MyViewModel()
private let numberFormatter: NumberFormatter
init() {
numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .currency
numberFormatter.maximumFractionDigits = 2
}
var body: some View {
VStack{
List{
ForEach(Array($myViewModel.things.enumerated()), id: \.offset) { index, $thing in
HStack{
//AnotherView(thing: $thing)
TextField("$0.00", value: $myViewModel.things[index].price, formatter: numberFormatter)
.keyboardType(.numberPad)
}//HStack
}//ForEach
}//List
}//VStack
}//View
}
With the above code, anytime the textfield is changed the didSet print statement will show that price = nil
However if I change the line under the comment with the ***** to the following, initializing price to 0 the changes in the textfield are correctly written back to the [Thing] array and it prints that its Optional(x.xx)
#Published var things : [Thing] = [Thing(id: "abc1234", price: 0)]{
What I also just figured out is that if you use the above line with price initialized to 0, if you backspace the default $0.00 in the TextField, it sets the value back to nil, and then it never changes again.
Price should not be optional just default it to 0. There is also a mistake in the ForEach View (it is not a for loop it needs to be given an identifiable array), fix as follows:
ForEach($store.things){ $thing in
HStack{
//AnotherView(thing: $thing)
TextField("$0.00", value: $thing.price, formatter: NumberFormatter.myCurrencyFormatter)
.keyboardType(.numberPad)
}//HStack
Note the formatter needs to be a singleton, global or static because we shouldn't init objects in View init or body.

SwiftUI - Subtotal TextField entries across multiple views

I have multiple views created by a ForEACH. Each View has a textfield where a user can enter a number. I would like to subtotal each entry in each view. In other words subtotal the binding in each view.
Is my approach wrong?
ForEach(someArray.allCases, id: \.id) { item in
CustomeRowView(name: item.rawValue)
}
struct CustomeRowView: View {
var name: String
#State private var amount: String = ""
var body: some View {
VStack {
HStack {
Label(name, systemImage: image)
VStack {
TextField("Amount", text: $amount)
.frame(width: UIScreen.main.bounds.width / 7)
}
}
}
}
}
Any help would be greatly appreciated.
there are many ways to achieve what you ask. I present here a very
simple approach, using an ObservableObject to keep the info in one place.
It has a function to add to the info dictionary fruits.
A #StateObject is created in ContentView to keep one single source of truth.
It is passed to the CustomeRowView view using #ObservedObject, and used to tally
the input of the TextField when the return key is pressed (.onSubmit).
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class FruitCake: ObservableObject {
#Published var fruits: [String : Int] = ["apples":0,"oranges":0,"bananas":0]
// adjust for you purpose
func add(to name: String, amount: Int) {
if let k = fruits.keys.first(where: {$0 == name}),
let sum = fruits[k] {
fruits[k] = sum + amount
}
}
}
struct ContentView: View {
#StateObject var fruitCake = FruitCake()
var body: some View {
VStack {
ForEach(Array(fruitCake.fruits.keys), id: \.self) { item in
CustomeRowView(name: item, fruitCake: fruitCake)
}
}
}
}
struct CustomeRowView: View {
let name: String
#ObservedObject var fruitCake: FruitCake
#State private var amount = 0
var body: some View {
HStack {
Label(name, systemImage: "info")
TextField("Amount", value: $amount, format: .number)
.frame(width: UIScreen.main.bounds.width / 7)
.border(.red)
.onSubmit {
fruitCake.add(to: name, amount: amount)
}
// subtotal
Text("\(fruitCake.fruits[name] ?? 0)")
}
}
}

How to select an item by a button or tapGesture in my SwiftUI List and transfer it to another view

In my app I´m needing two players (only 2 names/strings), selected from an array built in a List/ForEach SwiftUI-code, which are used in another view.
What is the way to bring the name into a string for my Text(item)?
Can I select two items out of the list?
Thx for any help.
Franz
My code (modified, found by Ale Patron,Tutorial using UserDefaults with encoding and decoding the array/list ):
#State private var allTeams: [PlayerItem] = []
#State private var newPlayer = ""
#State private var selectedPlayer = ""
#State private var selection: String?
struct PlayerItem: Identifiable {
var id = UUID()
let player: String
}
var body: some View {
VStack{
HStack {
TextField("Add Players/Teams...", text: $newPlayer)
.textFieldStyle(RoundedBorderTextFieldStyle())
Button(action: {
self.allTeams.append(PlayerItem(player: self.newPlayer))
self.newPlayer = ""
}) {
Image(systemName: "plus")
}
.padding(.leading, 5)
}.padding()
List{
ForEach(allTeams) { playerItem in
Text(playerItem.player)
}
.onTapGesture {
print("How can I select my first und my second player")
}
}
Text("Selected Player: \(selectedPlayer)")
}
}
}
You should use indices for what you are trying to do.
Try this:
ForEach(allTeams.indices) { i in
Text(allTeams[i].player)
.onTapGesture {
print("How can I select my first und my second player")
print("The selected player is \(allTeams[i].player). The second player is \(allTeams[i + 1].player)"
}
}
Make sure to also check if the selected player is the last one in the array and to catch this. Otherwise, you may encounter an out-of-bounds error.

How to set the style of the textField part of a DatePicker

I used a DatePicker inside a Form, and It looks like the following image ,
Now,I hope it display "2020-4-19 " instead of "4/19/20 ".
Someone knows how to do it?
I could not find a way with the default DatePicker, so I've taken the code from
Change selected date format from DatePicker SwiftUI
made some changes to make it work. This CustomDatePicker should do you what you asked for even within a Form.
struct CustomDatePicker: View {
#State var text: String = "Date"
#Binding var date: Date
#State var formatString: String = "yyyy-MM-dd"
#State private var disble: Bool = false
#State private var showPicker: Bool = false
#State private var selectedDateText: String = "Date"
let formatter = DateFormatter()
private func setDateString() {
formatter.dateFormat = formatString
self.selectedDateText = formatter.string(from: self.date)
}
var body: some View {
VStack {
HStack {
Text(text).frame(alignment: .leading)
Spacer()
Text(self.selectedDateText)
.onAppear() {
self.setDateString()
}
.foregroundColor(.blue)
.onTapGesture {
self.showPicker.toggle()
}.multilineTextAlignment(.trailing)
}
if showPicker {
DatePicker("", selection: Binding<Date>(
get: { self.date},
set : {
self.date = $0
self.setDateString()
}), displayedComponents: .date)
.datePickerStyle(WheelDatePickerStyle())
.labelsHidden()
}
}
}
}
struct ContentView: View {
#State var date = Date()
var body: some View {
Form {
Section {
CustomDatePicker(text: "my date", date: $date, formatString: "yyyy-MM-dd")
Text("test")
}
}
}