【问题标题】:How can I centre a circular progress bar in a UIView using Swift?如何使用 Swift 在 UIView 中将圆形进度条居中?
【发布时间】: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 个边缘都接触红色正方形的边缘。

非常感谢任何帮助。我在想这一定太明显了,但这今晚花了我太多时间。 :)

【问题讨论】:

    标签: swift uiview storyboard


    【解决方案1】:

    一些建议:

    1. 我建议让CircularProgress 占据整个视图。如果您希望将其插入视图中,那么,可以为此添加一个属性。在下面的示例中,我创建了一个名为 inset 的属性来捕获此值。

    2. 确保在layoutSubviews 中更新您的路径。特别是如果您使用约束,您希望它能够响应大小变化。所以从init添加这些层,但更新layoutSubviews中的路径。

    3. 不要引用frame(这是超级视图坐标系中的位置)。使用bounds。并将其插入线宽的一半,这样圆圈就不会超出视图的边界。

    4. 您创建了进度颜色和进度图层,但没有使用其中任何一个。我猜你希望它显示轨道内的进度。

    5. 你正在从 0 到 2π 抚摸你的路径。人们倾向于期望这些循环进度视图从 -π/2(12 点钟)开始,然后进展到 3π/2。所以我更新了使用这些值的路径。不过随便用吧。

    6. 如果你愿意,如果你想在 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)
          }
      }
      

      【讨论】:

      • 如果我们改变圆形视图的框架呢?我们需要在圆形视图中进行更改吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-28
      相关资源
      最近更新 更多