为什么这些约束布局具有不同的大小?

时间:2018-12-19 17:20:09

标签: ios swift uiviewcontroller autolayout nslayoutconstraint

我的布局已损坏。我在运行时添加了一个布局,并使用以下代码将其锚定到所有4个方面:

func anchorAllSides(to parentView:UIView, identifier: String? = nil ) {
    self.translatesAutoresizingMaskIntoConstraints = false
    let top = self.topAnchor.constraint(equalTo: parentView.topAnchor)
    let bottom = self.bottomAnchor.constraint(equalTo: parentView.bottomAnchor)
    let left = self.leadingAnchor.constraint(equalTo: parentView.leadingAnchor)
    let right = self.trailingAnchor.constraint(equalTo: parentView.trailingAnchor)
    if let identifierString = identifier {
        top.identifier = "\(identifierString) - top"
        bottom.identifier = "\(identifierString) - bottom"
        left.identifier = "\(identifierString) - left"
        right.identifier = "\(identifierString) - right"
    }
    NSLayoutConstraint.activate([top, bottom, left, right])
}

在控制台中,我可以看到2个视图具有约束所有4个侧面的约束,但是它们的大小也完全不同。

Printing description of $20:
<UIView: 0x7f93084220c0; frame = (0 0; 375 205.333); autoresize = W+H; gestureRecognizers = <NSArray: 0x600001956ac0>; layer = <CALayer: 0x60000176b320>>


Printing description of $21:  
<UIView: 0x7f930840ed40; frame = (0 528.667; 375 528.667); autoresize = RM+BM; layer = <CALayer: 0x6000017698e0>>

(lldb) po [0x7f930840ed40 constraints]
<__NSArrayI 0x600000650330>(
<NSLayoutConstraint:0x60000346a760 'drawerControllerView <-> rolePageDrawerView - bottom' UIView:0x7f93084220c0.bottom == UIView:0x7f930840ed40.bottom   (active)>,
<NSLayoutConstraint:0x60000346a7b0 'drawerControllerView <-> rolePageDrawerView - left' H:|-(0)-[UIView:0x7f93084220c0]   (active, names: '|':UIView:0x7f930840ed40 )>,
<NSLayoutConstraint:0x60000346a800 'drawerControllerView <-> rolePageDrawerView - right' UIView:0x7f93084220c0.trailing == UIView:0x7f930840ed40.trailing   (active)>,
<NSLayoutConstraint:0x60000346a710 'drawerControllerView <-> rolePageDrawerView - top' V:|-(0)-[UIView:0x7f93084220c0]   (active, names: '|':UIView:0x7f930840ed40 )>
)

通过在Xcode的视图层次结构调试器中查看视图来布局视图后,将打印上述信息。因此,所有约束都处于活动状态,此时应该使它们的大小相等。

如果将它们约束为相等,那么帧大小如何不同?

更新

为了缩小这一范围,我在父VC(角色vc)和子VC(日历vc)的layoutSubviews()函数中使用了断点日志记录。在这两种方法中,我都记录了抽屉的父视图的大小。在子级VC中,我记录了抽屉视图和self.view的大小,这是子项到抽屉的位置,并在所有侧面固定到抽屉。我还记录了抽屉视图实例,以确保它们相同。这是另一次运行的日志(因此,实例地址与上面的日志记录相比有所更改):

role page vc drawerSize (width = 375, height = 190.5)
role page vc drawer 0x00007f7ffd516d50
calendar vc drawerSize (width = 375, height = 222.66666666666666)
calendar vc view size (width = 375, height = 222.66666666666666)
calendar vc drawer is 0x00007f7ffd516d50
role page vc drawerSize (width = 375, height = 589.33333333333337)
role page vc drawer 0x00007f7ffd516d50
role page vc drawerSize (width = 375, height = 589.33333333333337)
role page vc drawer 0x00007f7ffd516d50
calendar vc drawerSize (width = 375, height = 205.33333333333334)
calendar vc view size (width = 375, height = 205.33333333333334)
calendar vc drawer is 0x00007f7ffd516d50

如您所见,抽屉实例是相同的。但是,日历vc报告将其插入和固定在其中的抽屉视图的大小不同。日历vc认为其视图和抽屉视图具有相同的大小,但是抽屉视图的父vc报告的是较大的大小,即显示时的大小。

2 个答案:

答案 0 :(得分:2)

  

如果将它们约束为相等,那么帧大小如何不同?

因为约束仅仅是指令。在执行这些说明之前,它们仅是说明而已。就像购物清单;您可以看到牛奶在清单上,但是您没有说“为什么冰箱里没有牛奶?”您还必须购买牛奶。

好吧,运行时要等到您打印说明后再购买牛奶。布局不会立即发生;它发生在布局时,这是在您的所有代码停止运行并且提交了当前的CATransaction之后。发生这种情况后,两个视图将具有相同的大小。

请注意,我不仅在谈论 first 时间安排的发生。随着视图触发事件的发生,布局在视图的整个生命周期中会间歇性地发生。但是,在这两个时刻之间,其他代码很有可能会出现,并手动将视图的框架设置为其他内容。在重新触发布局之前,它不会变回到约束条件。

(当然包括layoutSubviewsviewDidLayoutSubviews本身。此时会发生自动布局,但是您的代码可以出现并覆盖自动布局的内容。)

只是为了说明问题,这是一个应用程序的完整代码,它使我们进入您所描述的情况:

func delay(_ delay:Double, closure:@escaping ()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let v = UIView()
        v.backgroundColor = .red
        v.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(v)
        NSLayoutConstraint.activate([
            v.topAnchor.constraint(equalTo:self.view.topAnchor),
            v.leadingAnchor.constraint(equalTo:self.view.leadingAnchor),
            v.trailingAnchor.constraint(equalTo:self.view.trailingAnchor),
            v.bottomAnchor.constraint(equalTo:self.view.bottomAnchor),
            ])
        delay(5) {
            v.frame = v.frame.insetBy(dx: 30, dy: 30)
            print(v.frame)
            print(self.view.frame)
            print(self.view.constraints)
        }
    }
}

注意打印输出(为清楚起见,我已经编辑了一些约束):

(30.0, 30.0, 354.0, 676.0)
(0.0, 0.0, 414.0, 736.0)
[
    <NSLayoutConstraint:0x600000083570 V:|-(0)-[UIView:0x7fede9d07190]   (active, names: '|':UIView:0x7fede9f0d230 )>, 
    <NSLayoutConstraint:0x6000000957c0 H:|-(0)-[UIView:0x7fede9d07190]   (active, names: '|':UIView:0x7fede9f0d230 )>, 
    <NSLayoutConstraint:0x600000094c30 UIView:0x7fede9d07190.trailing == UIView:0x7fede9f0d230.trailing   (active)>, 
    <NSLayoutConstraint:0x600000095f40 UIView:0x7fede9d07190.bottom == UIView:0x7fede9f0d230.bottom   (active)>, 
    ...
]

这就像您的打印输出:约束条件说,两个视图的边缘应该相等,但是它们的框架大小不同。因此,您举报的确实非常有可能。

答案 1 :(得分:0)

@matt 的答案恰好描述了我在评论中想要的内容。示例:

override func viewDidLoad() {
    super.viewDidLoad()

    let subview = UIView()
    subview.backgroundColor = .orange

    view.addSubview(subview)
    subview.anchorAllSides(to: view)

    print("BEFORE layoutIfNeeded")
    print(view.frame)
    print(subview.frame)

    view.layoutIfNeeded() // forces the view to update its layout immediately

    print("AFTER layoutIfNeeded")
    print(view.frame)
    print(subview.frame)
}

此打印:

BEFORE layoutIfNeeded
(0.0, 0.0, 375.0, 812.0)
(0.0, 0.0, 0.0, 0.0)
AFTER layoutIfNeeded
(0.0, 0.0, 375.0, 812.0)
(0.0, 0.0, 375.0, 812.0)