草庐IT

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

coder 2024-01-29 原文

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

我有一个 View Controller (我们称之为“主视图 Controller ”),其 Root View 包含两个 subview , View A 和 View B 容器。 Root View 具有粉红色背景色。

View A 内部包含单个标签,垂直和水平居中,以及橙色背景色。 View A 有 4 个约束 - [Leading Space to Superview]、[Top Space to Top Layout Guide]、[Trailing Space to Superview] 和 [Bottom Space to Bottom Layout Guide]。

View B 容器最初没有内容。它有 4 个约束 - [宽度等于 240]、[高度等于 128]、[Leading Space to Superview] 和 [Leading Space to Superview]。


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

View B 与上面的 View A 几乎相同 - 单个标签垂直和水平居中,背景色为蓝色。 View B 有 4 个约束 - [Leading Space to Superview]、[Top Space to Superview]、[Trailing Space to Superview] 和 [Bottom Space to Superview]。


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

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

当调整大小事件发生时,代码允许 viewWillTransition() 触发,然后在完成回调中调用 _layoutContainers() 函数,以便设备处于新状态并可以遵循必要的逻辑路径。

整个主视图 Controller 单元如下:

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's controller
        let viewBViewController = self.storyboard!.instantiateViewController(withIdentifier: "ViewBViewController")
        self.addChildViewController(viewBViewController)

        // Instantiate and add View B'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: [NSLayoutConstraint] = [
            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 && 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()
    }
}

我的问题是,尽管应用程序的初始加载显示了预期的结果( View B 保持 16/9 的比例并位于屏幕底部, View A 占据了剩余空间):

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

此外,一旦应用程序加载,就会抛出以下约束警告:

TestResize[1794:51030] [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. 
(
    "<_UILayoutSupportConstraint:0x600000096c60 _UILayoutGuide:0x7f8d4f414110.height == 0   (active)>",
    "<_UILayoutSupportConstraint:0x600000090ae0 V:|-(0)-[_UILayoutGuide:0x7f8d4f414110]   (active, names: '|':UIView:0x7f8d4f40f9e0 )>",
    "<NSLayoutConstraint:0x600000096990 V:[_UILayoutGuide:0x7f8d4f414110]-(0)-[UIView:0x7f8d4f413e60]   (active)>",
    "<NSLayoutConstraint:0x608000094e10 V:|-(456.062)-[UIView:0x7f8d4f413e60]   (active, names: '|':UIView:0x7f8d4f40f9e0 )>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600000096990 V:[_UILayoutGuide:0x7f8d4f414110]-(0)-[UIView:0x7f8d4f413e60]   (active)>

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



TestResize[1794:51030] [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:0x600000096940 UIView:0x7f8d4f413e60.leading == UIView:0x7f8d4f40f9e0.leadingMargin   (active)>",
    "<NSLayoutConstraint:0x608000094e60 H:|-(0)-[UIView:0x7f8d4f413e60]   (active, names: '|':UIView:0x7f8d4f40f9e0 )>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600000096940 UIView:0x7f8d4f413e60.leading == UIView:0x7f8d4f40f9e0.leadingMargin   (active)>

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



TestResize[1794:51030] [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. 
(
    "<_UILayoutSupportConstraint:0x600000096d50 _UILayoutGuide:0x7f8d4f40f4b0.height == 0   (active)>",
    "<_UILayoutSupportConstraint:0x600000096d00 _UILayoutGuide:0x7f8d4f40f4b0.bottom == UIView:0x7f8d4f40f9e0.bottom   (active)>",
    "<NSLayoutConstraint:0x600000092e30 V:[UIView:0x7f8d4f40fd90]-(0)-[_UILayoutGuide:0x7f8d4f40f4b0]   (active)>",
    "<NSLayoutConstraint:0x608000092070 UIView:0x7f8d4f40fd90.bottom == UIView:0x7f8d4f413e60.top   (active)>",
    "<NSLayoutConstraint:0x608000094e10 V:|-(456.062)-[UIView:0x7f8d4f413e60]   (active, names: '|':UIView:0x7f8d4f40f9e0 )>",
    "<NSLayoutConstraint:0x600000096e40 'UIView-Encapsulated-Layout-Height' UIView:0x7f8d4f40f9e0.height == 667   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600000092e30 V:[UIView:0x7f8d4f40fd90]-(0)-[_UILayoutGuide:0x7f8d4f40f4b0]   (active)>

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



TestResize[1794:51030] [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. 
(
    "<_UILayoutSupportConstraint:0x600000096c60 _UILayoutGuide:0x7f8d4f414110.height == 20   (active)>",
    "<_UILayoutSupportConstraint:0x600000090ae0 V:|-(0)-[_UILayoutGuide:0x7f8d4f414110]   (active, names: '|':UIView:0x7f8d4f40f9e0 )>",
    "<NSLayoutConstraint:0x600000096850 V:[_UILayoutGuide:0x7f8d4f414110]-(0)-[UIView:0x7f8d4f40fd90]   (active)>",
    "<NSLayoutConstraint:0x608000093b50 V:|-(0)-[UIView:0x7f8d4f40fd90]   (active, names: '|':UIView:0x7f8d4f40f9e0 )>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600000096850 V:[_UILayoutGuide:0x7f8d4f414110]-(0)-[UIView:0x7f8d4f40fd90]   (active)>

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

感谢您阅读到这里!肯定有人遇到(并希望解决)这个或类似的问题。任何帮助将不胜感激!

最佳答案

与其尝试添加和删除约束,不如考虑调整优先级来转换 View 。

所以默认布局有一个优先级为 900 的约束。然后添加第二个优先级为 1 的冲突约束。现在要切换显示模式,只需将第二个约束优先级向上移动到 900 以上,然后返回下方以反转。只需更改优先级,即可轻松在 Interface Builder 中对其进行全部测试。

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

-

要考虑使用的另一件事是尺寸等级。使用它,您可以指定特定约束仅适用于特定方向,因此您可能完全“免费”获得所需的行为,只需在 IB 中全部设置即可。

关于iOS - 在 Swift 3 中以编程方式删除和激活新约束时布局损坏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45096359/

有关iOS - 在 Swift 3 中以编程方式删除和激活新约束时布局损坏的更多相关文章

  1. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

    我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  4. ruby - 我可以使用 Ruby 从 CSV 中删除列吗? - 2

    查看Ruby的CSV库的文档,我非常确定这是可能且简单的。我只需要使用Ruby删除CSV文件的前三列,但我没有成功运行它。 最佳答案 csv_table=CSV.read(file_path_in,:headers=>true)csv_table.delete("header_name")csv_table.to_csv#=>ThenewCSVinstringformat检查CSV::Table文档:http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV/Table.html

  5. ruby - 我可以使用 aws-sdk-ruby 在 AWS S3 上使用事务性文件删除/上传吗? - 2

    我发现ActiveRecord::Base.transaction在复杂方法中非常有效。我想知道是否可以在如下事务中从AWSS3上传/删除文件:S3Object.transactiondo#writeintofiles#raiseanexceptionend引发异常后,每个操作都应在S3上回滚。S3Object这可能吗?? 最佳答案 虽然S3API具有批量删除功能,但它不支持事务,因为每个删除操作都可以独立于其他操作成功/失败。该API不提供任何批量上传功能(通过PUT或POST),因此每个上传操作都是通过一个独立的API调用完成的

  6. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  7. ruby-on-rails - 正确的 Rails 2.1 做事方式 - 2

    question的一些答案关于redirect_to让我想到了其他一些问题。基本上,我正在使用Rails2.1编写博客应用程序。我一直在尝试自己完成大部分工作(因为我对Rails有所了解),但在需要时会引用Internet上的教程和引用资料。我设法让一个简单的博客正常运行,然后我尝试添加评论。靠我自己,我设法让它进入了可以从script/console添加评论的阶段,但我无法让表单正常工作。我遵循的其中一个教程建议在帖子Controller中创建一个“评论”操作,以添加评论。我的问题是:这是“标准”方式吗?我的另一个问题的答案之一似乎暗示应该有一个CommentsController参

  8. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  9. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  10. ruby - nanoc 和多种布局 - 2

    是否可以为特定(或所有)项目使用多个布局?例如,我有几个项目,我想对其应用两种不同的布局。一个是绿色的,一个是蓝色的(但是)。我想将它们编译到我的输出目录中的两个不同文件夹中(例如v1和v2)。我一直在玩弄规则和编译block,但我不知道这是怎么回事。因为,每个项目在编译过程中只编译一次,我不能告诉nanoc第一次用layout1编译,第二次用layout2编译。我试过这样的东西,但它导致输出文件损坏。compile'*'doifitem.binary?#don’tfilterbinaryitemselsefilter:erblayout'layout1'layout'layout2'

随机推荐