一、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]
注意:
可以看出上面方法中只会执行一次的方法有 removeFromSuperview、dealloc 两个方法,layoutSubviews 在子视图布局变动时会多次调用,所以可以在 removeFromSuperview、dealloc 这两个方法中执行释放内存等操作,比如移除观察者,定时器等。
给 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的效果。如果设置了exclusiveTouch为YES的话则可以避免发生这种事情。
检测一个视图是否属于另一个的子视图: 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 调用。