为iOS加载Storyboard和Xib文件的简单μ框架。
Loaders
让我们想象一下
您有一个名为Main.storyboard的文件,其中包含初始视图控制器和两个其他控制器,其标识符为:"PageViewController","PageDetailsViewController"。您需要以编程方式实例化它们,您只需要声明一个类似于以下枚举:
enum Main: String, Storyboard {
case initialViewController, pageViewController, pageDetailsViewController
}
...然后您可以加载视图控制器
let pageViewController = Main.pageViewController.load() // type will be UIViewController
...或者使用类型
let pageViewController: PageViewController = Main.pageViewController.load()
...并且您还希望写单元测试以检查所有控制器是否正确加载。所以您写
extension Main: CaseIterable { }
class AppTests: XCTestCase {
func testMainStoryboard() {
_ = Main.allCases.map { $0.load() } // [UIViewController]
}
}
真幸运!您可以使用Loaders来做这件事。
Storyboard的其他可能性
当您 为每个视图控制器使用单个Storyboard时,您可以声明如下:
enum Details: Storyboard, HasInitialController { }
然后您可以像这样实例化控制器:
_ = Details.initialViewController() // UIViewController
强类型视图控制器
当您需要一个特定类型的初始视图控制器时,您必须指定类似于以下类型的别名:
enum Details: Storyboard, HasInitialController {
typealias InitialControllerType = DetailsViewController
}
您还可以根据标识符声明强类型的视图控制器
enum Main: Storyboard, HasInitialController {
typealias InitialControllerType = MainViewController
static var pageViewController: PageViewController { return load() }
static var pageDetailsViewController: PageDetailsViewController { return load() }
}
然后加载数据
_ = Main.initialViewController() // MainViewController
_ = Main.pageViewController // PageViewController
_ = Main.pageDetailsViewController // PageDetailsViewController
如果您不喜欢计算属性作为车间工作,您也可以使用其他方法
enum Main: Storyboard, HasInitialController {
typealias InitialControllerType = MainViewController
static func pageViewController() -> PageViewController { return load() }
static func pageDetailsViewController() -> PageDetailsViewController { return load() }
}
然后加载数据
_ = Main.initialViewController() // MainViewController
_ = Main.pageViewController() // PageViewController
_ = Main.pageDetailsViewController() // PageDetailsViewController
可复用Nibs
加载器(Loaders)还提供了一种注册和从xib文件中加载的可重用视图的出队方式。它适用于UITableViewCell、UICollectionViewCell、UICollectionReusableView。必须遵循的规则是类名、xib文件名和标识符必须相同。
enum FormCells: Nibs {
static var firstTableViewCell: Reusable<FirstTableViewCell> { return load() }
static var secondTableViewCell: Reusable<SecondTableViewCell> { return load() }
}
然后您可以注册它们
FormCells.firstTableViewCell.register(on: tableView)
稍后再出队
let cell = FormCells.firstTableViewCell.dequeue(on: tableView, for: indexPath) // FirstTableViewCell
注意:对于UICollectionView来说,它完全一样。
模块
如果您在应用程序的不同模块中具有故事板或可重用视图,可以简单地使用同一名称的枚举封装声明。您仍然可以使用任何枚举进行分组,但请务必注意不要与应用程序中的任何模块冲突。
enum Storyboards { // there is no modulel 'Storyboards' in the app so it will use 'current' module for Main storyboard
enum Main: String, Storyboard {
case initialViewController, pageViewController, pageDetailsViewController
}
enum User { // there is module User in the app it will load storyboards 'Main' and 'Profile' from there
enum Main: String, Storyboard {
case initialViewController, pageViewController, pageDetailsViewController
}
enum Profile: Storyboard, HasInitialController { }
}
}
从Nib创建自定义视图
使用xib文件而不是手动编写代码来创建自定义视图是一个好主意。它很简单,但需要一些样板代码,并且对于初学者来说,如何以正确的方式执行它并不明显。使用您的自定义视图的每个开发者都应有可能在故事板中以实例化视图,同时他还应能够从代码中进行操作。他还应该在故事板中正确地看到自定义视图,而不是“白色矩形”。Loaders提供了一种简单机制来正确地加载xib文件到自定义视图中。
要创建可自定义设计的视图,您需要创建与您的自定义类相同的Xib文件,并将“文件所有者”(File Owner)设置为该类,以便正确初始化所有IBOutlets(记住 - 不要将“自定义类”(Custom Class)设置为主视图 - 仅设置“文件所有者”)。
在您的自定义视图中,您必须添加两个构造函数,并在其中使用单行代码添加“Xib文件”Nib.add(to: self`)
。简单的实现可能如下所示。
@IBDesignable
class DesignableView: UIView {
@IBOutlet private var label: UILabel!
@IBInspectable var title: String = "title" {
didSet {
label.text = title
}
}
override init(frame: CGRect) {
super.init(frame: frame)
Nib.add(to: self)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
Nib.add(to: self)
}
}
总结
Loaders是一种简单的方法,以清晰、声明性的方式定义所有您的Storyboard的UIViewControllers和NIB可重用视图(UITableViewCell、UICollectionViewCell、UICollectionReusableView)。它为故事板和xib文件带来了自动完成和编译时检查。