UIView

1,417 阅读8分钟

一、UIView生命周期:

- (instancetype)init {
    if (self = [super init]) {
        NSLog(@"%s",__func__);
    }
    return self;
}

// 通过代码创建控件就会调用这个方法
- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        NSLog(@"%s",__func__);
    }
    return self;
}

// 通过storyboared或者xib中创建控件就会调用这个方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder]) {
        NSLog(@"%s",__func__);
    }
    return self;
}

- (void)awakeFromNib {
    [super awakeFromNib];
    NSLog(@"%s",__func__);
}

// 如果在initWithFrame中添加子视图会调用两次
- (void)layoutSubviews {
    [super layoutSubviews];
    NSLog(@"%s",__func__);
}

// 当视图添加子视图时调用
- (void)didAddSubview:(UIView *)subview {
    [super didAddSubview:subview];
    NSLog(@"%s",__func__);
}

// 当子视图从本视图移除时调用
- (void)willRemoveSubview:(UIView *)subview {
    [super willRemoveSubview:subview];
    NSLog(@"%s",__func__);
}

// 当视图即将加入父视图时 / 当视图即将从父视图移除时调用
- (void)willMoveToSuperview:(nullable UIView *)newSuperview {
    [super willMoveToSuperview:newSuperview];
    NSLog(@"%s",__func__);
}

// 当试图加入父视图时 / 当视图从父视图移除时调用
- (void)didMoveToSuperview {
    [super didMoveToSuperview];
    NSLog(@"%s",__func__);
}

// 当视图即将加入父视图时 / 当视图即将从父视图移除时调用
- (void)willMoveToWindow:(nullable UIWindow *)newWindow {
    [super willMoveToWindow:newWindow];
    NSLog(@"%s",__func__);
}

// 当视图加入父视图时 / 当视图从父视图移除时调用
- (void)didMoveToWindow {
    [super didMoveToWindow];
    NSLog(@"%s",__func__);
}

- (void)removeFromSuperview {
    [super removeFromSuperview];
    NSLog(@"%s",__func__);
}

- (void)dealloc {
    NSLog(@"%s",__func__);
}

view 创建时

2021-02-07 16:20:58.206772+0800 demo[52526:3469833] -[AddView initWithFrame:]
2021-02-07 16:20:58.207088+0800 demo[52526:3469833] -[AddView willMoveToWindow:]
2021-02-07 16:20:58.207219+0800 demo[52526:3469833] -[AddView willMoveToSuperview:]
2021-02-07 16:20:58.207560+0800 demo[52526:3469833] -[AddView didMoveToWindow]
2021-02-07 16:20:58.207702+0800 demo[52526:3469833] -[AddView didMoveToSuperview]
2021-02-07 16:20:58.208550+0800 demo[52526:3469833] -[AddView layoutSubviews]

view 销毁时

2021-02-07 16:22:13.914086+0800 demo[52526:3469833] -[AddView willMoveToSuperview:]
2021-02-07 16:22:13.914477+0800 demo[52526:3469833] -[AddView willMoveToWindow:]
2021-02-07 16:22:13.914967+0800 demo[52526:3469833] -[AddView didMoveToWindow]
2021-02-07 16:22:13.915201+0800 demo[52526:3469833] -[AddView didMoveToSuperview]
2021-02-07 16:22:13.915409+0800 demo[52526:3469833] -[AddView removeFromSuperview]
2021-02-07 16:33:26.590064+0800 demo[52544:3471086] -[AddView dealloc]

注意: 可以看出上面方法中只会执行一次的方法有 removeFromSuperviewdealloc 两个方法,layoutSubviews 在子视图布局变动时会多次调用,所以可以在 removeFromSuperviewdealloc 这两个方法中执行释放内存等操作,比如移除观察者,定时器等。

view 添加子视图时

2017-11-06 10:45:47.749310+0800 iOSLife[17198:14898063] -[View initWithFrame:]
2017-11-06 10:45:47.749310+0800 iOSLife[17198:14898063] -[View init]
2017-11-06 10:45:47.749310+0800 iOSLife[17198:14898063] -[TestView initWithFrame:]
2017-11-06 10:45:47.749310+0800 iOSLife[17198:14898063] -[TestView init]
2017-11-06 10:45:47.749310+0800 iOSLife[17198:14898063] -[TestView willMoveToSuperview:]
2017-11-06 10:45:47.749310+0800 iOSLife[17198:14898063] -[TestView didMoveToSuperview]
2017-11-06 10:45:47.749310+0800 iOSLife[17198:14898063] -[View didAddSubview:]
2017-11-06 10:45:47.749310+0800 iOSLife[17198:14898063] -[View willMoveToSuperview:]
2017-11-06 10:45:47.749310+0800 iOSLife[17198:14898063] -[View didMoveToSuperview]
2017-11-06 10:45:47.749310+0800 iOSLife[17198:14898063] -[View willMoveToWindow:]
2017-11-06 10:45:47.749310+0800 iOSLife[17198:14898063] -[TestView willMoveToWindow:]
2017-11-06 10:45:47.749310+0800 iOSLife[17198:14898063] -[TestView didMoveToWindow]
2017-11-06 10:45:47.749310+0800 iOSLife[17198:14898063] -[View didMoveToWindow]
2017-11-06 10:45:47.749310+0800 iOSLife[17198:14898063] -[View layoutSubviews]
2017-11-06 10:45:47.749310+0800 iOSLife[17198:14898063] -[View layoutSubviews]
2017-11-06 10:45:47.749310+0800 iOSLife[17198:14898063] -[TestView layoutSubviews]

注意:didAddSubview:willRemoveSubview: 需要有子视图才能执行。

此时再销毁该 view

2017-11-06 10:46:28.022473+0800 iOSLife[17198:14898063] -[View willMoveToWindow:]
2017-11-06 10:46:28.022473+0800 iOSLife[17198:14898063] -[TestView willMoveToWindow:]
2017-11-06 10:46:28.022473+0800 iOSLife[17198:14898063] -[TestView didMoveToWindow]
2017-11-06 10:46:28.022473+0800 iOSLife[17198:14898063] -[View didMoveToWindow]
2017-11-06 10:46:28.022473+0800 iOSLife[17198:14898063] -[View willMoveToSuperview:]
2017-11-06 10:46:28.022473+0800 iOSLife[17198:14898063] -[View didMoveToSuperview]
2017-11-06 10:46:28.022473+0800 iOSLife[17198:14898063] -[View removeFromSuperview]
2017-11-06 10:46:28.022473+0800 iOSLife[17198:14898063] -[View dealloc]
2017-11-06 10:46:28.022473+0800 iOSLife[17198:14898063] -[View willRemoveSubview:]
2017-11-06 10:46:28.022473+0800 iOSLife[17198:14898063] -[TestView willMoveToSuperview:]
2017-11-06 10:46:28.022473+0800 iOSLife[17198:14898063] -[TestView didMoveToSuperview]
2017-11-06 10:46:28.022473+0800 iOSLife[17198:14898063] -[TestView removeFromSuperview]
2017-11-06 10:46:28.022473+0800 iOSLife[17198:14898063] -[TestView dealloc]

willRemoveSubview 是在 dealloc 后面执行的。如果有多个子视图,willRemoveSubview 会循环执行,直到移除所有子视图。

注意:

- (void)willMoveToSuperview:(nullable UIView *)newSuperview;
- (void)willMoveToWindow:(nullable UIWindow *)newWindow;

这俩个方法可以根据参数判断,nil 则为销毁,否则为创建;

- (void)didMoveToSuperview;
- (void)didMoveToWindow;

这个方法可以根据 self.superview 判断,nil 则为销毁,否则为创建。 如何绘制UIView:

二、绘制

绘制一个UIView最灵活的方法就是由它自己完成绘制。实际上你不是绘制一个UIView,而是子类化一个UIView并赋予绘制自己的能力。当一个UIView需要执行绘制操作时,drawRect:方法就会被调用,覆盖此方法让你获得绘图操作的机会。当drawRect:方法被调用,当前图形的上下文也被设置为属于视图的图形上下文,你可以使用 Core Graphic 或者 UIKit 提供的方法将图形画在该上下文中。

ExclusiveTouch属性:

可以使用 appearance 在APPDelegate中对UIView进行统一的设置。

[[UIView appearance] setExclusiveTouch:YES];

这个属性主要是解决多个控件同时响应事件的问题,将exclusiveTouch设置为YES的话可以阻止同一个window中其他控件与他响应,默认为NO。 举个例子:页面中有按钮ButtonA 和 ButtonB,点击ButtonA会push该页面,点击ButtonB则会pop该页面,如果同时点击这个按钮的话页面就会连续出现pop和push的效果。如果设置了exclusiveTouchYES的话则可以避免发生这种事情。

检测一个视图是否属于另一个的子视图: isDescendantOfView:

UIView的 setNeedsDisplay 和 setNeedsLayout 方法都是异步执行的。 而setNeedsDisplay会调用自动调用drawRect方法,这样可以拿到 UIGraphicsGetCurrentContext,就可以绘制了,而setNeedsLayout会默认调用layoutSubViews,就可以处理子视图中的一些数据。

1.计算阶段 - (void)updateConstraints;

布局可以写在updateConstraints中,并且必须调用 [super updateViewConstraints] 苹果不建议把初始化的约束写在这个方法里,原因如下: (1) 当视图约束被更新的时候(一般是被setNeedsUpdateConstraints标记更新), updateConstraints这个方法会被调用,如果里面包含大量的约束,系统就需要去判断是否已经存在相同的约束。 (2) 当前view不一定拥有所有的约束,其他view可能已经向该view添加了部分约束。 (3) 如果在点击事件中触发修改约束的行为,修改布局的代码和触发更新的代码不再同一处,这会让逻辑变得难以遵循。

仅需要更新约束的这部分代码写到updateViewConstraints,大量的初始化约束写到类似于 init、viewDidLoad中。

2.布局阶段 - (void)layoutSubviews;

此方法由系统调用,被调用时,系统已经计算好view对应的frame,如果需要修改布局,通过重写这个方法,并在方法体里修改frame,但是在方法里需要注意。 (1) 在方法里必须调用 super.layoutSubviews() (2) 不能在方法里修改约束,修改约束会触发系统重新计算布局,可能会导致布局错乱 (3) 不能在方法里调用setNeedsUpdateConstraints()、setNeedsLayout,可能会导致布局错乱

3.渲染阶段 - (void)drawRect:(CGRect)rect;

当视图在屏幕上出现的时候 -drawRect:方法就会被自动调用。-drawRect:方法里面的代码利用Core Graphics去绘制一个寄宿图,然后内容就会被缓存起来直到它需要被更新

  • (void)setNeedsDisplay,标记需要显示,在下个drawing cycle 调用。