G. Marc
G. Marc

Reputation: 6037

Strange Behavior of Stepper in SwiftUI

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

Upvotes: 8

Views: 2312

Answers (4)

Charles A.
Charles A.

Reputation: 11143

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

Upvotes: 1

Francojamesfan
Francojamesfan

Reputation: 105

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

Upvotes: 1

Skipjakk
Skipjakk

Reputation: 51

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

Upvotes: 2

Asperi
Asperi

Reputation: 258621

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...

Upvotes: 1

Related Questions