如何用Swift創(chuàng)建自定義iOS控件

本文將分兩個(gè)部分內(nèi)容:基于代碼的自定義控件和基于XIB的自定義控件。

基于代碼的自定義控件

實(shí)現(xiàn)的基本步驟(純文字版, 閱讀障礙者請(qǐng)?zhí)^~)

  1. 創(chuàng)建一個(gè)新類,基類選擇UIView
  2. 實(shí)現(xiàn)init(frame:)方法
override init(frame: CGRect) {
    super.init(frame: frame)
    setupSubviews()
}
func setupSubviews() {
// ..
}
  1. 實(shí)現(xiàn)init()方法
convenience init() {
    self.init(frame: CGRect.zero)
}
  1. 實(shí)現(xiàn)init(coder:)方法
    基本邏輯跟init(frame:)是類似的
required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    setupSubviews()
}

init(coder:)和init(frame:)是在兩個(gè)不同的情境下被調(diào)用的。前者是當(dāng)自定義控件在XIB中使用的時(shí)候被調(diào)用,后者是使用代碼直接創(chuàng)建自定義控件時(shí)被調(diào)用的。
當(dāng)自定義控件在XIB中使用時(shí)候還有一個(gè)方法會(huì)被觸發(fā):awakeFromeNib,此方法是當(dāng)控件從NIB文件加載完畢之后觸發(fā)的, 它和init(coder:)的觸發(fā)機(jī)制是不同的, init(coder:)要做真正的反序列化的工作, awakeFromNib是反序列化完畢并且設(shè)置好相關(guān)的Outlet變量之后被調(diào)用的。

  1. 重寫layoutSubviews方法,調(diào)整子View的大小。
override func layoutSubviews() {
    asubview.bounds = self.bounds
    // ...
}

有時(shí)候也可以在上述創(chuàng)建子View的時(shí)候設(shè)置相關(guān)的大小和約束,但是如果子View的大小或約束需要依賴self的大小的話,就只有在這個(gè)方法里進(jìn)行, 因?yàn)橹筮@個(gè)方法被調(diào)用的時(shí)候self的frame才是正確的, 同時(shí), 在self的frame發(fā)生變化的時(shí)候(包括發(fā)生了旋轉(zhuǎn)), 這個(gè)方法也會(huì)再次被調(diào)用。

基于XIB創(chuàng)建自定義控件

我們假設(shè)想寫一個(gè)類似百度個(gè)稅計(jì)算機(jī)的App, 想讓其中的稅率條目做成自定義控件


自定義控件界面
  1. 創(chuàng)建一個(gè)新類MyCustomView,派生類選擇UIView


    創(chuàng)建新類
  2. 創(chuàng)建一個(gè)和類同名的XIB文件


    創(chuàng)建XIB文件
  3. 修改XIB文件中View的Size 為Freedom, Status bar為None


    Metrices屬性
  4. 設(shè)計(jì)自定義控件的視圖布局和約束


    設(shè)計(jì)布局和約束
  5. 在XIB的File's Owner界面的Custom Class中選擇自定義類的名字
    File's Owner

    注意:不是在View自身的Custom Class中填寫,如果你調(diào)試的時(shí)候出現(xiàn)init(coder:)遞歸調(diào)用了的話,那就是這個(gè)地方弄錯(cuò)了
    錯(cuò)誤的示范

File's Owner設(shè)置的CustomView是設(shè)定子View關(guān)聯(lián)Outlet變量到哪個(gè)類的對(duì)象, 這個(gè)對(duì)象跟后面通過UINib加載Xib文件傳遞的Owner對(duì)象的關(guān)系就是"類"與"對(duì)象"的關(guān)系.
而View的CustomView是指當(dāng)Xib文件被加載進(jìn)來的時(shí)候其跟View對(duì)象的類型. 默認(rèn)是UIView, 后面我們會(huì)展示在Xib中使用我們的自定義控件MyCustomView的時(shí)候, 我們就會(huì)用到這個(gè)特性.

  1. 給子View拉線關(guān)聯(lián)必要的Outlet變量。
    @IBOutlet weak var titleLabel: UILabel!
    
    @IBOutlet weak var selfTaxRateInput: UITextField!

    @IBOutlet weak var selfTaxResultLabel: UILabel!
    
    @IBOutlet weak var companyTaxRateInput: UITextField!

    @IBOutlet weak var companyTaxResultLabel: UILabel!
  1. 定義一個(gè)加載XIB文件的方法和一個(gè)完整的初始化子類的方法
// load view from xib
    func loadViewFromNib() -> UIView {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: String(self.dynamicType), bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
        return view
    }
// setup view's auto resize to fill parent, and add a % view to input view    
    func setupSubviews() {
        view = loadViewFromNib()
        view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
        addSubview(view)
        
        selfTaxRateInput.rightViewMode = .Always
        let labelMaker:()->UIView = {
            let precentLabel = UILabel();
            precentLabel.text = "%";
            return precentLabel
        }
        selfTaxRateInput.rightView = labelMaker()
        companyTaxRateInput.rightViewMode = .Always
        companyTaxRateInput.rightView = labelMaker()
    }
// reset subview's frame
    override func layoutSubviews() {
        view.frame = bounds
        let precentRect = CGRect(x: 0, y: 0, width: 20, height: bounds.size.height)
        selfTaxRateInput.rightView?.bounds = precentRect
        companyTaxRateInput.rightView?.bounds = precentRect
    }

這里需要一個(gè)添加一個(gè)view的屬性, 用來保存從Xib中加載得到的View, 然后作為子View添加給self. 這里可能有點(diǎn)迷惑, 我們不就是一個(gè)view嗎? 其實(shí), 在現(xiàn)在這個(gè)用法里面, self只是一個(gè)容器, 它的作為就是作為Xib中View的容器

  1. 編寫我們的各個(gè)初始化函數(shù)
    weak var view: UIView!
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupSubviews()
    }
    
    convenience init() {
        self.init(frame: CGRect.zero)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupSubviews()
    }

上面的步驟已經(jīng)完成了自定義控件的編寫了, 下面接著介紹如何在Xib中使用它?

  1. 打開我們主界面的Xib文件, 拖入一個(gè)UIView 設(shè)置好大小位置和約束


    使用自定義控件
  2. 把這個(gè)UIView的CustomView設(shè)置為我們上面開發(fā)的"MyCustomView"


    設(shè)置CustomView

這里設(shè)置了子View的CustomView為MyCustomView, 那么當(dāng)主Xib被加載的時(shí)候, 解析到這個(gè)子View的時(shí)候, 就會(huì)用這個(gè)CustomView的類型來創(chuàng)建對(duì)象. 偽代碼類似這樣:

let theClass: AnyClass! = NSClassFromString(CustomClassName)
let classObject: NSObject.Type! = theClass as! NSObject.Type!
let obj = classObject.init()
rootView.addSubview(obj)   
  1. 好, 運(yùn)行一下看看效果.


    效果

上面的使用就介紹的差不多了, 但是有個(gè)美中不足的地方, 那就是不能在設(shè)計(jì)階段看到大致的效果. 也不能在設(shè)計(jì)階段修改一些屬性 -- 比方說設(shè)置title.

下面我們來修改一下MyCustomView, 讓它可以支持一些設(shè)計(jì)時(shí)實(shí)時(shí)展示的特性.

  1. 首先, 我們?cè)贛yCustomView的定義前面加上 @IBDesignable
@IBDesignable
class MyCustomView: UIView {
    
    @IBOutlet weak var titleLabel: UILabel!
    
    @IBOutlet weak var selfTaxRateInput: UITextField!
  1. 定義一個(gè)title的屬性, 并使用@IBInspectable修飾
    @IBInspectable var title: String = "" {
        didSet {
            self.titleLabel.text = title
        }
    }
實(shí)時(shí)設(shè)計(jì)

完成之后, 我們?cè)俅未蜷_主Xib的Interface Builder界面, 這時(shí)候, 我們就已經(jīng)可以實(shí)時(shí)看到控件的界面了, 并且在屬性面板里頭, 我們也多了一個(gè)叫"Title"的屬性可以設(shè)置, 我們?cè)囍O(shè)置成"公積金", 設(shè)計(jì)界面就自動(dòng)更新了. 是不是很有點(diǎn)標(biāo)準(zhǔn)控件的感覺呢?

注意, 你必須給變量定義一個(gè)明確的類型, 否則在IB上是看不到的

錯(cuò)誤的示范:
 @IBInspectable var title = "" {
        didSet {
            self.titleLabel.text = title
        }
    }

如果你對(duì)實(shí)時(shí)設(shè)計(jì)感興趣的話, 還可以閱讀文章《如何創(chuàng)建iOS可視化控件》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容