控制器加载过程
项目过程中,控制器可以看作是UIView的管理者,而且UIView可以看作是CALayer的管理者
一、控制器 初始化方法(是否有xib)
代码和xib的区别(普通代码构建,直接使用代码CODE而不用翻译XIB中的数据(xml),省了一个步骤)
1. init 和 initWithNibName
init底层执行了initWithNibName(默认加载)
首先去找有没有相关的xib文件,没有就执行loadview创建view, 有就不创建
2. loadview方法 (底层实现)
注意:xib的情况下,在viewDidLoad里面加载的view还是xib上大小的view,执行完才会适配(两种autolayout, autosize)
self.view 如果view 为空,那么self.view的get方法,会进入一个死循环
- -(instancetype)init;
- -(instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil;
- -(void)loadView;
- -(void)viewDidLoad;
- -(void)viewWillAppear:(BOOL)animated;
- -(void)viewDidAppear:(BOOL)animated;
注:
- init 封装了initWithNibName方法
- 创建类的时候使用
eocViewCtl = [[EOCViewContrl alloc] initWithNibName:nil bundle:nil]
代替eocViewCtl = [[EOCViewContrl alloc] init]
;可以跳过init方法中的一些逻辑而直接通过initWithNibName方法进行创建。
initWithNibName方法中name和bundle都传nil时,name会默认找与类名相同的nib文件,而bundle则是默认在mainBundle中去寻找。 - 如果是通过xib文件创建的控制器,则最好不要重写loadView方法,因为只要你重写,就会走这个方法,也就会覆盖掉你在xib中进行的操作。如果重写了loadView方法过后,想要加载Xib,就必须指定xib文件。
SecondViewCtr *vctr = [[SecondViewCtr alloc] initWithNibName:@"SecondViewCtr" bundle:nil];
- 就算我们不重写loadView,系统也肯定会走,只是当它发现有xib的时候,他就通过xib来进行创建,如果没有就通过别的方式进行创建。
- self.view 是懒加载的模式,懒加载在view的get方法中执行了loadview和viewDidLoad,如果你重写loadView,并且在viewDidLoad中又用到self.view,则会形成循环,无限走loadView和viewDidLoad,直至崩溃。类似于这样的代码。
if (!_view) {
[self loadView];
[self viewDidLoad];
}
return _view;
- 当我们需要进行项目拆分的时候,可以重写loadView方法,在里面将控制器的View替换成我们自定义的View,从而在View中实现一些业务逻辑。
UI初始化
二、UI的初始化
代码初始化:
init initWithFrame init封装了initWithFrame
xib初始化:
initWithCoder awakeFromNib
layoutSubviews 什么时候掉用(没有superview是不会掉用,除第一条例外)
1 第一次addsubview(加载到界面)的时候,提前条件有frame
2 修改本身frame的时候(前提是frame的值前后发生了变化)
3 修改子视图的frame的时候(前提是frame的值前后发生了变化)
3 添加子视图的时候
4 滚动一个scrollview的时候
5 旋转屏幕
6 init初始化不会触发layoutSubviews
7 直接调用setLayoutSubviews
layoutSubviews 的实用(UIButton的复用,改变文字和图片的位置)
UILabel 初始化过程, 完成之后_UILabelContentLayer 覆盖了上面所有的子视图层,view管理着CALayer层
UI稳定的点
IB_DESIGNABLE
IBInspectable
drawRect
CALayer
无Xib情况
- -(id)init
- -(id)initWithFrame:(CGRect)frame
- -(void)layoutSubviews
view在initWithFrame中的frame值是不准确的,我们不能在这里面设置它的frame值,只能做一些添加子控件和一些初始化的操作等。只有当view走完initWithFrame方法,才有一个完整的结构。
在label初始化的方法initWithFrame中,添加一个子view
- (id)initWithFrame:(CGRect)frame{
NSLog(@"%s", __func__);
self = [super initWithFrame:frame];
if (self) {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
view.backgroundColor = [UIColor blueColor];
[self addSubview:view];
}
return self;
}
而在初始化label的时候,将背景色设置为其他颜色
eocLabel = [[EOCLabel alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
eocLabel.backgroundColor = [UIColor yellowColor];
[self.view addSubview:eocLabel];
就会发现先添加的blue view被yellow覆盖
这就说明在initWithFrame走完过后,label的layer层才渲染完成,也即是说当走完initWithFrame方法后,view才会有一个完整的结构。但是试了UIButton一些空间,blue并没有被yellow覆盖,这应该是控件的底层实现有些不同。
有Xib情况
- -(id)initWithCoder:(NSCoder *)aDecoder
- -(void)awakeFromNib
- -(void)layoutSubviews
- -(void)drawRect:(CGRect)rect
initWithCoder:相当于我们用代码创建时的initWithFrame方法,只要对象是从文件解析来的, 就会调用这个方法。
awakeFromNib:从xib或者storyboard加载完毕就会调用,但是我们一般不在这里面做操作,当使用一个controller控制多个nib文件时,awakeFromNib方法会被多次调用。因此,当不使用awakeFromNib方法来完成nib对象的初始化时,需要注意此方法的多次调用对其他nib文件造成的影响。
layoutSubviews:不管是用代码创建的View还是用Xib创建的View,我们一般在这个方法里面做很多操作,比如设置准确的frame值等,但是这个方法只要当子控件frame发生变化,或者添加子控件,滚动tableView等,都会调用,所以不应做太复杂和占内存的操作。
drawRect:
以下情况会被调用:
- 如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。drawRect 掉用是在Controller->loadView, Controller->viewDidLoad 两方法之后掉用的.所以不用担心在 控制器中,这些View的drawRect就开始画了.这样可以在控制器中设置一些值给View(如果这些View draw的时候需要用到某些变量 值).
- 该方法在调用sizeToFit后被调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect:方法。
- 通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。
- 直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0。
以上1,2推荐;而3,4不提倡
方法使用注意点:
- 若使用UIView绘图,只能在drawRect:方法中获取相应的contextRef并绘图。如果在其他方法中获取将获取到一个invalidate 的ref并且不能用于画图。drawRect:方法不能手动显示调用,必须通过调用setNeedsDisplay 或 者 setNeedsDisplayInRect,让系统自动调该方法。
- 若使用calayer绘图,只能在drawInContext: 中(类似鱼drawRect)绘制,或者在delegate中的相应方法绘制。同样也是调用setNeedDisplay等间接调用以上方法
- 若要实时画图,不能使用gestureRecognizer,只能使用touchbegan等方法来掉用setNeedsDisplay实时刷新屏幕
Xib可视化
在代码中用到IB_DESIGNABLE和IBInspectable两个关键字,即可使Xib的控件属性,实时的显示在Xib上,而不必每次运行看效果。但是此属性改变最好写在drawRect方法中,应该改变的其实是layer层。
#import <UIKit/UIKit.h>
IB_DESIGNABLE
@interface EOCView : UIView{
}
@property (nonatomic, strong)IBInspectable UIColor *eocColor;
@property (nonatomic, assign)IBInspectable float widthStroke;
@end
.m文件中
- (void)drawRect:(CGRect)rect{
NSLog(@"%s", __func__);
CGFloat centerX = (self.bounds.size.width - self.bounds.origin.x) / 2;
CGFloat centerY = (self.bounds.size.height - self.bounds.origin.y) / 2;
UIBezierPath *path = [[UIBezierPath alloc] init];
// 添加一个圆形
[path addArcWithCenter:CGPointMake(centerX, centerY) radius:50 startAngle:0 endAngle:360 clockwise:YES];
// 设置线条宽度
path.lineWidth = _widthStroke;
// 设置线条颜色
[_eocColor setStroke];
// 绘制线条
[path stroke];
}
这里即便多了两个属性值,我们可以改变这里实时看到效果图
参考文章:
UIView的layoutSubviews和drawRect方法何时调用
awakeFromNib 整理摘录