Тут можно найти реализацию готового проекта
На сегодняшний день во многих приложениях мы можем наблюдать stepper, большинство из них кастомные. Несмотря на то, что Apple предоставляет уже реализацию готового степпера, иногда он не подходит по разным причинам. Это пример подхода к реализации кастомного степпера для кофейни.
Есть разные способы в достижение цели, но сегодня я вам покажу тот, которые не не нашел. В этой статье мы будем использовать верстку кодом, stackView с 2 кнопками (-,+) и лейбл.
Для того, чтобы многократно использовать наш степпер, сделаем его классом, который наследуется от UIView. По сути это полноценный UI-компонент, который можно будет потом взять в свои проекты.
Самое важное - логика, которая будет обрабатывать текущее значения нашего степпера. Для этого будет использовать переменную, которая будет отвечать за текущее значения степпера и обновлять текст лейбла.
final class CustomStepper: UIView {
private lazy var currentValue = 1
}
Дальше нам нужно создать 2 кнопки и лейбл, из которых и будет состоять наш степпер. Для того, чтобы отслеживать состояние степпера, мы будем использовать enum, который будет управлять состояниями кнопок. Мы использовали теги, для чтобы не делать 2 метода для обработки нажатия кнопок.
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
}()
Имея все необходимые элементы, мы можем реализовать метод, который отвечает за логику нашего степпера и привязать его к нашим кнопкам.
//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
default:
return
}
currentStepValueLabel.text = "\(currentValue)"
}
Будем использовать паттерн делегирования, чтобы передать данные степпера в наш контроллер для дальнейшей логики проекта и обновления значения.
protocol CustomStepperOutput: AnyObject {
func customStepper(_ didChangeValue: Int)
}
protocol CustomStepperInput: AnyObject {
func update(_ value:Int)
}
Это внутренний интерфейс, через который мы можем проинициализировать счетчик (при необходимости)
//MARK: - CustomStepperInput
extension CustomStepper: CustomStepperInput {
func update(_ value: Int) {
currentValue = value
}
}
Внутри контроллера проинициализируем наш степпер. Контроллер подпишем под делегатом степпера, поэтому контроллер будет реализовывать метод делегата, где получать данные от степпера. С этими данными контроллер может дальше осуществлять логику.
import UIKit
final class MainVC: UIViewController {
private lazy var stepperView = CustomStepper()
//MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
setupConstraints()
}
//MARK: - Private
private func setupViews() {
view.backgroundColor = .white
view.addSubview(stepperView)
stepperView.delegate = self
}
private func setupConstraints() {
stepperView.snp.makeConstraints { make in
make.centerX.centerY.equalToSuperview()
}
}
}
//MARK: - CustomStepperOutput
extension MainVC: CustomStepperOutput {
func customStepper(_ didChangeValue: Int) {
print(didChangeValue)
}
}
Готово! Вот конечный результат:
Комментарии (9)
storoj
24.11.2022 07:29+2CustomStepper: UIView
почему не UIControl? не надо было бы никакого делегата придумывать (который ещё и неправильный – что если я хочу узнавать об изменениях двух и более разных таких контролов, как отличить от которого из них пришло событие?)
зачем currentValue приватное? как мне узнавать текущее состояние, чтобы например программно сделать +3? зачем lazy Int?
зачем вообще всё приватное? как мне кастомизировать внешний вид?
storoj
24.11.2022 07:31+1... зачем теги кнопкам? в обработчике нажатия кнопки можно проверить кто был sender – increaseButton или же decreaseButton
2Grey
24.11.2022 12:53+1Продолжу Ваши рассуждения:
Зачем enum
CaseIterable
, если это нигде не используется?Зачем пихать события в один метод, если на каждую кнопку можно повесить свое событие?
Так можно избавиться от бесполезного enum'а.
Выглядит так, будто человек прочитал одну (не самую лучшую) книгу по Swift и начал писать не разбираясь в смысле написанного.
denisromanenko
Здорово! Только кнопка "дрожит" по ширине, зафиксировать как-нибудь бы размеры.
storoj
UIFont.monospaceDigit...
denisromanenko
Праздный интерес: а с кастомным фонтом как этого добиться?
house2008
а как же поддержка динамических шрифтов ?
zontz Автор
Спасибо за комментарий, все исправлю!