在layoutSubviews()中,子视图的边界为零

时间:2019-07-05 10:22:19

标签: ios swift uiview autolayout

自定义UIView的子视图的子视图的边界在layoutSubviews()中似乎为0,因此在layoutSubviews()中使用边界是一个问题。

为显示问题,我在GitHub上进行了演示:SOBoundsAreZero

以下是自定义视图实现的直接链接:DemoView.swift

自定义视图“ DemoView”的结构如下:

DemoView
    firstLevelSubview
        secondLevelSubview

正在使用“自动布局”以编程方式创建此结构。

调用layoutSubviews()时,视图和firstLevelSubview具有预期的边界,而secondLevelSubview的边界为0。

我希望至少在上次调用layoutSubviews时,所有使用“自动布局”的子视图都具有正确的边界。

结构是真实情况的抽象。为了避免该问题,可以将secondLevelSubview作为第一级子视图添加到DemoView。不过,这确实是不现实的。

我觉得我在这里缺少一些简单的东西,即使这是预期的行为。

3 个答案:

答案 0 :(得分:2)

我能够通过在secondLevelSubview.layoutIfNeeded()中调用layoutSubviews()来解决此问题。

override func layoutSubviews() {
    super.layoutSubviews()

    secondLevelSubview.layoutIfNeeded()

    firstLevelSubview.layer.cornerRadius = bounds.width / 2
    secondLevelSubview.layer.cornerRadius = secondLevelSubview.bounds.width / 2

    print("bounds.width: \(bounds.width), contentView.bounds.width: \(firstLevelSubview.bounds.width), backgroundView.bounds.width: \(secondLevelSubview.bounds.width)")
}

layoutIfNeeded()的描述是:

  

使用此方法强制视图立即更新其布局。   使用自动版式时,版式引擎会更新   满足约束更改所需的视图。使用的观点   接收消息作为根视图,此方法对视图进行布局   子树从根开始。如果没有任何布局更新待处理,则此   方法退出而不修改布局或调用任何   与布局相关的回调。

因此,基本上,您在这里有订购问题。该子视图已安排好布局,自动布局可以进入该子视图,但尚未完成。通过调用layoutIfNeeded(),您告诉自动版式立即执行挂起的版式,以便获取更新的框架信息。

注意:您也可以只调用self.layoutIfNeeded(),这将布局DemoView及其所有子视图。如果您有许多这样的子视图并且不想在每个子视图上调用layoutIfNeeded(),这将很有用。

答案 1 :(得分:0)

首先,您的视图没有任何问题,因为您有一个基于'CGRect'的初始化程序,因此它得到了它的尺寸! 第二, 在其父视图具有其尺寸之后,将初始化您的第一个子视图。第一个子视图的尺寸取决于准备好的父视图的尺寸。因此,正如您提到的,这里没有问题。 但是,与此同时,您的第一个子视图试图获取其尺寸时,您的第二个子视图也开始基于尚未准备好的第一个子视图获取其尺寸,因此一无所获。 我建议为每个子视图设置不同的功能。 在您的视图控制器中使用其框架初始化程序从视图类中创建对象。在“ viewDidLoad”中调用第一个子视图功能,在“ viewDidLoadSubView”中调用第二个子视图功能。 希望能帮助到你。

答案 2 :(得分:0)

如果尝试在主队列下访问UIView.bounds,则可以得到所需的结果。

override func layoutSubviews() {
    super.layoutSubviews()

    DispatchQueue.main.async {
        self.firstLevelSubview.layer.cornerRadius = self.bounds.width / 2
        self.secondLevelSubview.layer.cornerRadius = self.secondLevelSubview.bounds.width / 2
        print("bounds.width: \(self.bounds.width), contentView.bounds.width: \(self.firstLevelSubview.bounds.width), backgroundView.bounds.width: \(self.secondLevelSubview.bounds.width)")
    }
}

//print:
//bounds.width: 64.0, contentView.bounds.width: 64.0, backgroundView.bounds.width: 54.0

但是为什么呢?所以在这里我深入探讨您的问题。这是您的DemoView layoutSubviews方法的调用视图层次结构

DemoView
|
|___layoutSubviews
    |
    |___firstLevelSubview
        |
        |___layoutSubviews //Here you are trying to access bounds of second view which is still need to be resize or achieve it's bound
            |
            |___ secondLevelSubview
                | 
                |___layoutSubviews

因此,在这种情况下,主队列将帮助您获取任何UIView.subView的实际界限。

我做过的另一种情况,我尝试使用secondLevelSubview约束将DemoView添加到firstLevelSubview本身,如下所示:

fileprivate func setupViews() {
    backgroundColor = UIColor.clear

    firstLevelSubview.translatesAutoresizingMaskIntoConstraints = false
    firstLevelSubview.layer.masksToBounds = true
    firstLevelSubview.backgroundColor = UIColor.green
    addSubview(firstLevelSubview)
    firstLevelSubview.topAnchor.constraint(equalTo: topAnchor).isActive = true
    firstLevelSubview.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
    firstLevelSubview.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
    firstLevelSubview.leftAnchor.constraint(equalTo: leftAnchor).isActive = true

    secondLevelSubview.translatesAutoresizingMaskIntoConstraints = false
    secondLevelSubview.layer.masksToBounds = true
    secondLevelSubview.backgroundColor = UIColor.magenta
//  firstLevelSubview.addSubview(secondLevelSubview)
    addSubview(secondLevelSubview) //Here
    secondLevelSubview.widthAnchor.constraint(equalTo: firstLevelSubview.widthAnchor, multiplier: 0.84).isActive = true
    secondLevelSubview.heightAnchor.constraint(equalTo: firstLevelSubview.heightAnchor, multiplier: 0.84).isActive = true
    secondLevelSubview.centerXAnchor.constraint(equalTo: firstLevelSubview.centerXAnchor).isActive = true
    secondLevelSubview.centerYAnchor.constraint(equalTo: firstLevelSubview.centerYAnchor).isActive = true
}

其层次结构如下:

DemoView
    |
    |___layoutSubviews
        |
        |___firstLevelSubview
            |
            |___layoutSubviews
            |
            |___secondLevelSubview
            |
            |___layoutSubviews

因此,在上述情况下,如果尝试在没有任何主队列的情况下打印UIView.bounds,则将获得所需的结果。

让我知道,这可以帮助您了解层次结构。