如何在Xcode中使用Nibs和Coordinators代替Storyboard

Xcode中的情节提要板起初很方便,但是随着应用程序的增长,需要维护或您与团队一起工作,它们会带来很多伤害。 本文根据我的经验提供了有关如何使用nib文件构建应用程序的指导。

故事板有什么问题

与桌面应用程序相比,移动应用程序的导航功能繁重,在可视化编辑器中帮助开发人员进行设计和导航体验非常有意义。 苹果公司在2010年用Xcode 4引入了它们,并使它们成为在nib文件上创建可视用户界面的默认设置。 不幸的是,Xcode故事板存在很多问题:

  • 添加的屏幕越多,对其进行编辑就越麻烦。 您必须不断滚动和缩放很多才能找到要编辑的屏幕
  • 而且,如果您将情节提要板分开,那么记住每个视图控制器放在哪个情节提要板上并非易事
  • 他们很慢。 每次打开情节提要时,它都会显示一个微调框,而Xcode经常会忘记您的上一个滚动位置
  • 如果您在团队中工作,则不可能避免合并冲突,并且合并情节提要xml并不像合并代码那样容易
  • 如果您的屏幕根据应用程序的状态显示不同的视图,则必须使用遏制序列,这意味着创建许多子视图控制器类,或者在彼此之上堆叠视图,这使得编辑很麻烦,因为它们在Xcode中彼此隐藏
  • 通常情况下,导航是非常繁琐的,无法使用情节提要脚本来创建,最终您只能通过代码来完成。 然后,您将混合使用程序化和情节提要导航,这令人迷惑且令人困惑

创建和使用nib文件

自从放弃故事板以支持笔尖以来,我的开发人员的生活变得如此轻松。 幸运的是,Xcode仍然允许您使用关联的笔尖创建视图控制器子类:

这为您项目中的每个View Controller提供了两个文件,彼此相邻,并且始终易于访问:

然后可以使用默认的初始化程序实例化它(不需要标识符字符串!):

让viewController = ViewController()

应用程序启动后,您需要将根视图控制器分配给一个窗口,并告诉应用程序显示该窗口:

让窗口= UIWindow(框架:UIScreen.main.bounds)

window.rootViewController = viewConroller

window.makeKeyAndVisible()

(如果我们像Xcode的项目模板那样在Info.plist中指定主故事板,这就是Xcode / iOS为我们方便地做的事情)

通常,您希望将根视图控制器包装在导航控制器中,以便可以推送其他视图控制器:

让navigationContoller = UINavigationController(rootViewController:viewController)

window.rootViewController = navigationController

输入协调员课程

从结构上讲,笔尖非常适合协调器 (也称为流量控制器 ,请在此处观看Soroush Khanlou的精彩介绍演讲)。 其背后的想法非常简单:创建类,其唯一职责是通过管理视图控制器来控制应用程序的导航流程,从而使情节提要过时。 并且每当您在视图控制器中使用UINavigationController.pushViewController(_:animated 🙂UIViewController.present(_:animated :)等时 ,都可以调用协调器。

协调器不仅使我们能够减少视图控制器中的代码行,而且这种模式的真正优势在于减少了视图控制器之间的依赖关系:通过segue关联的两个视图控制器不需要彼此了解再也不用了,如果我们使用依赖反转,他们甚至不需要了解协调器(即让他们通过协议与协调器进行对话)。 这对我们的体系结构来说非常好,因为它意味着更低的耦合和更好的可重用性。

补充意见

但是回到笔尖文件。 nib文件的优点之一是,您可以将补充UIViews放置在画布上(不同于情节提要),这些UIViews仅在某些特定情况下显示(例如,在没有内容时等)。然后,可以使用IBOutlet将它们挂接到视图控制器上,在代码中使用addSubview()添加它们。 一个警告是,您还需要在运行时添加约束:

(默认情况下,从Nib加载的视图将其自动调整大小蒙版转换为约束,这与我们的约束冲突,因此我们希望将其关闭:)

mySupplementaryView.translatesAutoresizingMaskIntoConstraints = false

view.addSubview(mySupplementaryView)

(这是我知道的将视图限制为其超级视图的大小和位置的最短自动布局代码:)

view.addConstraints(NSLayoutContraint.constraintsWithVisualFormat(“ V:| [v] |”,选项:[],指标:无,视图:[“ v”:mySupplementaryView]))

view.addConstraints(NSLayoutContraint.constraintsWithVisualFormat(“ H:| [v] |”,选项:[],指标:无,视图:[“ v”:mySupplementaryView])

mySupplementaryView.isHidden = true

有了一点样板,您可以一目了然地看到所有相关的UI,而无需在子视图中拖拉,这使得维护起来更加容易,因此值得。

视图控制器包含

但是,您不应过度使用上述技巧。 减少视图控制器代码大小的一项很棒的技术是将它们拆分并通过视图控制器包含来合并它们:创建一个父视图控制器,该视图控制器负责单个屏幕以及标题和选项卡栏,并委派视图的逻辑子部分。屏幕到子视图控制器(在情节提要中,可以使用嵌入的segue来实现 。 我更喜欢这样做的方式是将子视图控制器放入指定的容器视图中,并通过约束继承大小和位置:

覆盖func viewDidLoad(){

super.viewDidLoad()

让childViewController = ChildViewController()

childViewController.view.translatesAutoresizingMaskIntoConstraints = false

childViewController.willMove(toParentViewController:self)

addChildViewController(childViewController)

containerView.addSubview(childViewController.view)

containerView.addConstraints(NSLayoutConstraint.constraintsUsingVisualFormat(“ V:| [v] |”,选项:[],指标:无,视图:[“ v”:childViewController.view]))

containerView.addConstraints(NSLayoutConstraint.constraintsUsingVisualFormat(“ H:| [v] |”,选项:[],指标:无,视图:[“ v”:childViewController.view]))

childViewController.didMove(toParentViewController:self)

提示:对于这样的视图控制器之间的父子关系,我发现协调器没有用:父级可以自行管理其子视图控制器。 对于干净的体系结构,子视图控制器应通过协议与父母对话。

子视图控制器还是辅助视图是更好的选择,取决于主视图控制器的复杂性:一方面,您不希望其代码大小增长太多,另一方面,您不想创建子视图控制器没有任何作用。

表和集合视图

笔尖的一个缺点是您不能拥有用于表视图和集合视图的原型单元,因此您将不得不为每个单元类创建单独的笔尖文件。 您可以仅使用表或集合视图控制器及其原型单元来创建情节提要,其优点是文件更少。 另一方面,无论如何,您都必须为每个单元类创建.swift文件,因此将很容易找到nib文件。 而且所有视图控制器都位于笔尖中将更加一致。