子视图何时完全正确地布置?

时间:2018-07-09 21:26:24

标签: ios swift uiview uikit

我正在尝试以编程方式绘制一个圆进度视图并将其居中于子视图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(),但是目前这给了我相同的结果。任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:8)

@rmaddy的评论是正确的:处理此问题的最佳方法是使用自定义视图来管理trackLayercircleShapeLayer。覆盖自定义视图的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:的方式是(其中包括)发送三则消息:

  1. 如果视图由视图控制器拥有,则UIViewviewWillLayoutSubviews发送到其视图控制器。
  2. UIView向自身发送layoutSubviews
  3. 如果视图由视图控制器拥有,则UIViewviewDidLayoutSubviews发送到其视图控制器。

-[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为某个视图控制器的视图。然后,在拥有B的视图控制器中覆盖viewDidLayoutSubviews

如果只需要确定C的大小何时已更新,则可以选择第三种方法:

  1. 覆盖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
    }

}

我只是撒了一些兰多颜色,但我想你明白了...