2

Hi all I'm new to SwiftUI and am trying to find a neat way to add or remove TextFields when either the plus or minus of a steeper is pressed.

I currently have

@State private var answers: [String] = ["", ""]
Stepper(onIncrement: {
    if(numberOfAnswers < 10){
        answers.append("")
        numberOfAnswers += 1
    }
    },
    onDecrement: {
        if(numberOfAnswers > 2){
        answers.removeLast()
        numberOfAnswers -= 1
        }
    }) {
ForEach(answers.indices, id: \.self) { index in
    TextField("Answer", text: $answers[index])
}

However this results in an Index out of bounds exception when onDecrement is called. I have tried wrapping the String to conform to Identifiable (with the @State inside the struct declaration), using ForEach(answers) however this produces a warning stating that the variable will not be updated.

I have tried solutions posted here but to no avail.

I do not need to persistently store the result of this, as it will be passed to a separate function making an API call on button press.

Any help for this would be much appreciated.

obevan
  • 104
  • 7

1 Answers1

1

Here is fixed replicated code. Tested with Xcode 11.4 / iOS 13.4

struct DemoView: View {
    @State var numberOfAnswers = 2
    @State private var answers: [String] = ["", ""]

    var body: some View {
        Stepper(onIncrement: {
            if(self.numberOfAnswers < 10){
                self.answers.append("")
                self.numberOfAnswers += 1
            }
        },
                onDecrement: {
                    if(self.numberOfAnswers > 2){
                        self.answers.removeLast()
                        self.numberOfAnswers -= 1
                    }
        }) {
            ForEach(Array(answers.enumerated()), id: \.0) { i, _ in
                TextField("Answer", text: self.$answers[i])
            }
        }
    }
}

Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Thanks, exactly what I was looking for however on reflection could you please clarify the use of `id: \.1`, how does Swift use this as a keyPath for id? What does the `1` refer to? I understand that the `enumerated()` returns pairs of values and indices. Furthermore, I noticed you answered [this](https://stackoverflow.com/questions/58960251/strange-behavior-of-stepper-in-swiftui), but I am still looking for a solution to the behaviour. – obevan Jul 02 '20 at 02:14
  • 1
    `.enumerated` return array of tuples `[(index, element)]`, and tuple members can be accessed as `.0` and `.1`, so `\.1` is equivalent to previously used `\.self` – Asperi Jul 02 '20 at 04:15
  • I'm having an issue with this solution only noticed through testing - sorry. When entering text into the dynamically added boxes, I can only type one character at a time, the first is often duplicated into the other boxes. Also, there are occasional index out of range exceptions. Thanks in advance for your help – obevan Jul 18 '20 at 23:56
  • Updated. Actually fix is `id: \.0` in this simple demo. In real project I would recommend to have some model for each with own unique identifier. – Asperi Jul 19 '20 at 06:02
  • Thanks for your response, however this solution crashes on removal of `TextField`, i.e. when stepper minus pressed. Do you mean a model wrapping a string with an identifier? – obevan Jul 19 '20 at 11:43
  • yes, like `struct Answer { let id: UUID, var text: String }`, because `ForEach` requires unique identifiable items for view constructions. – Asperi Jul 19 '20 at 11:48
  • Thanks for your help again and apologies for bothering you with this however I've tried creating a custom `Identifiable` string, however I either run into the same issue when removing from the list using `ForEach(Array(answers.enumerated()), id: \.1.id)`, or, when looping through the array on its own I struggle to access a `Binding` as required by `TextField`. Thanks again for your help. – obevan Jul 19 '20 at 21:41