为啥这些受约束的布局有不同的尺寸?

Posted

技术标签:

【中文标题】为啥这些受约束的布局有不同的尺寸?【英文标题】:Why do these constrained layouts have different sizes?为什么这些受约束的布局有不同的尺寸? 【发布时间】:2019-05-20 05:51:06 【问题描述】:

我的布局坏了。我在运行时添加了一个布局,我使用以下代码将其锚定到所有 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 报告的尺寸更大,即显示时的尺寸。

【问题讨论】:

具体什么时候打印描述?您能否展示更多代码以及如何设置视图层次结构? @AndréSlotta 我可以展示更多,但它们与我的问题无关。问题是为什么固定在所有 4 个侧面的 2 个视图的大小不同? 实际上,当您打印到控制台时,查看视图是否已经布局是相关的。 @AndréSlotta - 我知道。这就是为什么我说我在布局视图之后打印到控制台。这就是为什么我不明白它们如何在不破坏约束的情况下具有不同的尺寸。我希望我清楚自己。在视图布局后,我通过在 Xcode 中打开视图层次结构调试器来打印这些尺寸。 【参考方案1】:

如果它们被限制为相等,那么帧大小如何不同?

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

嗯,运行时直到您打印描述之后才会购买牛奶。布局不会立即发生;它发生在布局时,即在所有代码停止运行并且当前 CATransaction 已提交之后。之后,这两个视图将具有相同的大小。

请注意,我说的不仅仅是第一次时间布局发生。布局会在视图的整个生命周期中间歇性地发生,因为会发生布局触发事件。但是这些时刻之间,完全有可能出现其他代码并将视图的框架手动设置为其他内容。在再次触发布局之前,它不会变回约束所规定的内容。

(当然这也包括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)>, 
    ...
]

这就像您的打印输出:约束说两个视图的边缘应该相等,但它们的框架大小不同。所以你所报告的确实是极有可能的。

【讨论】:

我在问题中添加了更多信息,以澄清调试器信息是在 布局发生后 打印的。这就是我有这个问题的原因。 但是您仍然没有提供足够的信息让我们重现您的发现。 此外,您在视图调试器中这一事实并不能证明您允许layoutSubviews 发生。问题是什么时候,而不是什么地方。 不知道如何让你相信 layoutSubviews 已经发生了,但我在 layoutSubviews 中为父子视图添加了一个调试日志语句,并且它们都记录了 before 我打开查看层次结构调试器。我的源代码庞大、专有且复杂。我不能给你所有。我的问题不是任何人都可以重现这个问题,而是“这怎么可能?”。 添加了几个段落,指出在布局触发事件之间完全有可能出现其他代码并更改框架。我认为这构成了对“这怎么可能”问题的充分回答。绝对可以。为什么它会发生在你的应用中,只有你自己知道。【参考方案2】:

@matt 的回答准确地描述了我试图在我的 cmets 中要求的内容。示例:

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)

【讨论】:

我在问题中添加了更多信息,以明确我在控制台中打印的尺寸是在布局发生之后。

以上是关于为啥这些受约束的布局有不同的尺寸?的主要内容,如果未能解决你的问题,请参考以下文章

如何在情节提要中使用自动布局在不同设备尺寸上设置动态位置的约束

为啥 UIView 的这些自动布局约束会阻止 UIButton 子视图接收触摸事件?

当我添加自动布局约束时,为啥我的 textview 不显示?

不了解自动布局、约束和尺寸类

为啥我的第二个按钮在使用自动布局约束时表现不同?

更改尺寸等级时会影响约束