【发布时间】:2022-01-22 23:02:57
【问题描述】:
我创建了一个UIView extension,它应该将子视图移动到另一个(子)视图中,同时保持约束不变。子视图和视图之间的约束以及移动的子视图之间的约束。
虽然这在大多数情况下都可以正常工作,但 Xcode 在使用扩展名 a UICollectionViewCell 时会显示约束错误。奇怪的是,错误是在一个不存在的约束上报告的。
对于这个问题这么长,我深表歉意,但这个话题相当复杂,我试图提供尽可能多的内部信息。
演示项目可用于重现问题。
免责声明
这不是关于将带有约束的视图移动到新的子视图是否是个好主意的问题。如下所述,Xcode 在不存在(不再存在)的约束上显示了一个非常奇怪的错误,问题是,这怎么可能。
问题描述
演示项目
我创建了一个demo项目,上传到同事的GitHub账号:https://github.com/SDPrio/ConstraintsTest
这是一个简单的 iOS 应用程序,只有一个 ViewController 包含 UICollectionView。集合视图仅显示一个TestCell 单元格。 TestCell 只包含一个UILabel。
运行项目时,可以在调试控制台看到约束错误。
// View hierachy
TestCell ==> TestCell
ContentView ContentView
TitleLabel ContainerView
ContainerView ClippingView
ClippingView TitleLabel
调试输出
该项目还使用the extension 转储视图和约束层次结构before 和after,以将单元格内容(= 标签)移动到包装器视图中:
// BEFORE moving
TestCell - 0x000000014e907190
<NSLayoutConstraint:0x60000089f1b0 'UIIBSystemGenerated' ...>
..
// ContentView
UIView - 0x000000014e9178e0
// Constraints between TitleLabel and ContentView
<NSLayoutConstraint:0x60000089c3c0 V:|-(10)-[UILabel:0x14e913580] (active, names: '|':UIView:0x14e9178e0 )>
<NSLayoutConstraint:0x60000089f200 H:|-(10)-[UILabel:0x14e913580] (active, names: '|':UIView:0x14e9178e0 )>
<NSLayoutConstraint:0x60000089f250 V:[UILabel:0x14e913580]-(10)-| (active, names: '|':UIView:0x14e9178e0 )>
<NSLayoutConstraint:0x60000089f2a0 H:[UILabel:0x14e913580]-(10)-| (active, names: '|':UIView:0x14e9178e0 )>
// Constraints between first wrapper view (= ContainerView) and ContentView
<NSLayoutConstraint:0x60000089fb10 V:|-(5)-[UIView:0x14e91d650] (active, names: '|':UIView:0x14e9178e0 )>
<NSLayoutConstraint:0x60000089fcf0 H:|-(5)-[UIView:0x14e91d650] (active, names: '|':UIView:0x14e9178e0 )>
<NSLayoutConstraint:0x60000089fd40 UIView:0x14e91d650.bottom == UIView:0x14e9178e0.bottom - 5 (active)>
<NSLayoutConstraint:0x60000089fde0 UIView:0x14e91d650.trailing == UIView:0x14e9178e0.trailing - 5 (active)>
UILabel - 0x000000014e913580 // Title Label
UIView - 0x000000014e91d650 // ContainerView
// Constraints between first wrapper view (= ContainerView) and second wrapper view (= ClippingView)
<NSLayoutConstraint:0x60000089fe30 V:|-(0)-[UIView:0x14e91e770] (active, names: '|':UIView:0x14e91d650 )>
<NSLayoutConstraint:0x60000089fe80 H:|-(0)-[UIView:0x14e91e770] (active, names: '|':UIView:0x14e91d650 )>
<NSLayoutConstraint:0x60000089fed0 UIView:0x14e91e770.bottom == UIView:0x14e91d650.bottom (active)>
<NSLayoutConstraint:0x60000089ff20 UIView:0x14e91e770.trailing == UIView:0x14e91d650.trailing (active)>
UIView - 0x000000014e91e770 // ClippingView
// AFTER moving
TestCell - 0x000000014e907190
<NSLayoutConstraint:0x60000089f1b0 'UIIBSystemGenerated' ...>
..
// ContentView
UIView - 0x000000014e9178e0
// Unchanged Donstraints between first wrapper view (= ContainerView) and ContentView
<NSLayoutConstraint:0x60000089fb10 V:|-(5)-[UIView:0x14e91d650] (active, names: '|':UIView:0x14e9178e0 )>
<NSLayoutConstraint:0x60000089fcf0 H:|-(5)-[UIView:0x14e91d650] (active, names: '|':UIView:0x14e9178e0 )>
<NSLayoutConstraint:0x60000089fd40 UIView:0x14e91d650.bottom == UIView:0x14e9178e0.bottom - 5 (active)>
<NSLayoutConstraint:0x60000089fde0 UIView:0x14e91d650.trailing == UIView:0x14e9178e0.trailing - 5 (active)>
UIView - 0x000000014e91d650 // ContainerView
// Constraints between first wrapper view (= ContainerView) and second wrapper view (= ClippingView)
<NSLayoutConstraint:0x60000089fe30 V:|-(0)-[UIView:0x14e91e770] (active, names: '|':UIView:0x14e91d650 )>
<NSLayoutConstraint:0x60000089fe80 H:|-(0)-[UIView:0x14e91e770] (active, names: '|':UIView:0x14e91d650 )>
<NSLayoutConstraint:0x60000089fed0 UIView:0x14e91e770.bottom == UIView:0x14e91d650.bottom (active)>
<NSLayoutConstraint:0x60000089ff20 UIView:0x14e91e770.trailing == UIView:0x14e91d650.trailing (active)>
UIView - 0x000000014e91e770
// New constraints between TitleLabel and ClippingView
<NSLayoutConstraint:0x60000088bc00 V:|-(10)-[UILabel:0x14e913580] (active, names: '|':UIView:0x14e91e770 )>
<NSLayoutConstraint:0x60000088b5c0 H:|-(10)-[UILabel:0x14e913580] (active, names: '|':UIView:0x14e91e770 )>
<NSLayoutConstraint:0x60000088be30 V:[UILabel:0x14e913580]-(10)-| (active, names: '|':UIView:0x14e91e770 )>
<NSLayoutConstraint:0x60000088be80 H:[UILabel:0x14e913580]-(10)-| (active, names: '|':UIView:0x14e91e770 )>
UILabel - 0x000000014e913580
可以看到,titleLabel 已从单元格 contentView 正确移动到 clippingView,同时将 titleLabel 和 contentView 之间的旧约束转换为 titleLabel 和 clippingView 之间的新约束.
例子:
// 10px leading margin between titleLabel and contentView
<NSLayoutConstraint:0x60000089f200 H:|-(10)-[UILabel:0x14e913580] (active, names: '|':UIView:0x14e9178e0 )>
// Removed and replaced by 10px leading margin between titleLabel and clippingView
<NSLayoutConstraint:0x60000088b5c0 H:|-(10)-[UILabel:0x14e913580] (active, names: '|':UIView:0x14e91e770 )>
约束错误
因此,NSLayoutConstraint:0x60000089f200 已被删除,并且在 AFTER 转储中不再可见。
但是,在运行项目时 Xcode 显示这个约束会导致错误:
2021-12-21 13:21:27.256146+0100 ConstraintsTest[21962:21447166] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x60000088b5c0 H:|-(10)-[UILabel:0x14e913580] (active, names: '|':UIView:0x14e91e770 )>",
"<NSLayoutConstraint:0x60000089fcf0 H:|-(5)-[UIView:0x14e91d650] (active, names: '|':UIView:0x14e9178e0 )>",
"<NSLayoutConstraint:0x60000089fe80 H:|-(0)-[UIView:0x14e91e770] (active, names: '|':UIView:0x14e91d650 )>",
"<NSLayoutConstraint:0x60000089f200 H:|-(10)-[UILabel:0x14e913580] (active, names: '|':UIView:0x14e91e770 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x60000088b5c0 H:|-(10)-[UILabel:0x14e913580] (active, names: '|':UIView:0x14e91e770 )>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
观察
这是NSLayoutConstraint:0x60000089f200 在第一个转储中的显示方式:
<NSLayoutConstraint:0x60000089f200 H:|-(10)-[UILabel:0x14e913580] (active, names: '|':UIView:0x14e9178e0 )>
==> 10px Spacing between the titleLabel and view `UIView:0x14e9178e0` (== contentView)
该约束不包含在第二个转储中,这是正确的,因为标签已移动到剪辑视图,因此该约束被标签和剪辑视图之间的新约束替换。
但是,错误消息中仍然包含约束。虽然对象地址还是一样,但是现在约束在标签和裁剪视图之间:
<NSLayoutConstraint:0x60000089f200 H:|-(10)-[UILabel:0x14e913580] (active, names: '|':UIView:0x14e91e770 )>
问题
这怎么可能?
- 如果转储中不再显示约束,为什么约束仍然存在?
- 怎么可能,约束第二项从内容视图更改为剪辑视图?
我认为我的代码有问题,但错误在哪里?或者这是 Xcode/iOS 中的一些错误?
【问题讨论】:
-
很难说不花很多时间查看您的扩展代码。但是,您的 xib 将标签作为单元格本身的子视图。使用新的
TestCell.xib进行快速测试 ... 添加标签并将其约束到单元格的contentView... 并且不再有约束冲突。 -
@DonMag 感谢您将我指向这个方向。
TextCell.xib似乎确实有问题。更换它可以解决问题。您是如何注意到这一点的?在将新创建的文件与 IB 中的现有文件进行比较时,我看不出有任何区别。直接查看 XIB 文件的 XML 代码时,在这两个文件中,标签都是contentView的子视图(而不是您指出的单元格本身)。那么,您究竟在哪个位置看到了什么错误?
标签: ios swift xcode constraints nslayoutconstraint