在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)  
    }
}
相關文章
相關標籤/搜索