Strange Behavior of Stepper in SwiftUI
Asked Answered
A

4

7

I want to use the Stepper view in manual (not binding) mode using onIncrement and onDecrement. There's a strange behavior when I try to implement lower and upper bounds, eg. having an age value not going bellow 1 or above 10.

If you try the bellow code, you can press "-" two times after it already has the value "1". It doesn't go bellow 1 as expected, but after the two additional presses the "-" button suddenly gets disabled. If you then press "+" nothing happens. Only after 2 additional presses to "+" the counter reacts as expected and goes to 2.

Is this a bug?

struct StepperTest: View {
    @State private var age = 5

    var body: some View {
        VStack {
            Stepper("Enter your age", onIncrement: {
                if self.age < 10 {
                    self.age += 1
                    print("Adding to age")
                }
            }, onDecrement: {
                guard self.age > 1 else { return }
                self.age -= 1
                print("Subtracting from age")
            })

            Text("Your age is \(age)")
        }
    }
}
Arroba answered 20/11, 2019 at 17:34 Comment(6)
Why don't you want to use binding for this?Corneliacornelian
@MojtabaHosseini: Because I want to have full controlArroba
full control over what? you have the access to the editingChange already and you can detect witch key is touched. What else do you need?Corneliacornelian
The main reason is, I want to use a Redux like architecture where the state is only changed by actions in a separate module. Using bindings, the state is modified within various views.Arroba
That is called Uni Directional Data Flow architecture and that has no conflict with this.Corneliacornelian
@MojtabaHosseini: I don't fully get it: I would bind the stepper value to a state variable? But then, the state gets changed by the view directly and not through one of my action methods, right?Arroba
E
2

Checking and resetting the value after incrementing seems to work:

Stepper("Enter your age", onIncrement: {
                    self.age += 1
                    if self.age > 10 {self.age = 10}
                }
            }, onDecrement: {
                self.age -= 1
                if self.age < 1 {self.age = 1} 
            })
Encephalomyelitis answered 6/1, 2020 at 2:15 Comment(0)
N
1

I think here is explanation:

/// onIncrement will be initialized to nil if attempting to increment
/// value will have no effect. Likewise, onDecrement will be initialized
/// to nil if attempting to decrement value will have no effect.

If you try

Stepper("Enter your age", onIncrement: {
    print("Adding to age")
}, onDecrement: {
    print("Subtracting from age")
})

... you'll see that steppers got into the same state, as you described, so Apple tracks rebuild of Stepper view, if no rebuild was initiated by user action on Stepper (ie. no effect) it disable itself.

Another question is why it's not at once...

Nassi answered 20/11, 2019 at 18:47 Comment(3)
I don't fully understand on how you would use it that way. I'd expect to have those two handlers onIncrement and onDecrement and two properties to enable/disable the plus and minus buttons...Arroba
That's a question to Apple )), but it is a documented behavior.Nassi
I think it's a bug. I get the same kind of bizarro behavior, even if I don't have any if/else logic inside my onIncrement and onDecrement. It just stops and grays out the "+" or "-". Then if you tap the other one in goes in the wrong direction. The comment is grammatically bad and incomprehensible.Stillas
B
1

I was seeing the same thing. Definitely seems like a bug on Apples side to me. As one of the commenters mentioned, you can use the onEditingChanged parameter. This should give you all the functionality that you would need. As the bound variable changes with the Stepper inputs you are able to take an action. It looks like this:

Stepper(value: $age, in: 1...10, step: 1, onEditingChanged: { didChange in
        // take some action or make a calculation
}) 
{
        Text("Your age is \(age)")
}
Bastard answered 17/8, 2020 at 19:31 Comment(0)
G
1

I was seeing similar behavior as described using iOS 14.5 in Xcode 12.5.1. I found this open radar which describes the bug: https://openradar.appspot.com/FB7669491

In my case I was passing functions in my view model directly as the onIncrement and onDecrement parameters when initializing the Stepper:

Stepper("Label", onIncrement: viewModel.increment, onDecrement: viewModel.decrement)

Based on reading the radar, the current issue is caused by passing functions outside the view as the values for these parameters. Changing my code to this fixed the issue for me:

Stepper("Label", onIncrement: { viewModel.increment }, onDecrement: { viewModel.decrement })
Greta answered 8/8, 2021 at 0:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.