我正在尝试在 iOS 设备上实现自定义 UISlider。
首先,ThumbSlider 位于两端,期望的行为如下:当 ThumbSlider 向左或向右移动时,它们之间的 TopView 应同时展开或收缩。
当 TopView 实现为 UIView 时,上述视图可以正常工作。
同样,使用 Core Graphics 时也能很好地工作。
但是,当使用Core Animation实现TopView时,ThumbSlider和动画之间存在明显的延迟。
这种行为与我的预期完全不同。虽然我知道 UIView 可能针对这种操作进行了优化,但我无法理解为什么 Core Animation 与 Core Graphics 相比会引入延迟。
这是我的最后一个问题:
final class EditSliderBar: UIControl {
enum Constants {
static let sliderWidth: CGFloat = 16
}
// MARK: - UI Components
private let lowerThumbSlider = EditSlider(tintColor: .systemYellow)
private let upperThumbSlider = EditSlider(tintColor: .systemYellow)
private let topView = UIView()
private let topLayer = CALayer()
weak var currentHighlightedThumbSlider: EditSlider?
private var previousLocation: CGPoint = .zero
private var minimumValue: Double = 0
private var maximumValue: Double = 100
...
private(set) var lowerValue: Double = 0.0 {
didSet {
updateSliderFrame(lowerThumbSlider)
}
}
private(set) var upperValue: Double = 100 {
didSet {
updateSliderFrame(upperThumbSlider)
}
}
...
// MARK: - UIControl
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let location = touch.location(in: self)
defer { previousLocation = location }
if lowerSlider.contain(point: location) {
lowerSlider.isHightlighted = true
currentHighlightedSlider = lowerSlider
} else if upperSlider.contain(point: location) {
upperSlider.isHightlighted = true
currentHighlightedSlider = upperSlider
}
return lowerSlider.isHightlighted || upperSlider.isHightlighted
}
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let location = touch.location(in: self)
defer { previousLocation = location }
guard let slider = currentHighlightedThumbSlider else { return false }
let sliderDeltaValue = deltaValue(from: previousLocation, to: location)
if slider === lowerThumbSlider {
self.lowerValue = updatedLowerValue(moved: sliderDeltaValue)
} else if slider === upperThumbSlider {
self.upperValue = updatedUpperValue(moved: sliderDeltaValue)
}
sendActions(for: .valueChanged)
return true
}
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
...
}
// MARK: - LayoutSubviews
override func layoutSubviews() {
super.layoutSubviews()
updateSliderFrames()
}
// MARK: - Draw
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
let maskedRect = CGRect(
x: lowerSlider.center.x,
y: 0,
width: upperThumbSlider.center.x - lowerThumbSlider.center.x,
height: 5
)
context.setFillColor(UIColor.systemYellow.cgColor)
context.fill(maskedRect)
}
}
// MARK: - Private Methotds
private extension EditSliderBar {
func updateSliderFrames() {
updateSliderFrame(lowerThumbSlider)
updateSliderFrame(upperThumbSlider)
}
func updateSliderFrame(_ slider: EditSlider) {
let width = Constants.sliderWidth
let leading = slider === lowerThumbSlider ? leading(of: lowerValue) : leading(of: upperValue)
slider.frame = CGRect(
x: leading,
y: 0,
width: width,
height: bounds.height
)
setNeedsDisplay()
/// Core Animation
updateTopLayer()
/// Core Graphics
setNeedsDisplay()
/// UIView
updateTopView()
}
// Core Animation
func updateTopLayer() {
topLayer.frame = CGRect(
x: lowerThumbSlider.center.x,
y: 0,
width: upperThumbSlider.frame.maxX - lowerThumbSlider.center.x,
height: 5
)
topLayer.backgroundColor = UIColor.systemYellow.cgColor
}
// UIView
func updateTopView() {
topView.frame = CGRect(
x: lowerSlider.center.x,
y: 0,
width: upperThumbSlider.frame.maxX - lowerThumbSlider.center.x,
height: 5
)
topView.backgroundColor = UIColor.systemYellow
}
func updatedLowerValue(moved delta: Double) -> Double {
return (lowerValue + delta).bound(lower: minimumValue, upper: upperValue - gapBetweenSliders)
}
func updatedUpperValue(moved delta: Double) -> Double {
return (upperValue + delta).bound(lower: lowerValue + gapBetweenSliders, upper: maximumValue)
}
func deltaValue(from previous: CGPoint, to current: CGPoint) -> Double {
let deltaLocation = Double(current.x - previous.x)
return (maximumValue - minimumValue) * deltaLocation / Double(totalLength)
}
func leading(of value: Double) -> Double {
return totalLength * value / maximumValue
}
}
一些
CALayer
属性是“隐式动画”的。这意味着当您更改属性时,系统会为您创建动画。
该代码可能如下所示:
CATransaction.begin()
CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
// Change CALayer properties that you don’t want to animate here.
CATransaction.commit()