如何使用 Swift 在 UIView 中将圆形进度条居中?
Posted
技术标签:
【中文标题】如何使用 Swift 在 UIView 中将圆形进度条居中?【英文标题】:How can I centre a circular progress bar in a UIView using Swift? 【发布时间】:2020-09-01 19:53:23 【问题描述】:我很难确保圆形进度条始终位于与其关联的UIView
的中心。
这是正在发生的事情:
忽略灰色区域,这只是占位卡上的UIView
。红色是UIView
,我作为出口添加到UIViewController
。
下面是我制作的类的代码:
class CircleProgress: UIView
override init(frame: CGRect)
super.init(frame: frame)
setupView()
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
setupView()
var progressLyr = CAShapeLayer()
var trackLyr = CAShapeLayer()
var progressClr = UIColor.red
didSet
progressLyr.strokeColor = progressClr.cgColor
var trackClr = UIColor.black
didSet
trackLyr.strokeColor = trackClr.cgColor
private func setupView()
self.backgroundColor = UIColor.red
let centre = CGPoint(x: frame.size.width/2, y: frame.size.height/2)
let circlePath = UIBezierPath(arcCenter: centre,
radius: 10,
startAngle: 0,
endAngle: 2 * CGFloat.pi,
clockwise: true)
trackLyr.path = circlePath.cgPath
trackLyr.fillColor = UIColor.clear.cgColor
trackLyr.strokeColor = trackClr.cgColor
trackLyr.lineWidth = 5.0
trackLyr.strokeEnd = 1.0
layer.addSublayer(trackLyr)
目的只是让黑色圆圈的所有 4 个边缘都接触红色正方形的边缘。
非常感谢任何帮助。我在想这一定太明显了,但这今晚花了我太多时间。 :)
【问题讨论】:
【参考方案1】:一些建议:
我建议让CircularProgress
占据整个视图。如果您希望将其插入视图中,那么,可以为此添加一个属性。在下面的示例中,我创建了一个名为 inset
的属性来捕获此值。
确保在layoutSubviews
中更新您的路径。特别是如果您使用约束,您希望它能够响应大小变化。所以从init
添加这些层,但更新layoutSubviews
中的路径。
不要引用frame
(这是超级视图坐标系中的位置)。使用bounds
。并将其插入线宽的一半,这样圆圈就不会超出视图的边界。
您创建了进度颜色和进度图层,但没有使用其中任何一个。我猜你希望它显示轨道内的进度。
你正在从 0 到 2π 抚摸你的路径。人们倾向于期望这些循环进度视图从 -π/2(12 点钟)开始,然后进展到 3π/2。所以我更新了使用这些值的路径。不过随便用吧。
如果你愿意,如果你想在 IB 中看到这个渲染,你可以将它设为 @IBDesignable
。
因此,将它们放在一起,您会得到:
@IBDesignable
public class CircleProgress: UIView
@IBInspectable
public var lineWidth: CGFloat = 5 didSet updatePath()
@IBInspectable
public var strokeEnd: CGFloat = 1 didSet progressLayer.strokeEnd = strokeEnd
@IBInspectable
public var trackColor: UIColor = .black didSet trackLayer.strokeColor = trackColor.cgColor
@IBInspectable
public var progressColor: UIColor = .red didSet progressLayer.strokeColor = progressColor.cgColor
@IBInspectable
public var inset: CGFloat = 0 didSet updatePath()
private lazy var trackLayer: CAShapeLayer =
let layer = CAShapeLayer()
layer.fillColor = UIColor.clear.cgColor
layer.strokeColor = trackColor.cgColor
layer.lineWidth = lineWidth
return layer
()
private lazy var progressLayer: CAShapeLayer =
let layer = CAShapeLayer()
layer.fillColor = UIColor.clear.cgColor
layer.strokeColor = progressColor.cgColor
layer.lineWidth = lineWidth
return layer
()
override init(frame: CGRect = .zero)
super.init(frame: frame)
setupView()
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
setupView()
public override func layoutSubviews()
super.layoutSubviews()
updatePath()
private extension CircleProgress
func setupView()
layer.addSublayer(trackLayer)
layer.addSublayer(progressLayer)
func updatePath()
let rect = bounds.insetBy(dx: lineWidth / 2 + inset, dy: lineWidth / 2 + inset)
let centre = CGPoint(x: rect.midX, y: rect.midY)
let radius = min(rect.width, rect.height) / 2
let path = UIBezierPath(arcCenter: centre,
radius: radius,
startAngle: -.pi / 2,
endAngle: 3 * .pi / 2,
clockwise: true)
trackLayer.path = path.cgPath
trackLayer.lineWidth = lineWidth
progressLayer.path = path.cgPath
progressLayer.lineWidth = lineWidth
产生:
【讨论】:
在创建 UI 元素时,我对 let vs lazy var 感到困惑……你能指导我哪个更好吗?正如我所看到的,您正在使用惰性 var 来创建 UI 元素 我们尽可能使用let
。但我碰巧用闭包实例化它。但是闭包在初始化过程中不能引用其他属性。所以我把它做成了一个惰性属性,现在初始化闭包可以引用其他属性。如果您觉得这很混乱,请使用let
,但不要在闭包内进行配置,而是将配置代码移至setupView
。这是个人喜好问题。【参考方案2】:
更新这个函数
private func setupView()
self.backgroundColor = UIColor.red
let centre = CGPoint(x: bounds.midX, y: bounds.midY)
let circlePath = UIBezierPath(arcCenter: centre,
radius: bounds.maxX/2,
startAngle: 0,
endAngle: 2 * CGFloat.pi,
clockwise: true)
trackLyr.path = circlePath.cgPath
trackLyr.fillColor = UIColor.clear.cgColor
trackLyr.strokeColor = trackClr.cgColor
trackLyr.lineWidth = 5.0
trackLyr.strokeEnd = 1.0
layer.addSublayer(trackLyr)
【讨论】:
【参考方案3】:问题是创建对象时没有传递帧。无需对您的代码进行任何更改。当然,您必须将宽度和高度更改为您想要的任何值。
这是一个例子......
import UIKit
class ViewController: UIViewController
override func viewDidLoad()
super.viewDidLoad()
self.view.backgroundColor = .white
// Add circleProgress
addCircleProgress()
private func addCircleProgress()
let circleProgress = CircleProgress(frame: CGRect(x: self.view.center.x - 50, y: self.view.center.x - 50, width: 100, height: 100))
self.view.addSubview(circleProgress)
class CircleProgress: UIView
override init(frame: CGRect)
super.init(frame: frame)
setupView()
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
var progressLyr = CAShapeLayer()
var trackLyr = CAShapeLayer()
var progressClr = UIColor.red
didSet
progressLyr.strokeColor = progressClr.cgColor
var trackClr = UIColor.black
didSet
trackLyr.strokeColor = trackClr.cgColor
private func setupView()
self.backgroundColor = UIColor.red
let centre = CGPoint(x: frame.size.width/2, y: frame.size.height/2)
let circlePath = UIBezierPath(arcCenter: centre,
radius: 50,
startAngle: 0,
endAngle: 2 * CGFloat.pi,
clockwise: true)
trackLyr.path = circlePath.cgPath
trackLyr.fillColor = UIColor.clear.cgColor
trackLyr.strokeColor = trackClr.cgColor
trackLyr.lineWidth = 5.0
trackLyr.strokeEnd = 1.0
layer.addSublayer(trackLyr)
【讨论】:
如果我们改变圆形视图的框架呢?我们需要在圆形视图中进行更改吗?以上是关于如何使用 Swift 在 UIView 中将圆形进度条居中?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 iOS Swift 中将不同的角半径应用于 UIView 的不同角?