在Xcode8更好的使用StoryBoard

苹果在Xcode 8中为 Interface Builder 的界面作了很是伟大的改善,Size Classes 变得更加直观,StoryBoard的使用也变得更加的便利,还有一个完整度惊人的 Interface Builder 预览界面,这对于那些对 interface Builder 的使用犹豫不决的人来讲, 可能成为巨大的冲击。html

在另外一方面, 许多开发者在使用Interface Builder的时候仍然有一些麻烦, 尤为是在构建一个巨大的包含复杂导航的多屏幕应用的时候。编程

在这篇文章里, 我将分享给你一些处理项目里面的 StroyBoards 和 Nibs 的好建议,若是你尚未用过Interface Builder, 或者你正打算使用这个工具,那么这些建议可能对你颇有用。app

1. 若是是团队协做开发, 请为每个屏幕使用一个单独的 StoryBoard,若是你是独立工做, 这依旧是一个好的习惯。工具

你在项目里是否是有一个相似于这样的main.storyboard?ui

323.png

从设计师的角度来看这很是棒: 你能够很容易的看到完整的用户界面和导航流, 这正是使用Interface Builder想要达到的目的。编码

可是这对于开发者来讲, 就可能会存在不少问题:url

  • 源码控制: StoryBoard 很是难解决合并时候产生的冲突, 因此单独的StoryBoard会使你在团队工做中变得更加轻松。spa

  • StoryBoard文件会变得很是臃肿和难以驾驭,你有多少次由于点错而无心中改变了ViewController的约束?设计

  • 你须要为每个ViewController分配一个StoryBoard的ID, 这很是容易出错: 由于你每次使用这个veiwcontroller的时候都要硬编码这个ID。code

如何链接项目里面的不一样的StoryBoard? 这里有两种方法:

  • 使用Xcode7中所提供的StoryBoard Reference方案

  • 经过代码来链接StoryBoard

你能够点击这里来阅读关于第一种方法的更多的内容。

我将要介绍第二种方法, 由于它在复杂的项目中很是的常见。

2. StoryBoard文件与相关的ViewController subclass使用相同的名称。

这将简化命名的约定, 而且提供给你一些与第三条建议相关的好处。

3. 在UIViewController subclass中初始化StoryBoard.

在初始化StoryBoard的Base ViewController的代码中, 我常常看到下面这样的代码:

1
2
let storyboard = UIStoryboard(name: “Main”, bundle: nil)
let homeViewController = storyboard.instantiateViewController(withIdentifier: “HomeViewController”)

这看起来一点都不清晰: 你须要知道这个StoryBoard的名字, 还须要提供这个ViewController在StoryBoard中的ID, 并且你在建立HomeViewController时, 每次都要使用这种方式。

这有一个更好的方式让你用代码在ViewController中使用类方法来初始化它和它所在的StoryBoard:

1
2
3
4
5
6
7
8
class HomeViewController: UIViewController {
      static func storyboardInstance() -> HomeViewController? { 
          let storyboard = UIStoryboard(name: String.className(self), 
                                        bundle: nil)  return 
          storyboard.instantiateInitialViewController() as?   
                                                  HomeViewController 
      }
}

若是你按照以前的建议来操做, 你就能够避免硬编码 StoryBoard 的名称和实体类的名称.

1
let StoryBoard = UIStoryBoard(name: String.className(self), bundle: nil)

确保你的StoryBoard的名称和实体类的名称彻底相同,不然,当视图引用这个StoryBoard时, 应用程序会崩溃.

这使你代码的可读性更高, 并且能够下降出错率:

1
2
3
4
5
6
7
8
class HomeViewController: UIViewController {
      static func StoryBoardInstance() -> HomeViewController? { 
          let StoryBoard = UIStoryBoard(name: String.className(self), 
                                        bundle: nil)  return 
          StoryBoard.instantiateInitialViewController() as?   
                                                  HomeViewController 
      }
}

若是你想经过 instantiateInitialViewController()来访问ViewController, 请确保你在Interface Builder中设置这个ViewController为initialViewController . 若是你在相同的StoryBoard中有多个ViewController, 那么你须要使用instantiateViewController(withIdentifier: _ )

初始化这个ViewController的时候仅须要这一句代码:

1
let homeViewController = HomeViewController.StoryBoardInstance()

区别很明显吧!

你也但是使用相似的方法从nib中初始化view:

1
2
3
4
5
6
7
8
9
10
11
12
class LoginView: UIView {
 
      static func nibInstance() -> LoginView? {
         if  let loginView =  
               Bundle.mainBundle.loadNibNamed(String.className(self),
                                owner: nil, options: nil)?.first as? 
                                LoginView { 
               return  loginView
        
         return  nil 
      }
}

4. 不要让你的项目加载太多StoryBoard segue.

若是你遵循了第一个建议,就不会产生这样的问题。但即便单个StoryBoard中有多个ViewController,使用segue在ViewController之间进行导航也可能也不是个好主意:

你须要为每个segue命名, 这就很容易出错,毕竟使用硬编码的字符串名称始终不是一个好的编程习惯。

当你为segue添加少许 “if/else” 或 “switch” 语法的时候, PrepareForSegue方法将会变得丑陋并且不易读。

替代方案是什么呢?当咱们按下导航到下一个ViewController的按钮的时候, 须要为这个按钮添加一个IBAction, 还有初始化这个ViewController的代码. 若是你采用了第三条建议, 那么它实际上就只有一行代码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@IBAction func didTapHomeButton(_ sender: AnyObject) {
     if  let nextViewController =    
                      NextViewController.storyboardInstance() {
 
    // initialize all your class properties
    // homeViewController.property1 = … 
    // homeViewController.property2 = … 
 
    // either push or present the nextViewController,
    // depending on your navigation structure 
 
    // present present(nextViewController, animated: true, 
       completion: nil) 
 
    // or push  
       navigationController?.pushViewController(nextViewController, 
       animated:  true )
    }
}

5. 神秘的Unwind segue.

有的时候咱们须要让用户回到前一个屏幕。

这里存在另一个常见的错误:使用一个新的segue导航到前面的ViewController,这将建立一个相同实例的ViewController, 它会加入到视图栈中, 而不是释放当前处于最顶层的ViewController

从iOS7开始, Interface Builder 提供给你了 “unwind” 导航栈的方案.

39.png

StoryBoard 里的 Exit outlet

Unwind segue容许你返回到以前任意位置的屏幕,这听起来很简单,可是实际上它还须要一些会让开发者迷惑的额外操做:

  • 一般当你为一个按钮建立一个outlet事件的时候, Interface Builder 将会为你建立对应的代码。在这个时候, 按住”control”从按钮拖动到“Exit” 上面的时候, 对应的代码就会出如今你的项目里。

  • 一般当你为一个按钮建立一个outlet事件的时候, 它会为你的按钮在对应的类中作关联。若是是用 Unwind Segues, 你还须要在目标ViewController中编写代码。

  • prepareForUnwind 方法有 prepareForSegue 方法的所有缺陷. (请参考前面的说明)

那更简单的方式是什么样子的呢?

那么咱们用代码简单的实现一下: 并不用给你的按钮建立一个”Unwind segue”相关的方法, 而是建立一个常规的方法来实现dismissViewController或者popViewController (请参考你本身的导航结构):

1
2
3
4
5
6
7
8
9
10
11
@IBAction func didTapBackButton(_ sender: AnyObject) { 
 
// if you use navigation controller, just pop ViewController:
   
       if  let nvc = navigationController {   
           nvc.popViewController(animated:  true )
       else 
// otherwise, dismiss it
       dismiss(animated:  true , completion: nil)  
    }
}
相关文章
相关标签/搜索