Writing our own CustomStepper in Swift

Here you can find the implementation of the finished project

To date, in many applications we can observe steppers, most of them are custom. Despite the fact that Apple already provides an implementation of a ready-made stepper, sometimes it is not suitable for various reasons. This is an example approach to implementing a custom stepper for a coffee shop.

There are different ways to achieve the goal, but today I will show you one that I have not found. In this article, we will use code layout, a stackView with 2 buttons (-,+) and a label.

In order to reuse our stepper, let’s make it a class that inherits from UIView. In fact, this is a full-fledged UI component, which can then be taken into your projects.

The most important thing is the logic that will process the current values ​​of our stepper. To do this, it will use a variable that will be responsible for the current values ​​​​of the stepper and update the text of the label.

final class CustomStepper: UIView {
     private lazy var currentValue = 1

Next, we need to create 2 buttons and a label, of which our stepper will consist. In order to keep track of the state of the stepper, we will use an enum that will manage the states of the buttons. We used tags to avoid doing 2 methods for handling button clicks.

    private enum ButtonState: Int, CaseIterable {
        case decrease = 0
        case increase

    private lazy var decreaseButton: UIButton = {
        let button = UIButton()
        button.tag = ButtonState.decrease.rawValue
        button.setTitleColor(.black, for: .normal)
        button.setTitle("-", for: .normal)
        button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
        return button
    private lazy var currentStepValueLabel: UILabel = {
        let label = UILabel()
        label.textColor = .black
        label.text = "\(currentValue)"
        label.font = .systemFont(ofSize: 15)
        return label
    private lazy var increaseButton: UIButton = {
        let button = UIButton()
        button.tag = ButtonState.increase.rawValue
        button.setTitle("+", for: .normal)
        button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
        button.setTitleColor(.black, for: .normal)
        return button

Having all the necessary elements, we can implement a method that is responsible for the logic of our stepper and bind it to our buttons.

    //MARK: - Actions
    @objc private func buttonAction(_ sender: UIButton) {
        let buttonState = ButtonState(rawValue: sender.tag)
        switch buttonState {
        case .decrease:
            currentValue = currentValue > 1 ? currentValue - 1 : currentValue
        case .increase:
            currentValue += 1
        currentStepValueLabel.text = "\(currentValue)"

We will use the delegation pattern to pass the stepper data to our controller for further project logic and value updates.

protocol CustomStepperOutput: AnyObject {
    func customStepper(_ didChangeValue: Int)

protocol CustomStepperInput: AnyObject {
    func update(_ value:Int)

This is an internal interface through which we can initialize the counter (if necessary)

//MARK: - CustomStepperInput
extension CustomStepper: CustomStepperInput {
    func update(_ value: Int) {
        currentValue = value

Let’s initialize our stepper inside the controller. We will sign the controller under the stepper delegate, so the controller will implement the delegate method where to receive data from the stepper. With this data, the controller can carry out further logic.

import UIKit

final class MainVC: UIViewController {

    private lazy var stepperView = CustomStepper()
    //MARK: - Life Cycle
    override func viewDidLoad() {
    //MARK: - Private
    private func setupViews() {
        view.backgroundColor = .white
        stepperView.delegate = self
    private func setupConstraints() {
        stepperView.snp.makeConstraints { make in

//MARK: - CustomStepperOutput
extension MainVC: CustomStepperOutput {
    func customStepper(_ didChangeValue: Int) {

Ready! Here is the end result:

Similar Posts

Leave a Reply