我正在尝试以编程方式绘制一个圆进度视图并将其居中于子视图circleView
中,该视图已在界面构建器中设置/约束。但是,我不确定何时可以访问circleView
的最终大小和中心(我正在使用自动布局),这最终需要绘制圆圈。这是涉及的代码:
@IBOutlet weak var circleView: UIView!
let circleShapeLayer = CAShapeLayer()
let trackLayer = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
// createCircle()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print(circleView.frame.size.width)
createCircle()
}
func createCircle() {
// Draw/modify circle
let center = circleView.center
// Where I need to use circleView's width/center
let circularPath = UIBezierPath(arcCenter: center, radius: circleView.frame.size.width, startAngle: -CGFloat.pi/2, endAngle: 2 * CGFloat.pi, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.lightGray.cgColor
trackLayer.lineWidth = 10
trackLayer.fillColor = UIColor.clear.cgColor
circleView.layer.addSublayer(trackLayer)
circleShapeLayer.path = circularPath.cgPath
circleShapeLayer.strokeColor = Colors.tintColor.cgColor
circleShapeLayer.lineWidth = 10
circleShapeLayer.fillColor = UIColor.clear.cgColor
circleShapeLayer.lineCap = kCALineCapRound
// circleShapeLayer.strokeEnd = 0
circleView.layer.addSublayer(circleShapeLayer)
}
这会打印两次circleView
的宽度,并且仅在第二次运行viewDidLayoutSubviews()
时才正确:
207.0
187.5 // Correct (width of entire view is 375)
但是,圆两次都错误地沿着相同的精确路径绘制,这使我感到困惑,因为宽度如上所述变化。也许我在想这个错误的方式?
我宁愿不要画两次圆,而是希望有一种方法可以在createCircle()
内运行viewDidLoad()
,但是目前这给了我相同的结果。任何帮助将不胜感激。
答案 0 :(得分:8)
@rmaddy的评论是正确的:处理此问题的最佳方法是使用自定义视图来管理trackLayer
和circleShapeLayer
。覆盖自定义视图的layoutSubviews
,以设置图层的框架和/或路径。
也就是说,我将回答您所说的“子视图何时完整,正确布置?”
考虑此视图层次结构:
A
|
+--- B
| |
| +--- C
| |
| +--- D
|
+--- E
|
+--- F
|
+--- G
在运行循环的布局阶段,Core Animation以深度优先的顺序遍历图层层次,以查找需要布局的图层。因此,Core Animation首先访问A的层,然后访问B的层,然后访问C的层,然后访问D的层,然后访问E的层,然后访问F的层,然后访问G的层。
如果图层需要布局(其needsLayout
属性为true),则Core Animation将layoutSublayers
发送到该图层。默认情况下,图层通过将layoutSublayersOfLayer:
发送给其委托来处理此问题。通常,委托人是拥有图层的UIView
。
默认情况下,UIView
处理layoutSublayersOfLayer:
的方式是(其中包括)发送三则消息:
UIView
将viewWillLayoutSubviews
发送到其视图控制器。UIView
向自身发送layoutSubviews
。UIView
将viewDidLayoutSubviews
发送到其视图控制器。在-[UIView layoutSubviews]
的默认实现中,该视图基于自动布局约束设置其每个直接子视图的框架。
请注意,在layoutSubviews
中,视图仅设置其直接子视图的框架。因此,例如,A仅设置B和E的帧。不设置C,D,F和G的帧。
因此,我们假设A是视图控制器的视图,但其他视图均不属于视图控制器。
当A处理layoutSubviews
时,它将设置B和E的帧。然后将viewDidLayoutSubviews
发送到其视图控制器。 C,D,F和G的框架目前尚未尚未更新。视图控制器不能假定C,D,F和G在其viewDidLayoutSubviews
中具有正确的帧。
有两个放置C的框架肯定会更新的代码的好地方:
覆盖B的layoutSubviews
。由于B是C的直接父视图,因此可以确定B的layoutSubviews
调用super.layoutSubviews()
之后,C的框架已经更新。
放置一个由B负责的视图控制器。即,使B为某个视图控制器的视图。然后,在拥有B的视图控制器中覆盖viewDidLayoutSubviews
。
如果只需要确定C的大小何时已更新,则可以选择第三种方法:
layoutSubviews
。如果C
更改大小,将调用此方法。如果C改变位置但大小不变,则不一定会调用它。答案 1 :(得分:1)
像其他人一样,您应该将圆形视图编写为独立对象。只需将UIView子类化,并在需要时调用它即可。
我拿了你的代码并开始使用:
import Foundation
import UIKit
class MyCircle : UIView {
init(){
super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
setupViews();
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setupViews() {
let circleShapeLayer = CAShapeLayer()
let trackLayer = CAShapeLayer()
// Draw/modify circle
let center = self.center
// Where I need to use circleView's width/center
let circularPath = UIBezierPath(arcCenter: center, radius: self.frame.size.width, startAngle: -CGFloat.pi/2, endAngle: 2 * CGFloat.pi, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.lightGray.cgColor
trackLayer.lineWidth = 10
trackLayer.fillColor = UIColor.clear.cgColor
self.layer.addSublayer(trackLayer)
circleShapeLayer.path = circularPath.cgPath
circleShapeLayer.strokeColor = UIColor.blue.cgColor
circleShapeLayer.lineWidth = 10
circleShapeLayer.fillColor = UIColor.clear.cgColor
circleShapeLayer.lineCap = kCALineCapRound
circleShapeLayer.strokeEnd = 0
self.layer.addSublayer(circleShapeLayer)
self.backgroundColor = UIColor.black
}
}
我只是撒了一些兰多颜色,但我想你明白了...