@IBDesignable-所见即所得的Custom View

iOS8发布后,XCode对自定义控件支持有所加强,之前在途牛旅游客户端开发了一个通用的控件TNStepper,这篇文章主要通过升级TNStepper来简单介绍这项新技术@IBDesignable @IBInspectable

问题

在iOS的开发中,对于一些具备独立功能的可复用的控件我们往往会把它做成一个自定的控件(custom view)。使用控件时,我们调用initWithFrame:方法创建控件,对于storyboard/xib,我们通过在storyboard/xib拖拽一个UIView控件(要求自定义控件实现initWithCoder:方法),并将其Class指定为MyCustomView。我们看到的一个空白的view,并不是我们设计好的custom view,根本没有发挥storyboard/xib所见即所得优势,也没法动态去看到custom view属性调整后的效果。

@IBDesignable实时渲染

本文使用Swift编写了TNStepper的升级版VisualStepper,通过该控件来简单介绍这项新技术,读者可先行下载代码。其中,XTStepper为我们的自定义View。

首先简单介绍一下XTStepper的代码,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}

required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}

func xibSetup() {
view = loadViewFromNib()
// Add constraint here
...
self.updateApperance()
}

func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "XTStepper", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}

作为一个重度的storyboard/xib使用者,我是不希望自己去手写创建代码的,控件里的子view都是XTStepper.xib创建的,通过func loadViewFromNib() -> UIView加载。这样做的好处就是所见即所得,而且我们不用写那些繁琐的constraint。XTStepper.xib长这个样子。

image01

打开Main.storyboard文件,却发现一片空白,完全看不到我们设计好的控件的样子。
我们所需要做的很简单,只要在我们希望实时渲染的自定义控件前加上@IBDesignable关键字即可。

1
2
3
4
5
import UIKit

@IBDesignable class XTStepper: UIControl {
...
}

现在让我们来见证一下它的魔力。

image02

虽然只有加一个关键字这么简单,我们仍然要注意,因为从storyboard创建ViewController调用的是这个初始化方法,我们一定要覆写init(coder aDecoder: NSCoder),做初始化工作。现在控件已经是我们所希望看到的样子了,但这还不够。UIStepper是可以动态调整相关的属性,来控制控件的状态的,我们也想要VisulStepper一样好用。

@IBInspectable动态更新

我们为VisualStepper添加三个类似于TNStepper的属性,当前值valuemaxinumValueminmumValue,当这几个值发生变化的时候,控件的+``-的可用状态会发生变化。

1
2
3
4
5
func updateApperance(){
valueLabel.text = String(value)
minusButton.enabled = value > minmumValue && value <= maxinumValue
plusButton.enabled = value >= minmumValue && value < maxinumValue
}

我们希望可以在InterfaceBuilder里设置这些属性,方法很简单,在属性前加上@IBInspectable。我们可以看到刚才定义的几个属性,在这里我们可以设置他们的值。
在设置的时候,我们发现控件的状态不会变化,即使value <= minumValue了,-仍然是可以点击的状态,因此我们需要在检测到值变化的时候,去更新我们控件的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@IBInspectable var value:Int = 0 {
didSet{
sendActionsForControlEvents(UIControlEvents.ValueChanged)
self.updateApperance()
}
}
@IBInspectable var maxinumValue:Int = 0 {
didSet{
self.updateApperance()
}
}
@IBInspectable var minmumValue:Int = 0 {
didSet{
self.updateApperance()
}
}

至此我们完成了一个更好用的更像UIStepper的自定义Stepper

参考文献

[1] 王巍:WWDC 2014 Session笔记 - 可视化开发,IB 的新时代