如何使用 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 中将圆形进度条居中?的主要内容,如果未能解决你的问题,请参考以下文章

Swift 4为UIView添加自定义圆形[关闭]

尝试在 Swift 中创建圆形 UIView 时出错

如何在 iOS Swift 中将不同的角半径应用于 UIView 的不同角?

在 UIView (Swift) 中绘制圆形切口

swift Swift - 在UIView上创建带阴影的圆形图像的扩展

如何在 iOS Swift4 中创建这个圆形?