Dynamic TextFields based on user input using SwiftUI - swift

I am trying to create a dynamic set of TextFields which are added after the user presses the add button. Each press will add another set of those fields. I am new to this so please bear with me. I am getting a fatal error: index out of range. Here is a simple example of what I am trying to achieve.
struct ContentView: View {
#State var name: [String] = []
#State var counter = 0
var body: some View {
Form {
Section {
ForEach(0..<counter, id: \.self) { index in
TextField("Name", text: self.$name[index])
}
Button(action:{
self.counter += 1
}) {
Text("Add more")
}
}
}
}
}

You're increasing the counter without adding new items. If you add a new item to your array it will work without errors:
Button(action:{
self.name.append("")
self.counter += 1
}) {
Text("Add more")
}
But preferably don't use the counter variable at all. If you add a new item to the names array it's count will automatically increase. You can use it in the ForEach loop like this:
ForEach(0..<names.count, id: \.self) { index in
TextField("Name", text: self.$names[index])
}
Button(action:{
self.names.append("")
}) {
Text("Add more")
}
Note: For arrays it's better to use plural names: names instead of name. It indicates it's a collection of items.

SWIFTUI:
Here's the code to show multiple dynamic Text() element:
#State var textsArray = ["a","b","c","d"]
HStack{ ForEach(textsArray, id: \.self)
{ text in
Text("\(text)")
}
}
You can add more texts into "textsArray" or you can change the values in "textsArray" and it'll be automatically changing on UI.

Related

Fatal error: Index out of range when cursor in TextField when trying to removing it

I have a simple form where user is able to add and delete TextFields, where he/she should be able to insert choices. I want then to process user choices in the next steps. All seems to be working but when I try to remove a field with cursor in it I get a Swift/ContiguousArrayBuffer.swift:600: Fatal error: Index out of range error. It happens only if cursor is in the field which I try to remove(it removes fields with text if no cursor). Found a similar question but there is no option for cursor. How to Have a Deletable List of TextFields in SwiftUI
Thank you for help.
struct ChoicesView: View {
#State var choices: [String] = ["", ""]
var body: some View {
VStack {
Text("Enter your choices").font(.title)
Spacer()
Form {
ForEach(0..<choices.count, id: \.self) { choice in
TextField("", text: Binding(
get:{choices[choice]},
set:{choices[choice] = $0}
)
).textFieldStyle(.roundedBorder)
}
// Buttons
HStack{
if choices.count < 10 {
Button("Add field"){
choices.append("")
print(choices)
}
}
Spacer()
if choices.count > 2 {
Button("Delete field"){
choices.removeLast()
print(choices)
}
}
}.buttonStyle(BorderedButtonStyle())
}
}
}
}

iOS16 SwiftUI app crashes when trying to create view using loop

I've defined a view in SwiftUI which takes an Int array and is supposed to display the elements of the array in a VStack such that every "full row" contains three elements of the array and then the last "row" contains the remainder of elements. When running the app on iOS16 I get "Fatal error: Can't remove first element from an empty collection" for the call let die = dice.removeFirst() (also when passing in a non-empty array of course). I've tried following the debugger but I don't understand the way it jumps around through the loops.
On iOS15 this code worked fine. In the actual program I don't display the array content as Text but I have images associated with each Int between 1 and 6 which I display. I replaced this with Text for simplicity's sake.
Thanks for any help!
struct DiceOnTableView: View {
let diceArray: [Int]
var body: some View {
let fullRows: Int = diceArray.count / 3
let diceInLastRow: Int = diceArray.count % 3
var dice: [Int] = diceArray
VStack {
ForEach(0..<fullRows, id: \.self) { row in
HStack {
ForEach(0..<3) { column in
let die = dice.removeFirst()
Text("\(die)")
}
}
}
HStack {
ForEach(0..<diceInLastRow, id: \.self) { column in
let die = dice.removeFirst()
Text("\(die)")
}
}
}
}
}
This does kind of work on iOS 15 (but strangely - the order of the dice is unexpected), and crashes on iOS 16. In general, you should not be using vars in SwiftUI view building code.
Your code can be modified to compute the index into the original diceArray from the row, fullRows, and column values:
struct DiceOnTableView: View {
let diceArray: [Int]
var body: some View {
let fullRows: Int = diceArray.count / 3
let diceInLastRow: Int = diceArray.count % 3
VStack {
ForEach(0..<fullRows, id: \.self) { row in
HStack {
ForEach(0..<3) { column in
Text("\(diceArray[row * 3 + column])")
}
}
}
HStack {
ForEach(0..<diceInLastRow, id: \.self) { column in
Text("\(diceArray[fullRows * 3 + column])")
}
}
}
}
}
The ForEach View will crash if you use it like a for loop with dynamic number of items. You need to supply it an array of item structs with ids, e.g. one that is Identifiable, e.g.
struct DiceItem: Identifable {
let id = UUID()
var number: Int
}
#State var diceItems: [DiceItem] = []
ForEach(diceItems) { diceItem in
And perhaps use a Grid instead of stacks.

SwiftUI Picker Help -- Populating picker based on the selection of another picker

I am trying to populate a picker based on the selection of another picker. I am new to Swift and have been beating my head on this for way too long. I am sure its not as difficult as I am making it but I would appreciate any assistance.
I think my biggest issue is passing the selection of the first picker to the array name of the second. I have used switch case, tried to pass the selection raw value...etc. Below is a sample of what I would like it to look like without the binding of the pickers. Thanks
import SwiftUI
struct veggie: View {
let veggies = ["Beans", "Corn", "Potatoes"]
let beanList = ["Pole", "String", "Black"]
let cornList = ["Peaches & Cream", "Sweet"]
let potatoList = ["Yukon Gold", "Idaho"]
#State private var selectedVeggie = "Bean"
#State private var selectedBean = "Pole"
#State private var selectedType = ""
var body: some View {
NavigationView{
VStack{
Form{
Picker("Please choose a veggie", selection: $selectedVeggie)
{
ForEach(veggies, id: \.self) {
Text($0)
}
}
Text("You selected \(selectedVeggie)")
Picker("Type", selection: $selectedBean)
{
ForEach(beanList, id: \.self) {
Text($0)
}
}
}
} .navigationTitle("Veggie Picker")
}
}
}
I'm imagining that you want this to be somewhat dynamic and not hardcoded with if statements. In order to accomplish that, I setup an enum with the veggie types and then a dictionary that maps the veggie types to their subtypes. Those look like:
enum VeggieType : String, CaseIterable {
case beans, corn, potatoes
}
let subTypes : [VeggieType: [String]] = [.beans: ["Pole", "String", "Black"],
.corn: ["Peaches & Cream", "Sweet"],
.potatoes: ["Yukon Gold", "Idaho"]]
Then, in the view, I did this:
struct ContentView: View {
#State private var selectedVeggie : VeggieType = .beans
#State private var selectedSubtype : String?
var subtypeList : [String] {
subTypes[selectedVeggie] ?? []
}
var body: some View {
NavigationView{
VStack{
Form{
Picker("Please choose a veggie", selection: $selectedVeggie)
{
ForEach(VeggieType.allCases, id: \.self) {
Text($0.rawValue.capitalized)
}
}
Text("You selected \(selectedVeggie.rawValue.capitalized)")
Picker("Type", selection: $selectedSubtype)
{
ForEach(subtypeList, id: \.self) {
Text($0).tag($0 as String?)
}
}
}
} .navigationTitle("Veggie Picker")
}
}
}
The selectedVeggie is now typed with VeggieType. The selectedSubtype is an optional String that doesn't have an initial value (although you could set one if you want).
The first picker goes through all of the cases of VeggieType. The second one dynamically changes based on the computed property subtypeList which grabs the item from the subTypes dictionary I had made earlier.
The tag item is important -- I had to make the tag as String? because SwiftUI wants the selection parameter and the tag() to match exactly.
Note: you say you're new to Swift, so I'll mention that you had named your view `veggie` -- in Swift, normally types have capital names. I named mine `ContentView`, but you could rename it `VeggieView` or something like that.

How to build NumberField in SwiftUI?

I'm working on MacOS and I try to make NumberField — something like TextField, but for numbers. In rather big tree of views at the top I had:
...
VStack {
ForEach(instances.indices, id:\.self) {index in
TextField("",
text: Binding(
get: {"\(String(format: "%.1f", instances[index].values[valueIndex]))"},
set: {setValueForInstance(index, valueIndex, $0)})
)
}
}
...
And it worked well, but not nice:
✔︎ when I changed value, all View structure was redrawn – good
✔︎ values was updated if they were changed by another part of Views structure – good
✖︎ it was updated after each keypresses, which was annoying, when I tried to input 1.2, just after pressing 1 view was updated to 1.0. Possible to input every number but inconvenient – bad
So, I tried to build NumberField.
var format = "%.1f"
struct NumberField : View {
#Binding var number: Double {
didSet {
stringNumber = String(format: format, number)
}
}
#State var stringNumber: String
var body: some View {
TextField("" , text: $stringNumber, onCommit: {
print ("Commiting")
if let v = Double(stringNumber) {
number = v
} else {
stringNumber = String(format:format, number)
}
print ("\(stringNumber) , \(number)" )
})
}
init (number: Binding<Double>) {
self._number = number
self._stringNumber = State(wrappedValue: String(format:format, number.wrappedValue))
}
}
And It's called from the same place as before:
...
VStack {
ForEach(instances.indices, id:\.self) {index in
NumberField($instances[index].values[valueIndex])
}
}
...
But in this case it never updates NumberField View if values was changed by another part of View. Whats's wrong? Where is a trick?

Save value on stepper SwiftUI

I have a stepper:
Stepper(value: $timeBetweenMeals, in: 1...10, step: 1) {
Text("Time Between Meals: \(timeBetweenMeals, specifier: "%.0f")h")
self.settingsUserDefaults = $timeBetweenMeals
}
That gives me error because I'm trying to set: self.settingsUserDefaults = $timeBetweenMeals inside that view. How can I run that logic? I don't have a button, I just want to save that #Published value as the user changes it
Use a custom Binding, and inside your set you can store your value:
Stepper(value: Binding<Int>(get: {
self.timeBetweenMeals
}, set: {
print($0) // here you can access your value with $0, store it here
self.timeBetweenMeals = $0
}), in: 1...10, step: 1) {
Text("Time Between Meals: \(timeBetweenMeals)h")
}
You need to perform other actions when Stepper value changes in onIncrement and onDecrement method not inside of the label parameter which is meant to return the View of the Stepper. Here is an example (Note: this is just an example, you can achieve this in many easier ways too):
struct ContentView: View {
#State var timeBetweenMeals: Int
var body: some View {
Stepper(value: Binding<Int>(get: {
self.timeBetweenMeals
}, set: {
print($0)
self.timeBetweenMeals = $0
}), in: 1...10, step: 1) {
Text("Time Between Meals: \(timeBetweenMeals)h")
}
}
}
Note: I've updated to a much better approach. Credits to davidev. Also, your specifier seems to be incorrect.