1.2.1 IB的執行效率沒有純代碼高 這是一個不爭的事實,IB加載UI能夠簡單理解爲兩個過程:首先要把xib或sb文件對應的nib或storyboardc文件加載到內存中, 而後利用這些數據去生成UI頁面,加以顯示。而純代碼只須要一個過程, 這個過程相似於IB加載UI的第二個過程,直接在內存中生成UI頁面加以顯示swift
1.2.2 使用IB開發過程當中容易出現一些小的問題 用IB開發確實是會碰見一些小問題,可能這些小問題用代碼開發就不會出現,因此若是遇到由於IB開發的問題的話,就把IB的這些坑記錄下來,這是一個很好的學習習慣設計模式
1.2.3 文件容易衝突bash
1.2.4 沒有代碼表達清晰架構
1.2.5 不利於代碼的封裝和工程架構的組織 ###二 IB開發中的技巧ide
能夠把loadNibNamed(_:owner:option:)
方法封裝到源文件的一個類中,源文件派生出幾個子類,根據不一樣狀況加載並返回不一樣的子類。可使用工廠設計模式模塊化
// 咱們建立一個父類
class ToolBar: UIView {
class func toolBar(type:ToolBarType)-> ToolBar? {
if type == .normal {
return Bundle.main.loadNibNamed("ToolBar", owner: nil, options: nil)?[0] as? ToolBar
}else if type == .edit {
return Bundle.main.loadNibNamed("ToolBar", owner: nil, options: nil)?[1] as? ToolBar
}else {
return nil;
}
}
override func awakeFromNib() {
super.awakeFromNib()
handleEvent()
}
func handleEvent() {
// 子類重寫
}
}
// 實例化兩個子類並設置顏色
class NormalToolBar: ToolBar {
override func handleEvent() {
backgroundColor = UIColor.red
}
}
class EditToolBar: ToolBar {
override func handleEvent() {
backgroundColor = UIColor.yellow
}
}
複製代碼
而後在ToolBar.xib中添加兩個VIew 分別更改他們的class爲NormalToolBar
和EditToolBar
學習
而後在控制器中經過父類去初始化ui
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let normalToolBar = ToolBar.toolBar(type: ToolBarType.normal)
normalToolBar?.frame.origin = CGPoint(x: 0, y: 100)
view.addSubview(normalToolBar!)
let editToolBar = ToolBar.toolBar(type: ToolBarType.edit)
editToolBar?.frame.origin = CGPoint(x: 0, y: 200);
view.addSubview(editToolBar!)
}
複製代碼
運行後就能夠看到兩個視圖了 以下spa
.bundle
後綴名就能夠了 而後若是須要查看bundle的資源,就點擊右鍵顯示包內容。若是咱們想加載該bundle的資源的話就能夠這樣添加
let imageBundle = Bundle.main.path(forResource: "image", ofType: "bundle")
let imagePath = imageBundle! + "/icon.png"
let image = UIImage.init(contentsOfFile: imagePath)
複製代碼
用上述的方式建立的bundle能夠放幾乎全部的資源, 除了IB文件,由於IB文件在工程編譯後會被序列化爲二進制的nib和storyboardc文件,而修改文件夾後綴名的方式建立的Bundle是靜態的其內部的資源不參與項目編譯設計
而後想Banner這個Target下添加xib文件,這樣就能夠在Xcode左上角的scheme選擇Banner這個Target編譯了,此Banner.Bundle 就能夠直接複製到其餘工程中使用了
自定義的segue須要在segue菜單中選擇custom選項, 而後再Class標籤裏指定一個UIStoryboardSegue
子類的類名,這個子類必須實現perform方法, 本身完成segue跳轉的過程,若是選擇了segue,可是並無在class標籤中指定任何 UIStoryboardSegue
的子類,那麼App運行該segue是會crash,仍是以A頁面跳轉到B頁面爲例, 來講明一下自定義的segue,要自定義segue,就要繼承於UIStoryboardSegue
,寫一個子類,這個暫且叫作CustomSegue
,而後重寫perform方法
class CustomSegue: UIStoryboardSegue {
override func perform() {
let svc = source
let dvc = destination
dvc.view.frame = svc.view.frame
dvc.view.alpha = 0.0
svc.view.addSubview(dvc.view)
UIView.animate(withDuration: 0.3, animations: {
dvc.view.alpha = 1.0
}) { (flag:Bool) in
svc.navigationController?.pushViewController(dvc, animated: false)
}
}
}
複製代碼
在perform裏簡單的實現了一個漸顯的效果來顯示B頁面。準備好CustomSegue
以後,「拖」一個從A頁面的Button到B頁面的segue,在彈出的segue菜單中選擇一個Custom選項,而後把Class標籤設置成CustomSegue
,運行App就會發現,從A頁碼跳轉到B頁面已是自定義效果了
咱們先來看看下圖中的這種UI結構
你們應該第一眼就能看出來這種結構數據父子結構,代碼大體以下
let testVC = TestViewController.init()
self.view.addSubview(testVC.view)
self.addChildViewController(testVC)
複製代碼
而IB中的Embed Segue就是專門解決這種VC嵌套的。在右邊欄下面的Show the Object Library
中找到Container View
,拖 到 View Contwoller
的控件顯示區域,會看到Container View
與另外一個 View Contwoller
經過Segue連在一塊兒,以下圖
刪除該Segue箭頭指向的 View Contwoller
,選中Container View
,將segue拖到但願添加的子VC上,在彈出的菜單中選擇Embed
,注意這裏只能選擇Embed
,當選擇了Embed
後,你會發現子VC的大小和Container View
的大小同樣了, 此時改變Container View
的大小, 子VC的大小也隨之改變, 將Container View
調整到合適的尺寸運行App,會發現Container View
所在的區域已經變成了子VC,點擊子VC的上的按鈕,能夠正常處理事件,這說明了Embed Segue
執行了容器VC的 addChildVIewController
,將子VC自動添加到容器VC的ChildVIewController
中,整個嵌套過程操做十分簡單,Embed Segue
的優點不只體如今不用實例化子VC,不用本身添加到ChildVIewController
中,並且能夠在IB中調整Container View
的frame,給他添加必要的約束,這也是它優點的一個重要體現
在開發中可能遇到這樣的需求,從A頁面跳轉到B頁面,在B頁面選擇或者填寫一些數據後,在回到A頁面獲取剛剛在B頁面選擇或填寫的數據加以顯示;這中需求相信你們都作過無數遍了吧,代理,block,通知等什麼方式均可以作到的,如今來學習一下用Unwind Segue
的方式 Unwind Segue
提供了一種從一個頁面返回到上一個頁面時的回調, 能夠利用這個特性,簡單優雅的實現頁面間的反向傳值。這個回調能夠由系統自動觸發,也能夠手動觸發,只要在回到的頁面裏添加一個相似於下邊的代碼
@IBAction func handleUnWindSegue(unwindSegue: UIStoryboardSegue) {
if unwindSegue.identifier == "unwindB" {
if let svc = unwindSegue.source as? BViewController {
print("data fromB : \(svc.textF.text)")
}
}
}
複製代碼
而後再SB中選擇要返回到上一個頁面的Button,按住control將其拖動到Exit的位置(以下圖),在彈出的菜單中選擇 handleUnWindSegue方法便可
先看一下兩種加載IB的方式
// 第一種
let testView = Bundle.main.loadNibNamed("LLTestView", owner: nil, options: nil)?[0] as! UIView
// 第二種
let testViewNib = UINib.init(nibName: "LLTestView", bundle: Bundle.main)
let testView = testViewNib.instantiate(withOwner: nil, options: nil)[0] as! UIView
複製代碼
以上兩種方式都包括了這5個過程,下邊詳細介紹這5個教程
1 將nib加載到內存 該過程會將nib中的對象和對象所引用的資源加載到內存,例如,在nib中引用了圖片, 聲音等資源文件,該過程會把這些資源加載到相應的Cocoa Image cache
或 Cocoa sound cache
中, 前面說過, 從xib到nib的過程叫作序列化,是將XML格式的plist文件序列化爲二進制格式的plist,該過程雖然將nib種的對象加載到了內存,可是沒有進行反序列化
2 解僱化 並實例化nib文件裏對應的對象 該過程會將上面加載到內存中的對象進行反序列化,該過程會調用初始化,這裏注意,雖然這些對象大多數都是UIVIew
類,UIViewController
類,或者是它們的子類,可是這些對象經過IB進行初始化,並不會調用init(frame:)
或者普通的init
方法。UIVIew
及其子類會調用Init(coder:)
的方法,UIVIewController
及其子類會調用Init(nibName:bundle:)
的方法,而若是nib
中存在Object
或者External Object
對象,那麼會調用這些對象所在類的init
方法,通過這一步後,才真正把「數據」變成了「對象」
3 創建 connections
(outlets
, actions
) outlets
與 actions
就是前面提到的創建@IBOutlet
就與@IBAction
的鏈接。創建Connections
的順序爲,先創建outlets
鏈接,而後創建actions
鏈接。創建 outlet
鏈接到過程用到了setValue:forKey:
方法,同時創建outlet
過程支持KVO,若有有一個屬性:
@IBAction weak var testView : UIView!
複製代碼
那就就能夠註冊該屬性,經過KVO的回調得知outlet創建關係的時刻:
self.addObserver(self, forKeyPath: "testView", options: .initial, context: nil)
複製代碼
這裏注意,由於是初始化階段,因此options
必須有.initial
纔會發生回調,只有用.new
在outlet
階段是沒有回調發生的,只有初始化以後再從新賦值時,用.new
纔會發生回調
awakeFromNib()
方法對nib中的一些對象調用awakeFromNib
方法,這些對象包括IB
建立的控件,例如UIVIew
的子類等,可是不包括FIle's Owner
, First Response
,placeholder object
nib
中可見的控件顯示出來Object
重構 「神VC」背景: 在開發中, 你們或許遇到過業務和UI都很複雜的頁面,這樣的頁面每每對應了一個代碼量龐大,結構臃腫,可維護性差的VC, 這樣的VC一般稱之爲「神VC」, 「神VC」通常什麼事情都本身作,事無鉅細, 如何重構它每每都是咱們的一個「心病」,重構思路通常都是用適合的設計模式,將「神VC」的一些工做和職能拆分到其餘類,化整爲零,使結構更加清晰, 下面說一下如何利用IB中的Object
來重構「神VC」
1 使用 Object 咱們新建一個IBObjectDemo
的工程,而後在Main.storyboard
中的ViewController
下添加一個Object
,注意,要將其 「拖」到IB左邊欄或者Scene Dock中才能夠添加一個Object,以下圖
假設這個ViewController
是一個神VC,爲了重構這個神VC,咱們新建一個VIewControllerManage.swift
類,該類負責處理ViewController.swift
中的某一類業務邏輯或交互。 如今ViewControllerManager.swift
中添加以下方法:
class ViewControllerManager: NSObject {
@IBAction func handleSomethingForViewController() {
print("handle Something in manager")
}
}
複製代碼
想Main.storyboard
中的Viewcontroller
中放一個按鈕,而後再文件中添加對應的點擊事件
@IBAction func handleSomething(_ sender: UIButton) {
print("handle something in VC")
}
複製代碼
而後將 Object
和 ViewControllerManager
進行關聯 如圖
而後右鍵點擊 ViewControllerManager
,再彈出的菜單中找到剛添加的法法,而後連線到控制器的按鈕
handle something in VC
handle Something in manager
複製代碼
掌握了Object
的簡單使用以後,在進一步來說上面的例子,能夠只將Main.storyBoard
中的ViewController
中Button
事件處理放在ViewControllerManager
中。下面來讓ViewControllerManager
作更多的事情,如今能夠將IB的屬性也拖到 ViewControllerManager
中,這樣VIewController
就不用關心該UIButton
相關的邏輯了。 這是一個意義很大的事情, 對一個類來講,屬性和方法幾乎是類的所有,利用Object
能夠將VC的屬性和方法都放在manager
中管理, 就很方便的解決了神VC的問題了
一般用一個類去承擔VC的時候,咱們都須要給這個類的實例傳參數,並且這個實例每每設置成VC的屬性, 方便任何地方使用。 一樣的 咱們能夠把IB中的ViewControllerManager
當成一個屬性拖到ViewController
中
ViewControllerManager
了,能夠給他傳遞參數了,
在一些複雜的狀況下,manager
知道本身服務的VC是誰, 此時能夠給ViewControllerManager
一個屬性指向ViewController
,可是爲了防止循環引用要使用weak
修飾,以下
Object
意義很大,做用也很大,掌握了Object
的用法以後,能夠很靈活地運用它 在這裏能夠提出幾個思路,但願能起一個拋磚引玉的做用,可以對你們有所啓發,從而把Object
用的更好,更妙
Delegate
,須要處理不少回調,此事能夠用Object
替VC去實現這些Delegate
方法,例如,能夠建立一個TableVIewObject.swift
專門實現TableVIew
的Delegate
和DataSource
方法Object
裏,將這些需求或交互與VC解耦,也就是說,創建各個繼承於Object
類的一些子類,每一個子類實現特定的需求或交互,這些類做爲基本單元存在, 當要實現一個VC時,根據需求在IB中添加不一樣的Object
控件,這些不一樣的Object
控件共同完成了該VC中的大部分功能,能夠把IB中的Object
和它對應的NSObject
子類想象成一個零散的基礎的積木塊,把VC想象成用這些積木塊搭建起來的城堡,城堡的風格不一樣(VC的做用不一樣)使用積木塊的數量和種類也不一樣,這樣就使代碼的複用率很高,從而大大減小VC的代碼,用IB優雅的解決了「神VC」的問題External Object
重構「神VC」External Object
是與Object
相似的東西,它的功能更增強大,可是隻能用於Xib xib中有一個External Object
更「厲害」,它能夠將 Xib源文件、Xib的Files's Owner
源文件和NSObject
類的源文件三者創建關係。
新建一個項目IBExternalObjectDemo
,而後建立一個SegmentView.swift
、SegmentView.xib
和一個ViewControllerManager.swift
文件,而後再SegmentView.xib
中添加兩個按鈕,往SegmentView.swift
文件中連線回調方法
@IBAction func handleSelect(_ sender: UIButton) {
print("handle select in SegmentView")
}
複製代碼
而後再SegmentView.Xib
中選中File's Owner
,再Show the Identify inspector
中將class
改成 ViewController
,而後再講兩個按鈕像控制器中連線回調方法
@IBAction func handleSegmentChanges(_ sender: UIButton) {
print("handle select In VC")
}
複製代碼
重點是是 External Object
,向SegmentView
中拖入External Object
,而後更改External Object
的class
爲 ViewControllerManager
,而後再Show The Attributes inspector
中將Identifier
標籤值也設置爲 ViewControllerManager
,如圖
而後將兩個按鈕往ViewControllerManager
中脫線回調方法
@IBAction func managerSegmentView(_ sender: UIButton) {
print("handle select In manager")
}
複製代碼
而後再控制器中初始化SegmentView
並添加再控制器上
let manager = VIewControllerManager()
override func viewDidLoad() {
super.viewDidLoad()
let paramDic = ["VIewControllerManager" : manager]
let optionDict = [UINibExternalObjects : paramDic]
let segmentVIew = Bundle.main.loadNibNamed("SegmentView", owner: self, options: optionDict)?[0] as! SegmentView
segmentVIew.center = view.center
view.addSubview(segmentVIew)
}
複製代碼
首先初始化一個VIewControllerManager
實例做爲VC的Manager
屬性, 而後生成一個字典,這個字典的key 是 VIewControllerManager
,就是segmentVIew.xib
中的External Object
的Indentifier
標籤中的值,Value是Manager
,而後生成另外一個字段optionDict
,這個key:UINibExternalObjects
是固定的,只有這一個,Value
是paramDic
,接下來就是實例化xib,將optionDict
傳入options
這個參數中,而這個參數就是制定External Object
,運行代碼 點擊按鈕, 輸出一下結果
handle select In VC
handle select in SegmentView
handle select In manager
複製代碼
咱們就能夠用上述的思路將「神VC」中的功能分在三個模塊中完成,重構「神VC」的主要思路就是將該類的代碼清晰,合理的分散在其餘類中,讓每一個類僅僅處理本身的職責,各司其職。
Object 和 External Object總結 Object
能夠用於xib 和 sb,而External Object
只能用於sb,二者的類似之處是都提供了一種能夠將VC中的代碼放到其餘類中的途徑,這裏的其餘類必須是NSObject
的子類,當用Swift開發時要注意到這一點,當用External Object
和Object
重構神VC時,必定要清楚每一個類的職責是什麼,切記矯枉過正,把全部的邏輯都放在Object
和External Object
中,是VC變得無足輕重,因此必定要拿捏好重構「神VC」的角度,畢竟上下文的環境大多都在改VC中。 下圖展現了Object
中各個對象之間的關係
下圖展現了External Object
中各個對象之間的關係
以上就是本人詳讀全書以後的總結,此書中還有不少細小的知識點非常值得咱們學習的,有想對IB進一步瞭解學習的強烈推薦閱讀此書🙂