菜鸟教程小白 发表于 2022-12-11 18:17:28

iOS - 在 Swift 3 中以编程方式删除和激活新约束时布局损坏


                                            <p><p>在 Swift 3 中以编程方式处理约束让我非常沮丧。在非常基本的层面上,我的应用程序显示了许多具有初始约束的 View ,然后在旋转时应用新约束,以允许 View 调整大小并根据需要重新定位。不幸的是,这远非易事,因为我还是 iOS 开发和 Swift 的新手。我花了很多时间尝试 StackOverflow 和其他地方提供的许多不同的解决方案,但我一直得到相同的结果(最后详述)。</p>

<p> <a href="/image/LtA98.png" rel="noreferrer noopener nofollow"><img src="/image/LtA98.png" alt="Storyboard image"/></a> </p>

<p>我有一个 ViewController (我们称之为“主视图 Controller ”),它的 Root View 包含两个 subview , ViewA 和 ViewB 容器。 Root View 的背景颜色为粉红色。</p>

<p> ViewA 内部包含一个标签,垂直和水平居中,以及橙色背景色。 View A 有 4 个约束 - 、、 和 。</p>

<p>View B Container 最初没有内容。它有 4 个约束 - 、、 和 。</p>

<hr/>

<p>我还有另一个 ViewController (我们称之为“ ViewBViewController ”)驱动 ViewB 容器的内容。为了简单起见,这只是一个没有自定义逻辑的默认 ViewController 。 View B View Controller 的 Root View 包含一个 subviewView B。</p>

<p> ViewB 与上面的 ViewA 几乎相同 - 单个标签垂直和水平居中,背景颜色为蓝色。 ViewB 有 4 个约束 - 、、 和 。</p>

<hr/>

<p>在主视图 Controller 类中,我维护了对 ViewA 和 ViewB Container 的 IBOutlet 引用,以及上面提到的它们各自的约束。在下面的代码中,主视图 Controller 实例化 ViewBViewController 并将后续 View 添加到 ViewB 容器,应用灵活的宽度/高度自动调整大小掩码以确保其填充可用空间。然后它会触发对内部 _layoutContainers() 函数的调用,该函数根据设备的方向执行许多约束修改操作。当前的实现如下:</p>

<ul>
<li>从 ViewA 中移除已知约束</li>
<li>从 View B Container 中移除已知约束</li>
<li>根据设备方向,根据特定设计激活 View A 和 View B Container 的新约束(在下面的代码注释中有详细说明)</li>
<li>针对所有 View 触发 updateConstraintsIfNeeded() 和 layoutIfNeeded()</li>
</ul>

<p>当发生resize事件时,代码允许viewWillTransition()触发,然后在完成回调中调用_layoutContainers()函数,使设备处于一个新的状态并可以遵循必要的逻辑路径。</p>

<p>整个主视图 Controller 单元如下:</p>

<pre><code>import UIKit

class ViewController: UIViewController {

    // MARK: Variables

    @IBOutlet weak var _viewAView: UIView!

    @IBOutlet weak var _viewALeadingConstraint: NSLayoutConstraint!
    @IBOutlet weak var _viewATopConstraint: NSLayoutConstraint!
    @IBOutlet weak var _viewATrailingConstraint: NSLayoutConstraint!
    @IBOutlet weak var _viewABottomConstraint: NSLayoutConstraint!

    @IBOutlet weak var _viewBContainerView: UIView!

    @IBOutlet weak var _viewBContainerWidthConstraint: NSLayoutConstraint!
    @IBOutlet weak var _viewBContainerHeightConstraint: NSLayoutConstraint!
    @IBOutlet weak var _viewBContainerTopConstraint: NSLayoutConstraint!
    @IBOutlet weak var _viewBContainerLeadingConstraint: NSLayoutConstraint!

    // MARK: UIViewController Overrides

    override func viewDidLoad() {
      super.viewDidLoad()

      // Instantiate View B&#39;s controller
      let viewBViewController = self.storyboard!.instantiateViewController(withIdentifier: &#34;ViewBViewController&#34;)
      self.addChildViewController(viewBViewController)

      // Instantiate and add View B&#39;s new subview
      let view = viewBViewController.view
      self._viewBContainerView.addSubview(view!)
      view!.frame = self._viewBContainerView.bounds
      view!.autoresizingMask = [.flexibleWidth, .flexibleHeight]

      viewBViewController.didMove(toParentViewController: self)

      self._layoutContainers()
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
      super.viewWillTransition(to: size, with: coordinator)

      coordinator.animate(alongsideTransition: nil, completion: { _ in
            self._layoutContainers()
      })
    }

    // MARK: Internal

    private func _layoutContainers() {

      // Remove View A constraints
      self._viewAView.removeConstraints([
            self._viewALeadingConstraint,
            self._viewATopConstraint,
            self._viewATrailingConstraint,
            self._viewABottomConstraint,
      ])

      // Remove View B Container constraints
      var viewBContainerConstraints: = [
            self._viewBContainerTopConstraint,
            self._viewBContainerLeadingConstraint,
      ]

      if(self._viewBContainerWidthConstraint != nil) {
            viewBContainerConstraints.append(self._viewBContainerWidthConstraint)
      }
      if(self._viewBContainerHeightConstraint != nil) {
            viewBContainerConstraints.append(self._viewBContainerHeightConstraint)
      }

      self._viewBContainerView.removeConstraints(viewBContainerConstraints)


      // Portrait:
      // View B - 16/9 and to bottom of screen
      // View A - anchored to top and filling the remainder of the vertical space

      if(UIDevice.current.orientation != .landscapeLeft &amp;&amp; UIDevice.current.orientation != .landscapeRight) {

            let viewBWidth = self.view.frame.width
            let viewBHeight = viewBWidth / (16/9)
            let viewAHeight = self.view.frame.height - viewBHeight

            // View A - anchored to top and filling the remainder of the vertical space
            NSLayoutConstraint.activate([
                self._viewAView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
                self._viewAView.topAnchor.constraint(equalTo: self.view.topAnchor),
                self._viewAView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
                self._viewAView.bottomAnchor.constraint(equalTo: self._viewBContainerView.topAnchor),
            ])

            // View B - 16/9 and to bottom of screen
            NSLayoutConstraint.activate([
                self._viewBContainerView.widthAnchor.constraint(equalToConstant: viewBWidth),
                self._viewBContainerView.heightAnchor.constraint(equalToConstant: viewBHeight),
                self._viewBContainerView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: viewAHeight),
                self._viewBContainerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            ])
      }

      // Landscape:
      // View B - 2/3 of screen on left
      // View A - 1/3 of screen on right
      else {
            let viewBWidth = self.view.frame.width * (2/3)

            // View B - 2/3 of screen on left
            NSLayoutConstraint.activate([
                self._viewBContainerView.widthAnchor.constraint(equalToConstant: viewBWidth),
                self._viewBContainerView.heightAnchor.constraint(equalToConstant: self.view.frame.height),
                self._viewBContainerView.topAnchor.constraint(equalTo: self.view.topAnchor),
                self._viewBContainerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            ])

            // View A - 1/3 of screen on right
            NSLayoutConstraint.activate([
                self._viewAView.leadingAnchor.constraint(equalTo: self._viewBContainerView.trailingAnchor),
                self._viewAView.topAnchor.constraint(equalTo: self.view.topAnchor),
                self._viewAView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
                self._viewAView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
            ])
      }

      // Fire off constraints and layout update functions

      self.view.updateConstraintsIfNeeded()
      self._viewAView.updateConstraintsIfNeeded()
      self._viewBContainerView.updateConstraintsIfNeeded()

      self.view.layoutIfNeeded()
      self._viewAView.layoutIfNeeded()
      self._viewBContainerView.layoutIfNeeded()
    }
}
</code></pre>

<hr/>

<p>我的问题是,虽然应用程序的初始加载显示了预期的结果( ViewB 保持 16/9 比例并位于屏幕底部,但 ViewA 占用了剩余空间):</p>

<p> <a href="/image/DCwP4.png" rel="noreferrer noopener nofollow"><img src="/image/DCwP4.png" alt="Successful display"/></a> </p>

<p>任何后续的旋转都会完全破坏 View 并且不会恢复:</p>

<p> <a href="/image/PnEyN.png" rel="noreferrer noopener nofollow"><img src="/image/PnEyN.png" alt="Failed display in landscape"/></a> </p>

<p> <a href="/image/v4KOy.png" rel="noreferrer noopener nofollow"><img src="/image/v4KOy.png" alt="Failed display in portrait"/></a> </p>

<p>此外,一旦加载应用程序,就会引发以下约束警告:</p>

<pre><code>TestResize Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don&#39;t want.
    Try this:
      (1) look at each constraint and try to figure out which you don&#39;t expect;
      (2) find the code that added the unwanted constraint or constraints and fix it.
(
    &#34;&lt;_UILayoutSupportConstraint:0x600000096c60 _UILayoutGuide:0x7f8d4f414110.height == 0   (active)&gt;&#34;,
    &#34;&lt;_UILayoutSupportConstraint:0x600000090ae0 V:|-(0)-   (active, names: &#39;|&#39;:UIView:0x7f8d4f40f9e0 )&gt;&#34;,
    &#34;&lt;NSLayoutConstraint:0x600000096990 V:-(0)-   (active)&gt;&#34;,
    &#34;&lt;NSLayoutConstraint:0x608000094e10 V:|-(456.062)-   (active, names: &#39;|&#39;:UIView:0x7f8d4f40f9e0 )&gt;&#34;
)

Will attempt to recover by breaking constraint
&lt;NSLayoutConstraint:0x600000096990 V:-(0)-   (active)&gt;

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in &lt;UIKit/UIView.h&gt; may also be helpful.



TestResize Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don&#39;t want.
    Try this:
      (1) look at each constraint and try to figure out which you don&#39;t expect;
      (2) find the code that added the unwanted constraint or constraints and fix it.
(
    &#34;&lt;NSLayoutConstraint:0x600000096940 UIView:0x7f8d4f413e60.leading == UIView:0x7f8d4f40f9e0.leadingMargin   (active)&gt;&#34;,
    &#34;&lt;NSLayoutConstraint:0x608000094e60 H:|-(0)-   (active, names: &#39;|&#39;:UIView:0x7f8d4f40f9e0 )&gt;&#34;
)

Will attempt to recover by breaking constraint
&lt;NSLayoutConstraint:0x600000096940 UIView:0x7f8d4f413e60.leading == UIView:0x7f8d4f40f9e0.leadingMargin   (active)&gt;

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in &lt;UIKit/UIView.h&gt; may also be helpful.



TestResize Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don&#39;t want.
    Try this:
      (1) look at each constraint and try to figure out which you don&#39;t expect;
      (2) find the code that added the unwanted constraint or constraints and fix it.
(
    &#34;&lt;_UILayoutSupportConstraint:0x600000096d50 _UILayoutGuide:0x7f8d4f40f4b0.height == 0   (active)&gt;&#34;,
    &#34;&lt;_UILayoutSupportConstraint:0x600000096d00 _UILayoutGuide:0x7f8d4f40f4b0.bottom == UIView:0x7f8d4f40f9e0.bottom   (active)&gt;&#34;,
    &#34;&lt;NSLayoutConstraint:0x600000092e30 V:-(0)-   (active)&gt;&#34;,
    &#34;&lt;NSLayoutConstraint:0x608000092070 UIView:0x7f8d4f40fd90.bottom == UIView:0x7f8d4f413e60.top   (active)&gt;&#34;,
    &#34;&lt;NSLayoutConstraint:0x608000094e10 V:|-(456.062)-   (active, names: &#39;|&#39;:UIView:0x7f8d4f40f9e0 )&gt;&#34;,
    &#34;&lt;NSLayoutConstraint:0x600000096e40 &#39;UIView-Encapsulated-Layout-Height&#39; UIView:0x7f8d4f40f9e0.height == 667   (active)&gt;&#34;
)

Will attempt to recover by breaking constraint
&lt;NSLayoutConstraint:0x600000092e30 V:-(0)-   (active)&gt;

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in &lt;UIKit/UIView.h&gt; may also be helpful.



TestResize Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don&#39;t want.
    Try this:
      (1) look at each constraint and try to figure out which you don&#39;t expect;
      (2) find the code that added the unwanted constraint or constraints and fix it.
(
    &#34;&lt;_UILayoutSupportConstraint:0x600000096c60 _UILayoutGuide:0x7f8d4f414110.height == 20   (active)&gt;&#34;,
    &#34;&lt;_UILayoutSupportConstraint:0x600000090ae0 V:|-(0)-   (active, names: &#39;|&#39;:UIView:0x7f8d4f40f9e0 )&gt;&#34;,
    &#34;&lt;NSLayoutConstraint:0x600000096850 V:-(0)-   (active)&gt;&#34;,
    &#34;&lt;NSLayoutConstraint:0x608000093b50 V:|-(0)-   (active, names: &#39;|&#39;:UIView:0x7f8d4f40f9e0 )&gt;&#34;
)

Will attempt to recover by breaking constraint
&lt;NSLayoutConstraint:0x600000096850 V:-(0)-   (active)&gt;

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in &lt;UIKit/UIView.h&gt; may also be helpful.
</code></pre>

<p>感谢您阅读到这里!肯定有人遇到过(并希望解决)这个或类似的问题。任何帮助将不胜感激!</p></p>
                                    <br><hr><h1><strong>Best Answer-推荐答案</ strong></h1><br>
                                            <p><p>与其尝试添加和删除约束,不如考虑调整优先级来转换您的 View 。</p>

<p>所以对于您来说,默认布局有一个优先级为 900 的约束。然后添加第二个优先级为 1 的冲突约束。现在要切换显示模式,只需将第二个约束优先级移动到 900 以上,然后返回到下方以反转。只需更改优先级,即可在 Interface Builder 中轻松测试所有内容。</p>

<p>您还可以将更改放在动画 block 中以获得良好的平滑过渡。</p>

<p>-</p>

<p>要考虑使用的另一件事是尺寸等级。使用它,您可以指定特定约束仅适用于某些方向,这样您就可以完全“免费”获得所需的行为,只需在 IB 中进行设置。</p></p>
                                   
                                                <p style="font-size: 20px;">关于iOS - 在 Swift 3 中以编程方式删除和激活新约束时布局损坏,我们在Stack Overflow上找到一个类似的问题:
                                                        <a href="https://stackoverflow.com/questions/45096359/" rel="noreferrer noopener nofollow" style="color: red;">
                                                                https://stackoverflow.com/questions/45096359/
                                                        </a>
                                                </p>
                                       
页: [1]
查看完整版本: iOS - 在 Swift 3 中以编程方式删除和激活新约束时布局损坏