1:事件传递与视图响应链
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event 哪个视图响应了这个事件返回哪个视图
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 某一个点击的位置是否在当前视图范围内如果在的话就返回YES
问题1:事件传递流程?
如下图
问题2:hitTest:withEvent:系统实现
(1)首先会判断该视图自身能否处理该触摸事件(alpha、userInteractionEnabled、hidden)
(2)调用pointInside:point withEvent:event判断该点是否在显示区域中,如果不在返回nil
(3)如果(2)是YES则倒叙遍历所有的子视图,(注:subViews数组的排列顺序是按照子视图添加到父视图的顺序排列的), 并转化该点到子视图坐标系中。安装这样的顺序利用递归继续查询子视图的子视图,直到没有子视图了。返回最终的View,如果View不为nil,则返回最终可以响应的View
(4)如果(3)中没有子视图就返回自身
问题3:视图事件响应?
传递当前视图-传递到父视图-UIWindows-UIApplication-如果在找不到就忽视掉
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
2.0:滑动优化方案和思路
CPU:
对象创建,调整,销毁可以放到子线程去做,这样可以节省一部分cpu的时间
预排版(布局计算,文本计算)可以放到子线程去做,这样主线程就会有更多时间去响应用户的交互
预渲染(文本等异步绘制,图片编解码)
GPU:
纹理渲染
视图混合
3.0:UIView的绘制原理
问题1:系统绘制流程
如下图
问题2:异步绘制
[layer.delegate displayLayer:]
代理负责生成对应的bitmat
设置该bitmap作为layer.contents属性的值
问题3:UIView和CALayer之间的关系是怎样的?
UIView负责事件传递和响应的
CALayer负责视图显示工作
用到了6大设计模式中的单一职责原则
4:离屏渲染
在屏渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区进行
离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作
问题1:什么是离屏渲染 ?
当我们指定了UI视图的某些属性,标记为它在预合成之前不能用于当前屏幕上面直接显示的时候就会触发离屏渲染,而离屏渲染在GPU层面指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作
问题2:何时触发?
圆角(当和maskToBounds一起使用时)
图层蒙版
阴影
光栅化
问题3:为何要避免?
离屏渲染会创建新的渲染缓冲区,增加开销。
因为有多通道渲染管线最终需要把多通道渲染结果最终的合成,那需要上下文的切换,就会产生GPU额外的开销
总结:在触发离屏渲染的时候,会增加GPU的工作量,很有可能导致CPU和GPU加起来的总耗时超出16.7ms,那么可能就会导致UI的卡顿和掉帧,所以我们需要避免离屏渲染。
问题4:触摸事件
白色view是gray和yellow的父视图,gray是red和blue的父视图
单看查找顺序:
首先uiapplication分配任务到uiwindow然后在到viewController中的view
而后view会以倒叙的方式遍历子视图,yellow是最后添加到view中的,所以yellow首先被打印出来,由于touches不在yellow视图中返回nil,
不在yellowView中,继续判断是否在其grayView 中,如果是在grayView 中,按照倒叙遍历其子视图。在grayView中,首先判断blueView,正好点击的是在blueView,响应事件。
继承自UIResponder的类都可以称为响应者。UIViewcontroller和view都是继承自UIResponder的子类,而UIResponder有一个属性
@property(nonatomic, readonly, nullable) UIResponder *nextResponder;
问题5:手势总结
1、手势和pointInside以及hitTest的关系:手势的识别,必须先通过pointInside、hitTest找到这个view
2、通过hitTest、pointInside找到的view:如果view或者它的superView有手势事件,都会响应(父view有手势子view也会响应,子view有手势,父view不会响应)
3、手势的种类怎么分辨出来:tap、pangesture、swipeGesture:根据手势的四个touch方法来识别出种类的
4、手势和view的touch事件的关系:delayTouchBegin、cancelTouchInView
默认情况下,如果手势识别出来了,会cancel掉view的touch响应链
// 是否允许触发当前手势
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
问题6:UIButton超过frame的部分可以点击
重写UIButton的pointInside:withEvent:方法
如果button的size小于{50,50},那么只要点击在{50,50}区域范围内的事件都能响应
问题7:UIView超过俯视图的部分也可以响应
redView是grayView的子视图
分别点击1、2、3处
点击1处:点1,不在grayView的范围内,所以-pointInside:withEvent:(UIEvent *)event返回NO,所以点击1处的redView的touch事件不会响应,lightgray也不会相应
点击2处:点2,在grayView的范围内,不在redView内,但是根据查找原则还是会去redView中判断,结果返回的是nil
点击3处:点3,在两个View的内
第一种方法:重写grayView的-hitTest:withEvent:
方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(@"%s",__func__);
//逆序遍历子视图
for (UIView *subView in [self.subviews reverseObjectEnumerator]) {
//将subview中的点转换到self坐标系中的点
CGPoint covertPoint = [subView convertPoint:point toView:self];
//调用父类的super方法,依据查找原理,返回被点击的view
UIView *hitView = [super hitTest:covertPoint withEvent:event];
if (hitView) {
return hitView;
}
}
return [super hitTest:point withEvent:event];
}
将点击在子视图上点转换到其父视图坐标系中的点。
第二种方法:重写grayView的- (BOOL)pointInside: withEvent:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
for (UIView *subView in [self.subviews reverseObjectEnumerator]) {
CGPoint convertPoint = [self convertPoint:point toView:subView];
if ([subView pointInside:convertPoint withEvent:event]) {
return YES;
}
}
return [super pointInside:point withEvent:event];
}