如何制作类似iOS音量滑块的可滑动调节自定义UI组件?
自定义滑动数值组件实现方案
需求概述
需要实现一款支持滑动操作调整数值的UI组件,样式特点为:带有分段刻度标记、圆角轨道、自定义滑块样式(或分段式选择视觉)。
现有问题
尝试使用系统UISlider实现,但无法配置出符合需求的样式,现有代码如下:
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 }()
解决方案
方案一:自定义UISlider外观
通过重写UISlider的轨道和滑块布局方法,搭配自定义刻度标记,实现所需样式:
- 创建自定义Slider类
class CustomSegmentSlider: UISlider { // 自定义轨道高度 private let trackHeight: CGFloat = 8 // 刻度总数(对应1-10共10个刻度) var markerCount: Int = 10 // 调整轨道尺寸和位置 override func trackRect(forBounds bounds: CGRect) -> CGRect { var rect = super.trackRect(forBounds: bounds) rect.size.height = trackHeight rect.origin.y = bounds.midY - trackHeight / 2 return rect } // 自定义滑块尺寸和位置 override func thumbRect(forBounds bounds: CGRect, trackRect rect: CGRect, value: Float) -> CGRect { var thumbRect = super.thumbRect(forBounds: bounds, trackRect: rect, value: value) thumbRect.size = CGSize(width: 20, height: 20) thumbRect.origin.y = rect.midY - thumbRect.height / 2 return thumbRect } // 添加底部刻度标记 func addMarkers() { let trackRect = self.trackRect(forBounds: bounds) let spacing = trackRect.width / CGFloat(markerCount - 1) for i in 0..<markerCount { let markerView = UIView() markerView.backgroundColor = .gray markerView.frame = CGRect( x: trackRect.origin.x + spacing * CGFloat(i), y: trackRect.maxY + 4, width: 2, height: 8 ) addSubview(markerView) } } }
- 初始化并使用自定义Slider
lazy var timeSlider: CustomSegmentSlider = { let slider = CustomSegmentSlider() slider.minimumValue = 1 slider.maximumValue = 10 slider.value = 1 // 轨道圆角设为高度一半,实现圆润效果 slider.layer.cornerRadius = slider.trackHeight / 2 slider.tintColor = UIColor(hexColor: "#F9F5EF") // 替换为自定义滑块图片(若需要) slider.setThumbImage(UIImage(named: "custom_slider_thumb"), for: .normal) slider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged) slider.addMarkers() return slider }() @objc func sliderValueChanged(_ sender: UISlider) { // 处理数值变化逻辑,可根据需要取整 let currentValue = Int(sender.value) print("当前选中数值:\(currentValue)") }
方案二:UICollectionView实现分段滑动选择
如果UISlider的自定义程度无法满足需求,可使用UICollectionView实现分段式滑动选择器:
- 创建刻度单元格
class SliderMarkerCell: UICollectionViewCell { static let reuseID = "SliderMarkerCell" private let markerView = UIView() private let valueLabel = UILabel() override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { // 刻度线 markerView.backgroundColor = .gray markerView.translatesAutoresizingMaskIntoConstraints = false addSubview(markerView) // 数值标签 valueLabel.font = .systemFont(ofSize: 12) valueLabel.textAlignment = .center valueLabel.translatesAutoresizingMaskIntoConstraints = false addSubview(valueLabel) NSLayoutConstraint.activate([ markerView.widthAnchor.constraint(equalToConstant: 2), markerView.heightAnchor.constraint(equalToConstant: 8), markerView.centerXAnchor.constraint(equalTo: centerXAnchor), markerView.topAnchor.constraint(equalTo: topAnchor), valueLabel.topAnchor.constraint(equalTo: markerView.bottomAnchor, constant: 4), valueLabel.leadingAnchor.constraint(equalTo: leadingAnchor), valueLabel.trailingAnchor.constraint(equalTo: trailingAnchor), valueLabel.heightAnchor.constraint(equalToConstant: 16) ]) } func configure(with value: Int, isSelected: Bool) { valueLabel.text = "\(value)" let selectedColor = UIColor(hexColor: "#F9F5EF") markerView.backgroundColor = isSelected ? selectedColor : .gray valueLabel.textColor = isSelected ? selectedColor : .darkGray } }
- 配置CollectionView并实现逻辑
class YourViewController: UIViewController { private var currentSelectedValue = 1 private let totalValues = 10 // 1-10 lazy var segmentSlider: UICollectionView = { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal layout.itemSize = CGSize(width: 40, height: 40) layout.minimumInteritemSpacing = 0 let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) collectionView.showsHorizontalScrollIndicator = false collectionView.delegate = self collectionView.dataSource = self collectionView.register(SliderMarkerCell.self, forCellWithReuseIdentifier: SliderMarkerCell.reuseID) collectionView.backgroundColor = .clear return collectionView }() override func viewDidLoad() { super.viewDidLoad() setupLayout() // 默认选中第一个 scrollToSelectedValue() } private func setupLayout() { segmentSlider.translatesAutoresizingMaskIntoConstraints = false view.addSubview(segmentSlider) NSLayoutConstraint.activate([ segmentSlider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), segmentSlider.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), segmentSlider.heightAnchor.constraint(equalToConstant: 40), segmentSlider.centerYAnchor.constraint(equalTo: view.centerYAnchor) ]) } private func scrollToSelectedValue() { let indexPath = IndexPath(item: currentSelectedValue - 1, section: 0) segmentSlider.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false) } @objc private func sliderValueChanged() { print("当前选中数值:\(currentSelectedValue)") // 执行数值变化后的业务逻辑 } } // MARK: - UICollectionView DataSource & Delegate extension YourViewController: UICollectionViewDataSource, UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return totalValues } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SliderMarkerCell.reuseID, for: indexPath) as! SliderMarkerCell let value = indexPath.item + 1 cell.configure(with: value, isSelected: value == currentSelectedValue) return cell } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { updateSelectedValue(from: scrollView) } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if !decelerate { updateSelectedValue(from: scrollView) } } private func updateSelectedValue(from scrollView: UIScrollView) { let centerPoint = CGPoint(x: scrollView.contentOffset.x + scrollView.bounds.width / 2, y: scrollView.bounds.midY) guard let indexPath = segmentSlider.indexPathForItem(at: centerPoint) else { return } currentSelectedValue = indexPath.item + 1 segmentSlider.reloadData() sliderValueChanged() } }
内容的提问来源于stack exchange,提问作者Gagen
相关产品推荐
相关产品推荐

