什么是完整的子视图,正确布局?

Sid*_*ney 1 uikit uiview ios swift

我正在尝试以编程方式绘制圆形进度视图并将其置于子视图中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)

}
Run Code Online (Sandbox Code Playgroud)

这会打印circleView两次的宽度,并且只在第二次运行时viewDidLayoutSubviews()它才真正正确:

207.0
187.5 // Correct (width of entire view is 375)
Run Code Online (Sandbox Code Playgroud)

但是,圆圈​​在两次都沿着相同的精确路径不正确地绘制,这让我感到困惑,因为宽度如上所示变化.也许我正在考虑这个错误的方法?

我宁愿不画圆两次,希望会有一种方式来运行createCircle()viewDidLoad()代替,但目前这只是给了我同样的结果.任何帮助将非常感谢.

rob*_*off 13

@ rmaddy的评论是正确的:处理此问题的最佳方法是使用自定义视图来管理trackLayercircleShapeLayer.覆盖自定义视图layoutSubviews以设置图层的框架和/或路径.

也就是说,我会回答你所说的"什么时候子视图完整,正确布局?"

考虑此视图层次结构:

A
|
+--- B
|    |
|    +--- C
|    |
|    +--- D
|
+--- E
     |
     +--- F
     |
     +--- G
Run Code Online (Sandbox Code Playgroud)

在运行循环的布局阶段,Core Animation以深度优先的顺序遍历层次结构,查找需要布局的图层.因此,核心动画首先访问A层,然后是B层,然后是C层,然后是D层,然后是E层,然后是F层,然后是G层.

如果图层需要布局(其needsLayout属性为true),则Core Animation将发送layoutSublayers到图层.默认情况下,图层通过发送layoutSublayersOfLayer:给其委托来处理此问题.通常委托是UIView拥有该层的代理.

默认情况下,通过(以及其他)发送三个消息的UIView句柄layoutSublayersOfLayer::

  1. UIView发送viewWillLayoutSubviews,如果视图是由视图控制器所拥有的其视图控制器.
  2. UIView将自身发送layoutSubviews.
  3. 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的帧已经明确更新时,有两个好的地方可以运行代码:

  1. 覆盖B的layoutSubviews.由于B是C的直接超级视图,因此可以确定在B的layoutSubviews调用之后super.layoutSubviews(),C的帧已经更新.

  2. 放置一个视图控制器来管理B.也就是说,使B成为某个视图控制器的视图.然后,viewDidLayoutSubviews在拥有B的视图控制器中覆盖.

如果您只需要知道C的大小何时已经更新,那么您有第三种选择:

  1. 覆盖C的layoutSubviews.如果C更改大小,将调用此方法.如果C改变位置但保持相同的大小,则不一定会调用它.