•当从代码实例化UIView的时候,initWithFrame会执行;
当从文件加载UIView的时候,initWithCoder会执行。
您定义的每个新的视图对象都应该包含initWithFrame:初始化方法。该方法负责在创建对象时对类进行初始化,使之处于已知的状态。在通过代码创建您的视图实例时,需要使用这个方法。
程序清单1-1显示了标准的initWithFrame:方法的一个框架实现。该实现首先调用继承自超类的实现,然后初始化类的实例变量和状态信息,最后返回初始化完成的对象。您通常需要首先执行超类的实现,以便在出现问题时可以简单地终止自己的初始化代码,返回nil。
程序清单1-1初始化一个视图的子类
- (id)initWithFrame:(CGRect)aRect {
self = [super initWithFrame:aRect];
if (self) {
// setup the initial properties of the view
... }
return self;
}
如果您从nib文件中装载定制视图类的实例,则需要知道:在iPhone OS中,装载nib的代码并不通过initWithFrame:方法来实例化新的视图对象,而是通过NSCoding协议定义的initWithCoder:方法来进行。
即使您的视图采纳了NSCoding协议,Interface Builder也不知道它的定制属性,因此不知道如何将那些属性编码到nib文件中。所以,当您从nib文件装载定制视图时,initWithCoder:方法不具有进行正确初始化所需要的信息。为了解决这个问题,您可以在自己的类中实现awakeFromNib方法,特别用于从nib文件装载的定制类。
程序清单1-2显示了drawRect:方法的一个简单实现,即在视图边界描画一个10像素宽的红色边界。由于UIKit描画操作的实现也是基于Quartz,所以您可以像下面这样混合使用不同的描画调用来得到期望的结果。
程序清单1-2一个描画方法
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGRectmyFrame = self.bounds;
CGContextSetLineWidth(context, 10);
[[UIColor redColor] set];
UIRectFrame(myFrame);
}
如果您能确定自己的描画代码总是以不透明的内容覆盖整个视图的表面,则可以将视图的opaque属性声明设置为YES,以提高描画代码的总体效率。当您将视图标识为不透明时,UIKit会避免对该视图正下方的内容进行描画。这不仅减少了描画开销的时间,而且减少内容合成需要的工作。然而,只有当您能确定视图提供的内容为不透明时,才能将这个属性设置为YES;如果您不能保证视图内容总是不透明,则应该将它设置为NO。
提高描画性能(特别是在滚动过程)的另一个方法是将视图的clearsContextBeforeDrawing属性设置为NO。当这个属性被设置为YES时,UIKIt会在调用drawRect:方法之前,把即将被该方法更新的区域填充为透明的黑色。将这个属性设置为NO可以取消相应的填充操作,而由应用程序负责完全重画传给drawRect:方法的更新矩形中的部分。这样的优化在滚动过程中通常是一个好的折衷。
当控件从xib或storyboard中加载的时候,情况就变得复杂了,首先我们知道有initWithCoder方法,该方法会在对象被反序列化的时候调用,,一般是在xib或storyboard中写一个View,然后让系统来完成反序列化的工作,此时在initWithCoder调用之后,awakeFromNib方法也会被执行,既然在awakeFromNib方法里也能做初始化操作,那我们如何抉择?
一般来说要尽量在initWithCoder中做初始化操作,毕竟这是最合理的地方,只要你的控件支持序列化,那么它就能在任何被反序列化的时候执行初始化操作,这里适合做全局数据、状态的初始化工作,也适合手动添加子视图。
awakeFromNib相较于initWithCoder的优势是:当awakeFromNib执行的时候,各种IBOutlet也都连接好了;而initWithCoder调用的时候,虽然子视图已经被添加到视图层级中,但是还没有引用。如果你是基于xib或storyboard创建的控件,那么你可能需要对IBOutlet连接的子控件进行初始化工作,这种情况下,你只能在awakeFromNib里进行处理。同时xib或storyboard对灵活性是有打折的,因为它们创建的代码无法被继承,所以当你选择用xib或storyboard来实现一个控件的时候,你已经不需要对灵活性有很高的要求了,唯一要做的是要保证用户一定是通过xib创建的此控件,否则可能是一个空的视图,可以在initWithFrame里放置一个断言或者异常来通知控件的用户。
最后还要注意视图层级的问题,比如你要给View放置一个背景,你可能会在initWithCoder或awakeFromNib中这样写:
1 [self addSubview:self.backgroundView];//通过懒加载一个背景View,然后添加到视图层级上
你的本意是在控件的最下面放置一个背景,却有可能将这个背景覆盖到控件的最上方,原因是用户可能会在xib里写入这个控件,然后往它上面添加一些子视图,这样一来,用户添加的这些子视图会在你添加背景之前先进入视图层级,你的背景被添加后就挡住了用户的子视图。如果你想支持用户的这种操作,可以把addSubview替换成insertSubview:atIndex:。
同时支持从代码和文件中加载
如果你要同时支持initWithFrame和initWithCoder,那么你可以提供一个commonInit方法来做统一的初始化:
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [superinitWithCoder:aDecoder];
if(self) {
[self commonInit];
}
returnself;
}
- (id)initWithFrame:(CGRect)frame {
self = [superinitWithFrame:frame];
if(self) {
[self commonInit];
}
returnself;
}
- (void)commonInit {
// do something ...
}
awakeFromNib方法里就不要再去调用commonInit了。
支持sizeToFit
如果你的控件对尺寸有严格的限定,比如有一个统一的宽高比或者是固定尺寸,那么最好能实现系统给出的约定成俗的接口。
sizeToFit用在基于frame布局的情况下,由你的控件去实现sizeThatFits:方法:
- (CGSize)sizeThatFits:(CGSize)size {
CGSize fitSize = [supersizeThatFits:size];
fitSize.height += self.label.frame.size.height;
//如果是固定尺寸,就像UISwtich那样返回一个固定Size就OK了
returnfitSize;
}
然后在外部调用该控件的sizeToFit方法,这个方法内部会自动调用sizeThatFits并更新自身的Size:
[self.customView sizeToFit];