Last active
June 30, 2022 09:41
-
-
Save mazen-kasser/e133ff3797d9e2393eb604a57941c789 to your computer and use it in GitHub Desktop.
Stepper component based on `UIStepper` control with having a label as the divider image.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
/// Stepper component based on `UIStepper` control. | |
/// | |
/// - Parameters: | |
/// - onClick, closure returns the final `value` | |
/// - isContinuous, default = YES. value change events are sent any time the value changes during interaction. | |
/// - autorepeat, default = YES. press & hold repeatedly alters value. | |
/// - wraps, default = NO. value wraps from min <-> max. | |
/// - value, default = 0. sends UIControlEventValueChanged. clamped to min/max | |
/// - minimumValue, default 0. must be less than maximumValue | |
/// - maximumValue, default 100. must be greater than minimumValue | |
/// - stepValue, default 1. must be greater than 0 | |
class StepperControl: UIStepper { | |
var onClick: ((Double) -> Void)? | |
private lazy var label: UILabel! = { | |
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 30, height: frame.height)) | |
label.textAlignment = .center | |
label.textColor = .black | |
label.adjustsFontSizeToFitWidth = true | |
return label | |
}() | |
private | |
enum Constants { | |
static let decrementImage = UIImage(systemName: "minus")! | |
static let incrementImage = UIImage(systemName: "plus")! | |
} | |
override var value: Double { | |
willSet { | |
label.text = NSNumber(value: value).stringValue | |
configureUI() | |
} | |
} | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
setupUI() | |
configureUI() | |
} | |
required init?(coder: NSCoder) { | |
super.init(coder: coder) | |
setupUI() | |
configureUI() | |
} | |
override func awakeFromNib() { | |
super.awakeFromNib() | |
setupUI() | |
configureUI() | |
} | |
private func setupUI() { | |
// set events | |
addTarget(self, action: #selector(valueChange), for: .valueChanged) | |
addTarget(self, action: #selector(valueChanged), for: .touchUpInside) | |
let normalColor = UIColor.systemGreen | |
let highlightedColor = UIColor.green | |
let disabledColor = UIColor.gray | |
// adjust images size | |
let font = UIFont.systemFont(ofSize: 32) | |
label.font = font | |
let configuration = UIImage.SymbolConfiguration(font: font) | |
let decrementImage = Constants.decrementImage | |
.withConfiguration(configuration) | |
.withRenderingMode(.alwaysOriginal) | |
let incrementImage = Constants.incrementImage | |
.withConfiguration(configuration) | |
.withRenderingMode(.alwaysOriginal) | |
// set style | |
setDecrementImage(decrementImage.withTintColor(normalColor), for: .normal) | |
setDecrementImage(decrementImage.withTintColor(highlightedColor), for: .highlighted) | |
setDecrementImage(decrementImage.withTintColor(disabledColor), for: .disabled) | |
setIncrementImage(incrementImage.withTintColor(normalColor), for: .normal) | |
setIncrementImage(incrementImage.withTintColor(highlightedColor), for: .highlighted) | |
setIncrementImage(incrementImage.withTintColor(disabledColor), for: .disabled) | |
let backgroundBlankImage = UIGraphicsImageRenderer(size: CGSize(width: 64, height: 8)).image(actions: {_ in }) | |
setBackgroundImage(backgroundBlankImage, for: .normal) | |
} | |
private func configureUI() { | |
// set divider image for normal / normal | |
setDividerImage(image(fromView: label), | |
forLeftSegmentState: .normal, | |
rightSegmentState: .normal) | |
// set divider image for normal / highlighted | |
setDividerImage(image(fromView: label), | |
forLeftSegmentState: .normal, | |
rightSegmentState: .highlighted) | |
// set divider image for highlighted / normal | |
setDividerImage(image(fromView: label), | |
forLeftSegmentState: .highlighted, | |
rightSegmentState: .normal) | |
} | |
@objc | |
private func valueChange(sender: UIStepper) { | |
value = sender.value // to update the divider label | |
} | |
@objc private func valueChanged(sender: UIStepper) { | |
onClick?(sender.value) // to return the final updated value after long press or short press | |
} | |
private func image(fromView view: UIView) -> UIImage { | |
UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, 0) | |
defer { UIGraphicsEndImageContext() } | |
view.layer.render(in: UIGraphicsGetCurrentContext()!) | |
return UIGraphicsGetImageFromCurrentImageContext()! | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To consume it is just as simple as setting the properties: