UIStackView 隐藏视图和旋转时无法满足的布局
Posted
技术标签:
【中文标题】UIStackView 隐藏视图和旋转时无法满足的布局【英文标题】:UIStackView hiding views and unsatisfiable layout on rotation 【发布时间】:2017-12-04 02:02:42 【问题描述】:我有一个视图控制器,我想在其中隐藏某些子视图、标签等,具体取决于特征集合是什么。例如,我在横向压缩时要隐藏的一侧有标签,纵向压缩时要在顶部和底部隐藏标签。
因为我想隐藏界面的整个部分,听起来 UIStackView 会让这更容易。同样,我尝试在 Interface Builder / Xcode (9.1 9B55) 中而不是在代码中做尽可能多的事情,以尽量保持简单。
设计的主要元素是棋盘,它:
始终为 1:1 纵横比 在视图中尽可能大然后我想根据当前的特征集合移动和隐藏它周围的标签和其他项目。
我首先构建了一个水平堆栈视图、对齐方式和分布设置为“填充”。其中有两个项目,boardView(紫色)和 labelView(黄色)。
labelView 中有几个标签,在视图本身中设置了约束以用于这些标签的布局。
boardView 具有纵横比 1:1 (@1000) 的约束:
我为 Stack View 设置了以下约束,将其固定到超级视图(底部除外):
Safe Area.trailing = Stack View.trailing(@1000) Safe Area.leading = Stack View.leading (@1000) Safe Area.top = Stack View.top(@1000) Safe Area.bottom >= Stack View.bottom(@1000)我设置了这些约束以确保 boardView 永远不会溢出 superview:
boardView.width boardView.height然后我将这些约束设置为较低的优先级,这样,如果可能,宽度或高度将扩展以填充超级视图(但不会溢出,因为这些优先级低于上述优先级):
boardView.width = Safe Area.width (@250) boardView.height = 安全区域.height (@250)这一切似乎都很好。 Xcode 中没有错误,应用程序的行为与我预期的一样,纵向和横向(这些屏幕截图来自 iPhone 8 模拟器):
当我试图隐藏正确的视图时,问题就来了。当我们垂直处于常规模式时,我试图通过隐藏 nameView 来做到这一点。我选择nameView,点击Installed复选框旁边的+,选择hR:
然后取消选中 hR 的已安装框:
这在 Xcode 中看起来很棒。没有错误,IB 似乎在横向和纵向(隐藏了名称标签)中都显示了我所期望的所有视图:
到目前为止,一切都很好。事实上,当我运行它时,它最初看起来和我们预期的一样。如果我们在风景中启动模拟器,它看起来不错:
如果我们以纵向启动模拟器,它看起来不错:
那么问题是什么?好吧,一旦我们从纵向旋转到横向,布局就完全错误了:
同时,我们在控制台中得到一个 LayoutConstraints 错误:
2017-12-03 19:25:55.710381-0600 TestLayoutSIngleView[31439:3231004] [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 fixes it.
(
"<NSLayoutConstraint:0x60400009b8a0 UIView:0x7fe920603e90.width == UIView:0x7fe920603e90.height (active)>",
"<NSLayoutConstraint:0x60000009ced0 UIStackView:0x7fe92050bd70.leading == UILayoutGuide:0x6000001b5d20'UIViewSafeAreaLayoutGuide'.leading (active)>",
"<NSLayoutConstraint:0x60000009d100 UIStackView:0x7fe92050bd70.top == UILayoutGuide:0x6000001b5d20'UIViewSafeAreaLayoutGuide'.top (active)>",
"<NSLayoutConstraint:0x60000009d150 UILayoutGuide:0x6000001b5d20'UIViewSafeAreaLayoutGuide'.trailing == UIStackView:0x7fe92050bd70.trailing (active)>",
"<NSLayoutConstraint:0x60000009d1a0 UILayoutGuide:0x6000001b5d20'UIViewSafeAreaLayoutGuide'.bottom >= UIStackView:0x7fe92050bd70.bottom (active)>",
"<NSLayoutConstraint:0x60000009e5a0 'UISV-canvas-connection' UIStackView:0x7fe92050bd70.leading == UIView:0x7fe920603e90.leading (active)>",
"<NSLayoutConstraint:0x60000009ce30 'UISV-canvas-connection' UIStackView:0x7fe92050bd70.top == UIView:0x7fe920603e90.top (active)>",
"<NSLayoutConstraint:0x60000009e5f0 'UISV-canvas-connection' V:[UIView:0x7fe920603e90]-(0)-| (active, names: '|':UIStackView:0x7fe92050bd70 )>",
"<NSLayoutConstraint:0x60000009cde0 'UISV-canvas-connection' H:[UIView:0x7fe920603e90]-(0)-| (active, names: '|':UIStackView:0x7fe92050bd70 )>",
"<NSLayoutConstraint:0x60000009e7d0 'UIView-Encapsulated-Layout-Height' UIView:0x7fe92050b8f0.height == 375 (active)>",
"<NSLayoutConstraint:0x60000009e780 'UIView-Encapsulated-Layout-Width' UIView:0x7fe92050b8f0.width == 667 (active)>",
"<NSLayoutConstraint:0x60000009d060 'UIViewSafeAreaLayoutGuide-bottom' V:[UILayoutGuide:0x6000001b5d20'UIViewSafeAreaLayoutGuide']-(0)-| (active, names: '|':UIView:0x7fe92050b8f0 )>",
"<NSLayoutConstraint:0x60000009d010 'UIViewSafeAreaLayoutGuide-left' H:|-(0)-[UILayoutGuide:0x6000001b5d20'UIViewSafeAreaLayoutGuide'](LTR) (active, names: '|':UIView:0x7fe92050b8f0 )>",
"<NSLayoutConstraint:0x60000009d0b0 'UIViewSafeAreaLayoutGuide-right' H:[UILayoutGuide:0x6000001b5d20'UIViewSafeAreaLayoutGuide']-(0)-|(LTR) (active, names: '|':UIView:0x7fe92050b8f0 )>",
"<NSLayoutConstraint:0x60000009cfc0 'UIViewSafeAreaLayoutGuide-top' V:|-(0)-[UILayoutGuide:0x6000001b5d20'UIViewSafeAreaLayoutGuide'] (active, names: '|':UIView:0x7fe92050b8f0 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x60400009b8a0 UIView:0x7fe920603e90.width == UIView:0x7fe920603e90.height (active)>
我喜欢使用 UIStackView 来简化自动布局和隐藏视图的想法。但是,如果我在 Xcode 中看到的内容在编译和运行时不起作用,那么这似乎会很棘手。
我做错了什么(或者我遇到了 Xcode 错误)?
我创建了一个 GitHub 项目来演示上述内容: https://github.com/johnstewart/TestLayoutSIngleView
编辑......当我写完以上所有内容时,我突然想到,如果我们要在横向模式下隐藏这个视图,布局将无法满足......但我不是将此视图隐藏在横向中,仅纵向。出于某种原因,ios 在旋转之前对布局进行了更改。
如果我将它的优先级从 1000 设置为 750:
Safe Area.trailing = Stack View.trailing(@1000)...我在控制台上没有收到任何自动布局错误。但是,一旦旋转后,横向视图就根本没有正确的位置。
就好像堆栈视图在开始从纵向旋转到横向时立即缩小,并在旋转完成后使堆栈视图处于缩小状态,即使现在有足够的空间:
我真诚地希望我在这里遗漏了一些明显的东西......否则我看不出使用 UIStackView 卸载基于特征集合的视图是多么可行;这似乎是一个简单的示例(确实如此;我还有很多东西要添加!)
编辑:
我看到这种技术的 WWDC 视频来自 WWDC2017,Interface Builder 中的自动布局技术 (https://developer.apple.com/videos/play/wwdc2017/412/)。 29:00 开始演示。
但是,我错了,它是用于此的“已安装”属性。正如公认的答案所示,“隐藏”是这样做的方法。
【问题讨论】:
更新 20171205 - 更新到 Xcode 9.2 (9C40b) 并且行为相同。 【参考方案1】:我认为您可以在没有“已安装”属性的情况下实现您想要的: 只需为纵向模式设置“隐藏”:
【讨论】:
我一直在尝试解决这个问题,但它并没有真正解决我的问题。当您在 hR 上隐藏 nameView 时,您会收到有关 nameView 中项目约束的错误。您也可以将它们隐藏起来,但如果布局比这个(非常最小化的)示例更复杂,这似乎无法扩展。installed
属性的问题是在“卸载”控制器后无法获取leftView
。它已从 superview 中删除,并且控制器无法从 xib 加载它,因为它仅在 vc 生命周期开始时发生。解决方案是使用插座到leftView
和stackView
并根据屏幕方向在viewWillTransitionToSize:withTransitionCoordinator:
方法中调用[self.stackView removeArrangedSubview:self.leftView];
和[self.stackView addArrangedSubview:self.leftView];
。
我 swear 我看到这个“已安装”的技巧在某个 WWDC 视频中隐藏了一段 UIStackView,这就是为什么我认为这是“Apple 方式”处理删除 IB 中的视图。我一直在浏览所有 WWDC 视频,我可以找到该参考堆栈视图,但到目前为止我还没有找到它。
我找到了,WWDC 2017,Interface Builder 中的自动布局技术,29:00 开始。你是对的,乔萨德。他用来根据大小等级隐藏东西的属性确实是“隐藏的”而不是“安装的”。看起来,隐藏属性使包含的视图仍保留在 UIStackView 的排列子视图中。将您的答案标记为答案,对于没有在您的答案过期之前给予赏金,我深表歉意。
我会说这种技术,当我最初看到这个演示时,我很兴奋,但它的用处似乎有限......当我使用它来隐藏堆栈视图中的项目时,包括所有子视图等,我得到这些 hidden 项的各种缺少约束错误。 UIStackView 似乎没有像视频中暗示的那样处理从自动布局过程中删除这些内容。也许它适用于超级简单的设计,但我看不出这将如何适用于任何实际的应用程序。以上是关于UIStackView 隐藏视图和旋转时无法满足的布局的主要内容,如果未能解决你的问题,请参考以下文章