原子设计和系统设计在设计中很流行:这是指从控件到屏幕的所有组件都由组件组成的情况。 程序员编写单独的控件并不难,但是如何处理整个屏幕呢?
让我们看一下新年的例子:
- 让我们把所有东西粘在一起;
- 分为控制器:选择导航,模板和内容;
- 在其他屏幕上重用代码。

一堆
这个新年的屏幕上讨论了比萨店的特殊营业时间。 这非常简单,因此将其设置为控制器将不会构成犯罪:

但是 下次,当我们需要类似的屏幕时,我们将不得不再次重复所有操作,然后对所有屏幕进行相同的更改。 好吧,没有修改就不会发生。
因此,将其划分为多个部分并用于其他屏幕更为合理。 我强调了三个:
- 导航
- 在屏幕底部具有内容区域和操作位置的模板,
- 中心的独特内容。
在其自己的UIViewController
选择每个部分。
集装箱导航
导航容器最引人注目的示例是UINavigationController
和UITabBarController
。 每个控件在其自己的控件下在屏幕上占据一个小条,并为另一个UIViewController
保留剩余空间。
在我们的情况下,将为所有模式屏幕提供一个容器,其中只有一个关闭按钮。
有什么意义?如果要向右移动按钮,则只需要在一个控制器中进行更改即可。
或者,如果我们决定用特殊的动画显示所有模态窗口,并通过滑动以交互方式关闭,如AppStore故事卡中所示。 然后,仅需要为此控制器设置UIViewControllerTransitioningDelegate
。

您可以使用container view
来分隔控制器:它将在父container view
中创建一个UIView
并将子控制器的UIView
插入其中。

将container view
拉伸到屏幕边缘。 Safe area
将自动应用于子控制器:

屏幕图案
内容在屏幕上显而易见:图片,标题,文本。 该按钮似乎是其中的一部分,但内容在不同的iPhone上是动态的,并且该按钮是固定的。 可以看到两个任务不同的系统:一个显示内容,另一个嵌入并对齐内容。 它们应分为两个控制器。

第一个负责屏幕的布局:内容应居中,并且按钮应钉在屏幕底部。 第二个将绘制内容。

没有模板,所有控制器都是相似的,但是元素却在起舞。
最后一个屏幕上的按钮不同-它取决于内容。 委派将帮助解决问题:控制器模板将从内容中请求控件,并将其显示在其UIStackView
。
可以通过相关对象将按钮附加到控制器。 它们的IBOutlet
和IBAction
存储在内容控制器中,只是元素未添加到层次结构中。

您可以在UIStoryboardSegue
的准备阶段从内容中获取元素并将其添加到模板中:
在设置器中,我们向UIStackView
添加控件:
结果,我们的控制器分为三个部分:导航,模板和内容。 在图片中,所有container view
显示为灰色:

动态控制器尺寸
内容控制器具有自己的最大大小,受内部constraints
。
Container view
基于自动调整Autoresizing mask
添加了Autoresizing mask
,它们与内容的内部尺寸冲突。 该问题已在代码中解决:在内容控制器中,您需要从Autoresizing mask
指示它不受存储库的影响:

Interface Builder还有两个步骤:
步骤1.为UIView
指定Intrinsic size
。 实际价值将在发布后显示,但现在我们将放置所有合适的价值。

步骤2.对于内容控制器,指定Simulated Size
。 它可能与过去的大小不符。
出现布局错误,该怎么办?当AutoLayout
无法找出如何分解当前大小的元素时,会发生错误。
通常,在更改常数的优先级后问题就消失了。 您需要放下它们,以便其中一个UIView
可以比其他UIView
进行更多的扩展/收缩。
我们分成几部分并编写代码
我们将控制器分为几个部分,但到目前为止,我们无法重用它们, UIStoryboard
的接口很难分部分提取。 如果我们需要将一些数据传输到内容,那么我们将不得不在整个层次结构中加以处理。 它应该是另一种方式:首先获取内容,对其进行配置,然后将其包装在必要的容器中。 像灯泡。
我们的方法出现了三个任务:
- 将每个控制器分成自己的
UIStoryboard
。 - 拒绝
container view
,将控制器添加到代码容器中。 - 绑回去。
共享UIStoryboard
您需要创建两个附加的UIStoryboard
然后将导航控制器和模板控制器复制粘贴到其中。 Embed segue
将中断,但是将传输具有已配置约束的container view
。 必须保存约束,并且必须用常规UIView
替换container view
。
最简单的方法是在UIStoryboard代码中更改Container视图的类型。 我们将控制器设置is initial view controller
,然后将UIStoryboard
称为控制器。
我们从UIStoryboard加载控制器。如果控制器的名称与UIStoryboard
的名称匹配,则可以将下载内容包装在一种方法中,该方法本身将找到所需的文件:
protocol Storyboardable { } extension Storyboardable where Self: UIViewController { static func instantiateInitialFromStoryboard() -> Self { let controller = storyboard().instantiateInitialViewController() return controller! as! Self } static func storyboard(fileName: String? = nil) -> UIStoryboard { let storyboard = UIStoryboard(name: fileName ?? storyboardIdentifier, bundle: nil) return storyboard } static var storyboardIdentifier: String { return String(describing: self) } static var storyboardName: String { return storyboardIdentifier } }
如果在.xib
描述了控制器,则标准构造函数将加载而不会发生此类舞动。 xi, .xib
只能包含一个控制器,通常这还不够:在一个好的情况下,一个屏幕包含多个屏幕。 因此,我们使用UIStoryborad
,很容易将屏幕分成几部分。
在代码中添加控制器
为了使控制器正常工作,我们需要其生命周期中的所有方法: will/did-appear/disappear
。
为了正确显示,您需要调用5个步骤:
willMove(toParent parent: UIViewController?) addChild(_ childController: UIViewController) addSubview(_ subivew: UIView) layout didMove(toParent parent: UIViewController?)
苹果建议将代码减少到4个步骤,因为addChild()
本身将调用willMove(toParent)
。 总结:
addChild(_ childController: UIViewController) addSubview(_ subivew: UIView) layout didMove(toParent parent: UIViewController?)
为简单起见,您可以将其全部包装在extension
。 对于我们的情况,我们需要一个带有insertSubview()
的版本。
extension UIViewController { func insertFullframeChildController(_ childController: UIViewController, toView: UIView? = nil, index: Int) { let containerView: UIView = toView ?? view addChild(childController) containerView.insertSubview(childController.view, at: index) containerView.pinToBounds(childController.view) childController.didMove(toParent: self) } }
要删除,您需要执行相同的步骤,只需要设置nil
代替父控制器。 现在removeFromParent()
调用didMove(toParent: nil)
,并且不需要布局。 缩短的版本非常不同:
willMove(toParent: nil) view.removeFromSuperview() removeFromParent()
布局图
设置约束
为了正确设置控制器的大小,我们将使用AutoLayout
。 我们需要将所有方面都牢牢钉在身边:
extension UIView { func pinToBounds(_ view: UIView) { view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ view.topAnchor.constraint(equalTo: topAnchor), view.bottomAnchor.constraint(equalTo: bottomAnchor), view.leadingAnchor.constraint(equalTo: leadingAnchor), view.trailingAnchor.constraint(equalTo: trailingAnchor) ]) } }
在代码中添加一个子控制器
现在可以将所有内容组合在一起:
由于使用频率高,我们可以将所有这些包装在extension
:
模板控制器也需要类似的方法。 prepare(for segue:)
过去是在prepare(for segue:)
设置的prepare(for segue:)
,但是现在您可以将其绑定到控制器的embed方法中:
创建一个控制器如下所示:
将新屏幕连接到模板很简单:
- 删除与内容无关的内容;
- 通过实现OnboardingViewControllerDatasource协议指定操作按钮;
- 编写链接模板和内容的方法。
有关容器的更多信息
状态栏
通常, status bar
的可见性必须由具有内容的控制器而不是容器来控制。 有两个property
:
使用这些property
您可以创建一个控制器链,后者将负责显示status bar
。
安全区
如果容器按钮与内容重叠,则应增加safeArea
区域。 这可以通过以下代码完成:为子控制器设置additinalSafeAreaInsets
。 您可以从embedController()
调用它:
private func addSafeArea(to controller: UIViewController) { if #available(iOS 11.0, *) { let buttonHeight = CGFloat(30) let topInset = UIEdgeInsets(top: buttonHeight, left: 0, bottom: 0, right: 0) controller.additionalSafeAreaInsets = topInset } }
如果在顶部添加30个点,则该按钮将停止重叠内容,并且safeArea
将占据绿色区域:

保证金。 保留超级视图边距
控制器具有标准margins
。 通常,它们从屏幕的每一侧等于16点,仅在加大尺寸上为20点。
根据margins
您可以创建常量,不同iPhone的边缘缩进量将有所不同:

当我们将一个UIView
放入另一个UIView
, margins
减半:降至8点。 为防止这种情况,您需要包括Preserve superview margins
。 然后,子级UIView
的margins
将等于父级UIView
的margins
。 适用于全屏容器。
结束
容器控制器是一个强大的工具。 它们简化了代码,分离了任务,并且可以重复使用。 您可以以任何方式编写嵌套控制器:在UIStoryboard
,在UIStoryboard
中或仅在代码中。 最重要的是,它们易于创建且易于使用。
→ GitHub上一篇文章的示例
您是否有值得从中制作模板的屏幕? 分享评论!