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)")
        }
    }
}
G. Marc
  • 4,987
  • 4
  • 32
  • 49
  • Why don't you want to use binding for this? – Mojtaba Hosseini Nov 20 '19 at 22:04
  • @MojtabaHosseini: Because I want to have full control – G. Marc Nov 21 '19 at 12:58
  • 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? – Mojtaba Hosseini Nov 21 '19 at 14:08
  • 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. – G. Marc Nov 24 '19 at 08:24
  • That is called *Uni Directional Data Flow* architecture and that has no conflict with this. – Mojtaba Hosseini Nov 24 '19 at 08:45
  • @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? – G. Marc Nov 25 '19 at 07:39

4 Answers4

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} 
            })
Skipjakk
  • 51
  • 1
  • 6
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...

Asperi
  • 228,894
  • 20
  • 464
  • 690
  • 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... – G. Marc Nov 24 '19 at 08:27
  • That's a question to Apple )), but it is a documented behavior. – Asperi Nov 24 '19 at 08:31
  • 4
    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. – Rob N Apr 10 '20 at 01:47
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 })
Charles A.
  • 10,685
  • 1
  • 42
  • 39
0

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