【问题标题】:Programmatically move contraints - Xcode reports error on not existing constraint以编程方式移动约束 - Xcode 报告不存在约束的错误
【发布时间】: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 转储视图和约束层次结构beforeafter,以将单元格内容(= 标签)移动到包装器视图中:

// 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,同时将 titleLabelcontentView 之间的旧约束转换为 titleLabelclippingView 之间的新约束.

例子:

// 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


【解决方案1】:

您的TestCell.xib 似乎缺少单元格的Content View

当我在 IB 中打开 TestCell.xib 时,我看到了这个:

当我创建一个新的空用户界面文件 (.xib) 并将一个集合视图单元格拖入其中时,它开始如下:

然后,添加标签后:

检查 xml 源,TestCell.xib 显示:

<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">

应该在哪里:

<collectionViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="y54-cV-vD7">

因此,不知何故,您的 contentView 已更改为默认的 UIView。我现在找不到文档,但我知道在更改内容视图的类会导致各种问题之前,我遇到过类似的问题。

我什至不能让它在 IB 中改变,所以也许 Xcode 不再允许它避免这个问题。


编辑 - 情侣笔记...

第一条评论:您的TestCell.xib 有第二个底部约束。这并没有错,例如,有多个“相同项目”约束具有不同优先级的原因有很多。

在这种情况下,该约束未已安装,因此我们预计它不会影响任何内容。

但是,如果我将其标记为 Placeholder ☑️ 在构建时删除,或者如果我将其删除,则不再有约束冲突。

我怀疑您的 moveSubviewsIntoView() 函数中的某些内容正在复制该未安装的约束并激活它。

第二条评论——关于Content View

如果我创建一个 xib,并将UICollectionViewCell 拖入其中,IB 会自动为其提供预期的Content View

但是,如果我去New File -&gt; Cocoa Touch Class -&gt; Subclass of: UICollectionViewCell 并且我检查 ☑️ 同时创建 XIB 文件,IB 不会给我Content View .

令我惊讶的是,这不会造成问题!我在 Apple 文档(和其他地方)中阅读的所有内容都强化了仅将子视图添加到单元格的内容视图(与表格视图单元格相同)的指令。所以,看起来真的很奇怪......这是一个“错误”吗?我不是 Apple 工程师,所以我不知道 - 也许我只是遗漏了一些信息(很可能甚至很可能是这种情况)。

【讨论】:

  • 感谢您的详细解答!它很奇怪,但我无法重现这些步骤。当我使用新的 XIB 创建一个新的 UICollectionViewCell 子类时,它包含 &lt;view key="contentView"... 而没有 &lt;collectionViewCellContentView key="contentView"。 IB 也不会在新的 XIB 文件中显示 contentView。我使用的是 Xcode 13.1,请问您使用的是哪个版本?无论如何:似乎很清楚问题的根源是 XIB 文件的一些奇怪的奇怪问题。非常感谢您的宝贵时间和帮助!
  • @AndreiHerford - 我也在使用 13.1 --- 但是,看起来我每天都在学习新的东西......我将用更多的 cmets 编辑我的答案......
  • @AndreiHerford - 请参阅我的答案的 编辑。可能比回答问题所需的信息要多,但是……嗯,现在是假期:)
猜你喜欢
  • 2018-11-18
  • 1970-01-01
  • 1970-01-01
  • 2014-08-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多