美文网首页
控制器加载过程及UI初始化

控制器加载过程及UI初始化

作者: 简_爱SimpleLove | 来源:发表于2017-03-20 17:35 被阅读146次

    控制器加载过程

    项目过程中,控制器可以看作是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;

    注:

    1. init 封装了initWithNibName方法
    2. 创建类的时候使用eocViewCtl = [[EOCViewContrl alloc] initWithNibName:nil bundle:nil]代替 eocViewCtl = [[EOCViewContrl alloc] init];可以跳过init方法中的一些逻辑而直接通过initWithNibName方法进行创建。
      initWithNibName方法中name和bundle都传nil时,name会默认找与类名相同的nib文件,而bundle则是默认在mainBundle中去寻找。
    3. 如果是通过xib文件创建的控制器,则最好不要重写loadView方法,因为只要你重写,就会走这个方法,也就会覆盖掉你在xib中进行的操作。如果重写了loadView方法过后,想要加载Xib,就必须指定xib文件。

    SecondViewCtr *vctr = [[SecondViewCtr alloc] initWithNibName:@"SecondViewCtr" bundle:nil];

    1. 就算我们不重写loadView,系统也肯定会走,只是当它发现有xib的时候,他就通过xib来进行创建,如果没有就通过别的方式进行创建。
    2. self.view 是懒加载的模式,懒加载在view的get方法中执行了loadview和viewDidLoad,如果你重写loadView,并且在viewDidLoad中又用到self.view,则会形成循环,无限走loadView和viewDidLoad,直至崩溃。类似于这样的代码。
        if (!_view) {
            [self loadView];
            [self viewDidLoad];
        }
    return _view;
    
    1. 当我们需要进行项目拆分的时候,可以重写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:
    以下情况会被调用:

    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不提倡

    方法使用注意点:

    1. 若使用UIView绘图,只能在drawRect:方法中获取相应的contextRef并绘图。如果在其他方法中获取将获取到一个invalidate 的ref并且不能用于画图。drawRect:方法不能手动显示调用,必须通过调用setNeedsDisplay 或 者 setNeedsDisplayInRect,让系统自动调该方法。
    2. 若使用calayer绘图,只能在drawInContext: 中(类似鱼drawRect)绘制,或者在delegate中的相应方法绘制。同样也是调用setNeedDisplay等间接调用以上方法
    3. 若要实时画图,不能使用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 整理摘录

    相关文章

      网友评论

          本文标题:控制器加载过程及UI初始化

          本文链接:https://www.haomeiwen.com/subject/eifynttx.html