This is what I need to create:
I need this element's value to be able to change with the user's swipes.
I thought it was UISlider
but it’s impossible to configure it the way I need:
lazy var timeSlider : UISlider = {
let slider = UISlider()
slider.minimumValue = 1
slider.maximumValue = 10
slider.value = 1
slider.layer.cornerRadius = 10
slider.tintColor = UIColor(hexColor: "#F9F5EF")
slider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged)
addSectionMarkers(for: slider)
return slider
}()
I think it would be easier to make your own using a UIStackView
and UIPanGestureRecognizer
instead of trying to inherit a UISlider
.
Here's what I came up with:
CustomSliderView.swift
:
import UIKit
final class CustomSliderView: UIView {
// MARK: - Properties
var progress: Int = 0 {
didSet {
progress = max(0, min(progress, sectionCount))
updateProgress()
}
}
private let sectionCount = 10
private var sectionHeight: CGFloat = 0
private var startProgress: Int = 0
private var startTouchY: CGFloat = 0
// MARK: - Views
private let stackView = UIStackView()
private let filledView = UIView()
// MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
setupView()
setupStackView()
setupFilledView()
addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))))
}
// MARK: - Layout
override func layoutSubviews() {
super.layoutSubviews()
sectionHeight = bounds.height / CGFloat(sectionCount)
updateProgress()
}
private func setupView() {
backgroundColor = .clear
layer.cornerRadius = 10
layer.masksToBounds = true
}
private func setupStackView() {
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 1
stackView.layer.cornerRadius = 10
stackView.clipsToBounds = true
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
for _ in 0..<sectionCount {
let view = UIView()
view.backgroundColor = UIColor(white: 0.95, alpha: 1)
stackView.addArrangedSubview(view)
}
}
private func setupFilledView() {
filledView.backgroundColor = UIColor.brown.withAlphaComponent(0.6)
addSubview(filledView)
}
// MARK: - Actions
private func updateProgress() {
let sectionHeight = bounds.height / CGFloat(sectionCount)
let height = CGFloat(progress) * sectionHeight
filledView.frame = CGRect(
x: 0,
y: bounds.height - height,
width: bounds.width,
height: height
)
}
@objc
private func handlePan(_ gesture: UIPanGestureRecognizer) {
let location = gesture.location(in: self)
switch gesture.state {
case .began:
startTouchY = location.y
startProgress = progress
case .changed:
let delta = startTouchY - location.y
let newProgress = startProgress + Int(delta / sectionHeight)
progress = newProgress
default:
break
}
}
}
Usage:
view.addSubview(customSlider)
customSlider.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
customSlider.centerXAnchor.constraint(equalTo: view.centerXAnchor),
customSlider.centerYAnchor.constraint(equalTo: view.centerYAnchor),
customSlider.widthAnchor.constraint(equalToConstant: 60),
customSlider.heightAnchor.constraint(equalToConstant: 200)
])