为 UISlider 自定义形状并在 Swift 5 中更新进度
Custom shape to UISlider and update progress in Swift 5
我已经检查了一些关于自定义 UIView 滑块的 Whosebug 答案,但是使用它们我无法像这样制作滑块。这样就形成了一个圆或半圆。我已经找到了一些使用 UIView 制作圆形滑块的库,但它对我没有帮助所以任何人都可以帮助我。我怎样才能像下面的 UIImage 那样制作滑块?谢谢!
您可能会自行推出。 (您显然可以搜索第三方实现,但这超出了 Whosebug 的范围。)有很多方法可以解决这个问题,但这里的基本要素是:
- 整个路径的粉红色圆弧。就个人而言,我会为此使用
CAShapeLayer
。
- 从开始到当前进度(从0到1测量)的白色圆弧。同样,
CAShapeLayer
是合乎逻辑的。
- 当前进度处的白点。下面我创建了一个白色背景的
CALayer
,然后应用 CAGradientLayer
作为遮罩。您也可以为此创建一个 UIImage
。
- 关于如何设置进度,你会把粉色和白色圆弧的路径设置成相同的路径,只是更新白色圆弧的
strokeEnd
。您也可以相应地调整白点图层的位置。
- 这里唯一复杂的事情是找出圆弧的中心。在我下面的例子中,我根据视图的边界用一些三角函数计算它,这样弧从左下角到顶部,然后回到右下角。或者您可以将圆弧的中心作为参数传递。
无论如何,它可能看起来像:
@IBDesignable
class ArcView: UIView {
@IBInspectable
var lineWidth: CGFloat = 7 { didSet { updatePaths() } }
@IBInspectable
var progress: CGFloat = 0 { didSet { updatePaths() } }
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
progress = 0.35
}
lazy var currentPositionDotLayer: CALayer = {
let layer = CALayer()
layer.backgroundColor = UIColor.white.cgColor
let rect = CGRect(x: 0, y: 0, width: lineWidth * 3, height: lineWidth * 3)
layer.frame = rect
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor.white.cgColor, UIColor.clear.cgColor]
gradientLayer.type = .radial
gradientLayer.frame = rect
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 1)
gradientLayer.locations = [0.5, 1]
layer.mask = gradientLayer
return layer
}()
lazy var progressShapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.lineCap = .round
shapeLayer.lineWidth = lineWidth
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
return shapeLayer
}()
lazy var totalShapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.lineCap = .round
shapeLayer.lineWidth = lineWidth
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = #colorLiteral(red: 0.9439327121, green: 0.5454334617, blue: 0.6426400542, alpha: 1)
return shapeLayer
}()
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
override func layoutSubviews() {
super.layoutSubviews()
updatePaths()
}
}
// MARK: - Private utility methods
private extension ArcView {
func configure() {
layer.addSublayer(totalShapeLayer)
layer.addSublayer(progressShapeLayer)
layer.addSublayer(currentPositionDotLayer)
}
func updatePaths() {
let rect = bounds.insetBy(dx: lineWidth / 2, dy: lineWidth / 2)
let halfWidth = rect.width / 2
let height = rect.height
let theta = atan(halfWidth / height)
let radius = sqrt(halfWidth * halfWidth + height * height) / 2 / cos(theta)
let center = CGPoint(x: rect.midX, y: rect.minY + radius)
let delta = (.pi / 2 - theta) * 2
let startAngle = .pi * 3 / 2 - delta
let endAngle = .pi * 3 / 2 + delta
let path = UIBezierPath(arcCenter: center,
radius: radius,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true)
progressShapeLayer.path = path.cgPath // white arc
totalShapeLayer.path = path.cgPath // pink arc
progressShapeLayer.strokeEnd = progress
let currentAngle = (endAngle - startAngle) * progress + startAngle
let dotCenter = CGPoint(x: center.x + radius * cos(currentAngle),
y: center.y + radius * sin(currentAngle))
currentPositionDotLayer.position = dotCenter
}
}
上面,我设置了 ArcView
的背景颜色,所以你可以看到它的 bounds
,但显然你会把背景颜色设置为透明的。
现在您可以添加大量附加功能(例如,添加用户交互以便您可以“清理”它等)。例如,参见 https://github.com/robertmryan/ArcView。但是设计这类东西的关键是将其分解成组成元素,将一个元素叠加在另一个元素之上。
您可以通过编程方式设置 arcView
的 progress
以使其在值 0 和 1 之间更改当前值:
func startUpdating() {
arcView.progress = 0
var current = 0
Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { [weak self] timer in
current += 1
guard let self = self, current <= 10 else {
timer.invalidate()
return
}
self.arcView.progress = CGFloat(current) / 10
}
}
导致:
我已经检查了一些关于自定义 UIView 滑块的 Whosebug 答案,但是使用它们我无法像这样制作滑块。这样就形成了一个圆或半圆。我已经找到了一些使用 UIView 制作圆形滑块的库,但它对我没有帮助所以任何人都可以帮助我。我怎样才能像下面的 UIImage 那样制作滑块?谢谢!
您可能会自行推出。 (您显然可以搜索第三方实现,但这超出了 Whosebug 的范围。)有很多方法可以解决这个问题,但这里的基本要素是:
- 整个路径的粉红色圆弧。就个人而言,我会为此使用
CAShapeLayer
。 - 从开始到当前进度(从0到1测量)的白色圆弧。同样,
CAShapeLayer
是合乎逻辑的。 - 当前进度处的白点。下面我创建了一个白色背景的
CALayer
,然后应用CAGradientLayer
作为遮罩。您也可以为此创建一个UIImage
。 - 关于如何设置进度,你会把粉色和白色圆弧的路径设置成相同的路径,只是更新白色圆弧的
strokeEnd
。您也可以相应地调整白点图层的位置。 - 这里唯一复杂的事情是找出圆弧的中心。在我下面的例子中,我根据视图的边界用一些三角函数计算它,这样弧从左下角到顶部,然后回到右下角。或者您可以将圆弧的中心作为参数传递。
无论如何,它可能看起来像:
@IBDesignable
class ArcView: UIView {
@IBInspectable
var lineWidth: CGFloat = 7 { didSet { updatePaths() } }
@IBInspectable
var progress: CGFloat = 0 { didSet { updatePaths() } }
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
progress = 0.35
}
lazy var currentPositionDotLayer: CALayer = {
let layer = CALayer()
layer.backgroundColor = UIColor.white.cgColor
let rect = CGRect(x: 0, y: 0, width: lineWidth * 3, height: lineWidth * 3)
layer.frame = rect
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor.white.cgColor, UIColor.clear.cgColor]
gradientLayer.type = .radial
gradientLayer.frame = rect
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 1)
gradientLayer.locations = [0.5, 1]
layer.mask = gradientLayer
return layer
}()
lazy var progressShapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.lineCap = .round
shapeLayer.lineWidth = lineWidth
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
return shapeLayer
}()
lazy var totalShapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.lineCap = .round
shapeLayer.lineWidth = lineWidth
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = #colorLiteral(red: 0.9439327121, green: 0.5454334617, blue: 0.6426400542, alpha: 1)
return shapeLayer
}()
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
override func layoutSubviews() {
super.layoutSubviews()
updatePaths()
}
}
// MARK: - Private utility methods
private extension ArcView {
func configure() {
layer.addSublayer(totalShapeLayer)
layer.addSublayer(progressShapeLayer)
layer.addSublayer(currentPositionDotLayer)
}
func updatePaths() {
let rect = bounds.insetBy(dx: lineWidth / 2, dy: lineWidth / 2)
let halfWidth = rect.width / 2
let height = rect.height
let theta = atan(halfWidth / height)
let radius = sqrt(halfWidth * halfWidth + height * height) / 2 / cos(theta)
let center = CGPoint(x: rect.midX, y: rect.minY + radius)
let delta = (.pi / 2 - theta) * 2
let startAngle = .pi * 3 / 2 - delta
let endAngle = .pi * 3 / 2 + delta
let path = UIBezierPath(arcCenter: center,
radius: radius,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true)
progressShapeLayer.path = path.cgPath // white arc
totalShapeLayer.path = path.cgPath // pink arc
progressShapeLayer.strokeEnd = progress
let currentAngle = (endAngle - startAngle) * progress + startAngle
let dotCenter = CGPoint(x: center.x + radius * cos(currentAngle),
y: center.y + radius * sin(currentAngle))
currentPositionDotLayer.position = dotCenter
}
}
上面,我设置了 ArcView
的背景颜色,所以你可以看到它的 bounds
,但显然你会把背景颜色设置为透明的。
现在您可以添加大量附加功能(例如,添加用户交互以便您可以“清理”它等)。例如,参见 https://github.com/robertmryan/ArcView。但是设计这类东西的关键是将其分解成组成元素,将一个元素叠加在另一个元素之上。
您可以通过编程方式设置 arcView
的 progress
以使其在值 0 和 1 之间更改当前值:
func startUpdating() {
arcView.progress = 0
var current = 0
Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { [weak self] timer in
current += 1
guard let self = self, current <= 10 else {
timer.invalidate()
return
}
self.arcView.progress = CGFloat(current) / 10
}
}
导致: