以编程方式移动约束 - Xcode 报告不存在约束的错误

Programmatically move contraints - Xcode reports error on not existing constraint

我创建了一个 UIView extension,它应该将子视图移动到另一个(子)视图,同时保持约束不变。子视图和视图之间的约束以及移动的子视图之间的约束。

虽然这在大多数情况下工作正常,但 Xcode 在使用扩展名 a UICollectionViewCell 时显示约束错误。奇怪的是,报错是针对一个不存在的约束。

很抱歉这个问题这么长,但是这个话题很复杂,我尽量提供尽可能多的内容。

演示项目可用于重现问题。

免责声明

这不是关于将具有约束的视图移动到新子视图是否是个好主意的问题。如下所述,Xcode 在不存在(不再存在)的约束上显示了一个非常奇怪的错误,问题是,这怎么可能。

问题描述

演示项目

我创建了一个演示项目并将其上传到同事的 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 和 [=24= 之间的旧约束时,titleLabel 已从单元格 contentView 正确移动到 clippingView ] 到 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 中的一些错误?

您的 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。我现在找不到文档,但我知道我之前遇到过类似的问题,更改内容视图的 Class 会导致各种问题。

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


编辑 - 一些注释...

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

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

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

我怀疑您的 moveSubviewsIntoView() 函数中的某些内容正在复制 not-installed 约束并 激活 它。

第二条评论 - 关于 Content View:

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

但是,如果我去 New File -> Cocoa Touch Class -> Subclass of: UICollectionViewCell 我检查 ☑️ 同时创建 XIB 文件, IB 没有给我 Content View.

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