为什么这些受约束的布局具有不同的大小?
Why do these constrained layouts have different sizes?
我的布局坏了。我在 运行 时添加了一个布局,我使用以下代码锚定到所有 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 :
<UIView: 0x7f93084220c0; frame = (0 0; 375 205.333); autoresize = W+H; gestureRecognizers = <NSArray: 0x600001956ac0>; layer = <CALayer: 0x60000176b320>>
Printing description of :
<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的视图层级调试器中查看视图布局视图后打印的。因此,所有这些约束都处于活动状态,此时它们的大小应该相等。
如果约束它们相等,帧大小怎么可能不同?
更新
为了缩小范围,我在 parent VC(角色页面 vc)和 childVC(日历vc)。在两者中,我都记录了抽屉的 parent 视图的大小。在 child VC 中,我记录了抽屉视图和 self.view 的大小,这是 child 到抽屉的所有侧面固定到抽屉的尺寸。我还记录了抽屉视图实例,以确保它们相同。这是来自另一个 运行 的日志(因此实例地址已从上面的日志中更改):
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 认为它的视图和抽屉视图大小相同,但是抽屉视图的 parent vc 报告更大的尺寸,这是显示时的尺寸。
If they are constrained to be equal, how can the frame sizes be different?
因为约束只是指令。在执行这些指示之前,它们只是指示而已。这就像一张购物清单;你可以看到牛奶在清单上,但你不会说“那为什么冰箱里没有牛奶?”你还得买牛奶。
好吧,运行时直到您打印描述后才会购买牛奶。布局不会立即发生;它发生在布局时,即在所有代码停止 运行 并且当前 CATransaction 已提交之后。发生这种情况后,这两个视图将具有相同的大小。
请注意,我不只是在谈论 第一次 布局发生的时间。随着布局触发事件的发生,布局在视图的整个生命周期中间歇性地发生。但是 在 那些时刻之间,其他代码完全有可能出现并手动将视图的框架设置为其他内容。在再次触发布局之前,它不会改回约束规定的内容。
(当然也包括 layoutSubviews
和 viewDidLayoutSubviews
本身。Autolayout 发生在那个时候,但是你的代码可以出现并覆盖 autolayout 所做的。)
为了说明问题,这里是一个应用程序的完整代码,可以让我们进入您所描述的情况:
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)>,
...
]
这就像您的打印输出:约束说明两个视图的边缘应该相等,但它们的框架大小不同。所以你所报告的确实是非常可能的。
@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)
我的布局坏了。我在 运行 时添加了一个布局,我使用以下代码锚定到所有 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 :
<UIView: 0x7f93084220c0; frame = (0 0; 375 205.333); autoresize = W+H; gestureRecognizers = <NSArray: 0x600001956ac0>; layer = <CALayer: 0x60000176b320>>
Printing description of :
<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的视图层级调试器中查看视图布局视图后打印的。因此,所有这些约束都处于活动状态,此时它们的大小应该相等。
如果约束它们相等,帧大小怎么可能不同?
更新
为了缩小范围,我在 parent VC(角色页面 vc)和 childVC(日历vc)。在两者中,我都记录了抽屉的 parent 视图的大小。在 child VC 中,我记录了抽屉视图和 self.view 的大小,这是 child 到抽屉的所有侧面固定到抽屉的尺寸。我还记录了抽屉视图实例,以确保它们相同。这是来自另一个 运行 的日志(因此实例地址已从上面的日志中更改):
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 认为它的视图和抽屉视图大小相同,但是抽屉视图的 parent vc 报告更大的尺寸,这是显示时的尺寸。
If they are constrained to be equal, how can the frame sizes be different?
因为约束只是指令。在执行这些指示之前,它们只是指示而已。这就像一张购物清单;你可以看到牛奶在清单上,但你不会说“那为什么冰箱里没有牛奶?”你还得买牛奶。
好吧,运行时直到您打印描述后才会购买牛奶。布局不会立即发生;它发生在布局时,即在所有代码停止 运行 并且当前 CATransaction 已提交之后。发生这种情况后,这两个视图将具有相同的大小。
请注意,我不只是在谈论 第一次 布局发生的时间。随着布局触发事件的发生,布局在视图的整个生命周期中间歇性地发生。但是 在 那些时刻之间,其他代码完全有可能出现并手动将视图的框架设置为其他内容。在再次触发布局之前,它不会改回约束规定的内容。
(当然也包括 layoutSubviews
和 viewDidLayoutSubviews
本身。Autolayout 发生在那个时候,但是你的代码可以出现并覆盖 autolayout 所做的。)
为了说明问题,这里是一个应用程序的完整代码,可以让我们进入您所描述的情况:
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)>,
...
]
这就像您的打印输出:约束说明两个视图的边缘应该相等,但它们的框架大小不同。所以你所报告的确实是非常可能的。
@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)