知识点5

关于 initWithNibName 和 loadNibNamed 的区别和联系:

iOS initWithFrame: 与initWithCoder:区别

http://www.jianshu.com/p/d90c7bc20132

•当从代码实例化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];

iOS - layoutSubviews、drawRect、awakeFromNib和 loadNibNamed解释

http://blog.csdn.net/xiaohe901216/article/details/50380625

awakeFromNib

当.nib文件被加载的时候,会发送一个awakeFromNib的消息到.nib文件中的每个对象,每个对象都可以定义自己的 awakeFromNib函数来响应这个消息,执行一些必要的操作。也就是说通过nib文件创建view对象是执行awakeFromNib 。

viewDidLoad

当view对象被加载到内存是就会执行viewDidLoad,所以不管通过nib文件还是代码的方式创建对象都会执行viewDidLoad。

layoutSubviews

layoutSubviews方便数据计算,drawRect方便视图重绘。

layoutSubviews在以下情况下会被调用:

1、init初始化不会触发layoutSubviews。

2、addSubview会触发layoutSubviews。

3、设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化。

4、滚动一个UIScrollView会触发layoutSubviews。

5、旋转Screen会触发父UIView上的layoutSubviews事件。

6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件。

7、直接调用setLayoutSubviews。

drawRect

drawRect在以下情况下会被调用:

1、如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。drawRect 掉用是在Controller->loadView,?Controller->viewDidLoad?两方法之后掉用的.所以不用担心在 控制器中,这些View的drawRect就开始画了.这样可以在控制器中设置一些值给View(如果这些View?draw的时候需要用到某些变量 值).

2、该方法在调用sizeToFit后被调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect:方法。

3、通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。

4、直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0。

以上1,2推荐;而3,4不提倡 ? drawRect方法使用注意点:

1、 若使用UIView绘图,只能在drawRect:方法中获取相应的contextRef并绘图。如果在其他方法中获取将获取到一个invalidate 的ref并且不能用于画图。drawRect:方法不能手动显示调用,必须通过调用setNeedsDisplay?或 者?setNeedsDisplayInRect,让系统自动调该方法。

2、若使用calayer绘图,只能在drawInContext:?中(类似于drawRect)绘制,或者在delegate中的相应方法绘制。同样也是调用setNeedDisplay等间接调用以上方法

3、若要实时画图,不能使用gestureRecognizer,只能使用touchbegan等方法来调用setNeedsDisplay实时刷新屏幕

initWithNibName

initWithNibName这个方法是在controller的类在IB中创建,但是通过Xcode实例化controller的时候用的.

initWithCoder

initWithCoder 是一个类在IB中创建但在xocde中被实例化时被调用的.比如,通过IB创建一个controller的nib文件,然后在xcode中通过 initWithNibName来实例化这个controller,那么这个controller的initWithCoder会被调用.或者是一个view的nib文件,类似方法创建时调用initWithCoder

关于 initWithNibName 和 loadNibNamed 的区别和联系

之所以要把这两者来一起讲,我觉的我也有点困惑,到底用那种?其实真正搞清楚了他们之间的差别,就不会这么迷惘了。因为这两个方法,根本就不是一路货色。

既然,是要说明这2个方法,那就着重将区别吧。

但是第一步,还是要罗嗦一下,他们的联系:可以使用此方法加载用户界面(xib文件)到我们的代码中,这样,可以通过操作这个加载进来的(xib)对象,来操作xib文件内容。

下面进入主题,谈区别:

1. ShowViewController的initWithNibName方法

ShowViewController * showMessage = [[ShowViewController alloc]

initWithNibName:@"ShowViewController" bundle:nil];

self.showViewController = showMessage;

2.VideoCellController的loadNibNamed方法

NSArray * nib = [[NSBundle mainBundle] loadNibNamed:@"SaveViewController"

owner:self options:nil] ;

self.showViewController = [nib lastObject];

[nib objectAtIndex:0];

总结:

只看他们初始化,那可能感觉是一样的。但是如果,打开分别看xib的关系的时候,才恍然大悟,原来他们的集成类都不一样。

1. initWithNibName要加载的xib的类为我们定义的视图控制器类

2.加载方式不同

initWithNibName方法:是延迟加载,这个View上的控件是 nil 的,只有到 需要显示时,才会不是 nil

loadNibNamed方法:即时加载,用该方法加载的xib对象中的各个元素都已经存在。

(认真理解这句帮规:when using loadNibNamed:owner:options:, the File's Owner should be NSObject, the main view should be your class type, and all outlets should be hooked up to the view, not the File's Owner.)

initWithCoder和initWithFrame的区别

initWithoder 是当从nib文件中加载对象的时候会调用,比如你的view来自nib那么就会调用这个view的这个函数。(由框架调用)

initWithFrame (是由用户调用,来初始化对象的)

//当你通过storyboared或者xib中创建控件就会调用这个方法

- (id)initWithCoder:(NSCoder *)aDecoder

推荐阅读更多精彩内容