《iOS UI開發捷徑》之從新認識IB

11511686475_.pic.jpg
做者二亮子用寫IB省出的時間出了這本書,這本書確實是一本好書,本文章根據書中的內容總結了IB開發中本身不熟悉或者是不經常使用的知識點。

一 來看一下IB開發的優勢以及缺點

1.1 優勢
  • 1.1.1 開發和維護效率高 IB開發與純代碼開發相比, 效率至少提升兩倍 這也就是爲何做者在業餘時間能寫出這本書的緣由😁
  • 1.1.2 減小大量的UI代碼和「膠水代碼」 IB 開發與純代碼開發相比,代碼量至少減小三分之一
  • 1.1.3 適配變得十分簡單
  • 1.1.4 IB也能夠作一些非UI的事情 例如能夠用IB中的Object從新組織VC的業務邏輯,減小一下沒必要要的代碼,
  • 1.1.5 利用IB學習控件能夠達到事半功倍的效果
1.2 缺點
  • 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

2.1 xib是能夠不依賴於源文件而單獨使用的,純粹的「死」UI能夠只用一個xib文件展現,無需使它與源文件關聯
2.2 理解File's Owner 使用File'sOwner 讓xib中的button事件同時響應兩個文件

WechatIMG1.jpeg
File'sOwner 就是文件的全部者, 這個file就是指該Xib文件,文件的全部者就是處理這個文件所涉及的業務邏輯與交互的對象。 咱們能夠經過此File'sOwner 來設置他的文件全部者
image.png
這樣不只能夠在toolBar.swift文件中拖UIbuttonClick事件 也能夠在ViewController中去拖拽UIbuttonClick事件 這樣 點擊Button 兩個文件下的事件都是響應
image.png

2.3 封裝xib

能夠把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爲NormalToolBarEditToolBar 學習

image.png

而後在控制器中經過父類去初始化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

image.png

2.4 建立bundle的兩種方式(一種能夠包含IB,一種不能包含IB)
  • Bundle 就是一個有着固定結構的目錄, 因此能夠新建一個文件夾,把須要封裝的資源文件複製到該目錄下,而後直接給該文件夾加.bundle後綴名就能夠了 而後若是須要查看bundle的資源,就點擊右鍵顯示包內容。若是咱們想加載該bundle的資源的話就能夠這樣添加
    image.png
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是靜態的其內部的資源不參與項目編譯設計

  • 建立一個基於macOSBundle的target來得到Bundle 這種方式能夠把其中的XIB和SB序列化爲二進制的nib和storyboardc文件。建立Bundle的Target的方式是,點擊菜單欄中的file->New->Target,在彈出的菜單中選擇macOS->Framework&Library->Bundle,就能夠建立除一個bundle了 如圖:
    image.png
    之因此選擇macOS,是由於iOS不支持以target形式建立的Bundle,爲了非讓剛剛建立的的Bundle能在iOS上順利工做,須要將Banner這個Target下的Build Setting裏的的SupportedPlatment修改爲iOS,

image.png

而後想Banner這個Target下添加xib文件,這樣就能夠在Xcode左上角的scheme選擇Banner這個Target編譯了,此Banner.Bundle 就能夠直接複製到其餘工程中使用了

2.5 自定義的segue

自定義的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頁面已是自定義效果了

CustomSegue.gif

2.6 深刻學習:Embed Segue

咱們先來看看下圖中的這種UI結構

IMG_341B14EFB937-1.jpeg

你們應該第一眼就能看出來這種結構數據父子結構,代碼大體以下

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連在一塊兒,以下圖

image.png

刪除該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,給他添加必要的約束,這也是它優點的一個重要體現

2.7 深刻學習:Unwind Segue

在開發中可能遇到這樣的需求,從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方法便可

image.png

2.7 IB文件的加載過程(分爲5步)

先看一下兩種加載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 cacheCocoa 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) outletsactions 就是前面提到的創建@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纔會發生回調,只有用.newoutlet階段是沒有回調發生的,只有初始化以後再從新賦值時,用.new纔會發生回調

  • 4 調用awakeFromNib()方法

對nib中的一些對象調用awakeFromNib方法,這些對象包括IB建立的控件,例如UIVIew的子類等,可是不包括FIle's OwnerFirst Response,placeholder object

  • 5 將nib中可見的控件顯示出來
2.8 用 Object 重構 「神VC」

背景: 在開發中, 你們或許遇到過業務和UI都很複雜的頁面,這樣的頁面每每對應了一個代碼量龐大,結構臃腫,可維護性差的VC, 這樣的VC一般稱之爲「神VC」, 「神VC」通常什麼事情都本身作,事無鉅細, 如何重構它每每都是咱們的一個「心病」,重構思路通常都是用適合的設計模式,將「神VC」的一些工做和職能拆分到其餘類,化整爲零,使結構更加清晰, 下面說一下如何利用IB中的Object來重構「神VC」

  • 1 使用 Object 咱們新建一個IBObjectDemo的工程,而後在Main.storyboard中的ViewController 下添加一個Object,注意,要將其 「拖」到IB左邊欄或者Scene Dock中才能夠添加一個Object,以下圖

    image.png

    假設這個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")        
    }
複製代碼

而後將 ObjectViewControllerManager進行關聯 如圖

image.png

而後右鍵點擊 ViewControllerManager ,再彈出的菜單中找到剛添加的法法,而後連線到控制器的按鈕

image.png
此時運行App , 點擊按鈕 會看到這樣的輸出

handle something in VC
handle Something in manager
複製代碼
  • 2 用Object 重構 「神VC」的思路

掌握了Object的簡單使用以後,在進一步來說上面的例子,能夠只將Main.storyBoard中的ViewControllerButton事件處理放在ViewControllerManager中。下面來讓ViewControllerManager作更多的事情,如今能夠將IB的屬性也拖到 ViewControllerManager 中,這樣VIewController就不用關心該UIButton相關的邏輯了。 這是一個意義很大的事情, 對一個類來講,屬性和方法幾乎是類的所有,利用Object能夠將VC的屬性和方法都放在manager中管理, 就很方便的解決了神VC的問題了

image.png

一般用一個類去承擔VC的時候,咱們都須要給這個類的實例傳參數,並且這個實例每每設置成VC的屬性, 方便任何地方使用。 一樣的 咱們能夠把IB中的ViewControllerManager 當成一個屬性拖到ViewController

image.png
連線後 咱們就能夠隨時使用 ViewControllerManager了,能夠給他傳遞參數了,

image.png

在一些複雜的狀況下,manager知道本身服務的VC是誰, 此時能夠給ViewControllerManager一個屬性指向ViewController,可是爲了防止循環引用要使用weak修飾,以下

image.png

  • 3 如何用好Object IB中的Object意義很大,做用也很大,掌握了Object的用法以後,能夠很靈活地運用它 在這裏能夠提出幾個思路,但願能起一個拋磚引玉的做用,可以對你們有所啓發,從而把Object用的更好,更妙
    • ① 一般一個神VC會成爲不少對象的Delegate,須要處理不少回調,此事能夠用Object替VC去實現這些Delegate方法,例如,能夠建立一個TableVIewObject.swift專門實現TableVIewDelegateDataSource方法
    • ② 能夠將一些通用的需求或交互模塊化在對應的Object裏,將這些需求或交互與VC解耦,也就是說,創建各個繼承於Object類的一些子類,每一個子類實現特定的需求或交互,這些類做爲基本單元存在, 當要實現一個VC時,根據需求在IB中添加不一樣的Object控件,這些不一樣的Object控件共同完成了該VC中的大部分功能,能夠把IB中的Object和它對應的NSObject子類想象成一個零散的基礎的積木塊,把VC想象成用這些積木塊搭建起來的城堡,城堡的風格不一樣(VC的做用不一樣)使用積木塊的數量和種類也不一樣,這樣就使代碼的複用率很高,從而大大減小VC的代碼,用IB優雅的解決了「神VC」的問題
2.9 用 External Object 重構「神VC」

External Object 是與Object相似的東西,它的功能更增強大,可是隻能用於Xib xib中有一個External Object更「厲害」,它能夠將 Xib源文件、Xib的Files's Owner 源文件和NSObject類的源文件三者創建關係。

新建一個項目IBExternalObjectDemo,而後建立一個SegmentView.swiftSegmentView.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 ObjectclassViewControllerManager,而後再Show The Attributes inspector 中將Identifier標籤值也設置爲 ViewControllerManager,如圖

image.png
image.png

而後將兩個按鈕往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 ObjectIndentifier標籤中的值,Value是Manager,而後生成另外一個字段optionDict,這個key:UINibExternalObjects是固定的,只有這一個,ValueparamDic,接下來就是實例化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 ObjectObject重構神VC時,必定要清楚每一個類的職責是什麼,切記矯枉過正,把全部的邏輯都放在ObjectExternal Object中,是VC變得無足輕重,因此必定要拿捏好重構「神VC」的角度,畢竟上下文的環境大多都在改VC中。 下圖展現了Object中各個對象之間的關係

Object中各個對象之間的關係.png

下圖展現了External Object中各個對象之間的關係

External Object中各個對象之間的關係.png

以上就是本人詳讀全書以後的總結,此書中還有不少細小的知識點非常值得咱們學習的,有想對IB進一步瞭解學習的強烈推薦閱讀此書🙂

相關文章
相關標籤/搜索