前言
总的而言,就我这将近一年的工作经历来说,成长真的是远远不足。
如果突然有天公司把我辞退,想要通过社招找到一份称心的工作,以我现在的水平来说可能真的是有点痴心妄想了。
出于这种虚无的紧迫感吧,尝试学习/复习/巩固一下iOS开发的知识点,以其在未来不至于那么被动吧。
全篇图文基本自网络,属于知识总结,如有错漏欢迎指出
UIView与CALayer
(一)
UIView是iOS系统中界面元素的基础,CALayer管理UIView的绘图部分,所以UIView本身更像是一个CALayer的管理器,访问它的跟绘图和跟坐标有关的属性,例如frame,bounds等等, 实际上内部都是在访问它所包含的CALayer的相关属性
(二)
UIView为CALayer提供内容,继承自UIResponder,负责处理触摸等事件,参与响应链
CALayer负责显示内容contents
UIView 是 CALayer 的CALayerDelegate
(三)
UIView有一个layer属性,返回主CALayer实例。通过重载UIView的layerClass方法可以让UIView使用不同的CALayer
- (class) layerClass {
return ([CAEAGLLayer class]);
}
CALayer类似UIView的subView树形结构,也可以在layer上添加subLayer完成特殊的显示
grayCover = [[CALayer alloc] init];
grayCover.backgroundColor = [[[UIColor blackColor] colorWithAlphaComponent:0.2] CGColor];
[self.layer addSubLayer: grayCover];
(四)
CALayer具有隐式动画:在修改CALayer(非根层)的属性的时候,会自动的产生一些动画,但是在修改UIView的属性的时候并不能产生动画。主要表现在:1.bounds 在修改CALayer的宽度和高度的时候,会产生动画效果;2. backgroundColor 3.position 修改这个属性,会产生平移动画。
事件传递与视图响应链
总结起来一句话:通过事件传递找到响应事件的起点,通过视图响应链找到处理事件的终点。
(一)事件传递两个核心方法
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;
第一个方法返回一个UIView,是用来寻找哪一个视图响应这个事件
第二个方法用来判断某一个点击的位置是否在视图范围内
(二)事件传递的流程
事件传递流程描述
- 点击屏幕产生触摸事件,系统将事件加入到一个由UIApplication管理的事件队列中,UIApplication会从消息队列里取事件分发下去,首先传给UIWindow
- UIWindow调用hitTest:withEvent:方法寻找响应的视图,其中就会用到pointInside: withEvent:去判断当前点击的point是否在UIWindow范围内,如果是,就去遍历它的子视图来查找最终响应的子视图
- 子视图按倒序遍历,也就是说最后添加的子视图会最先遍历。在每一个视图中都回去调用它的hitTest:withEvent:方法(同样通过pointInside: withEvent:去判断当前点击的point是否在子视图范围内),可以理解为是一个递归调用
- 最终返回一个响应视图,如果返回视图有值,那么这个视图就作为最终响应视图,结束整个事件传递;如果没有值,那么就会将UIWindow作为响应者(?)
(三)hitTest:withEvent:
hitTest:withEvent:流程描述(不重写的情况下)
- 首先判断视图的hiden属性、是否可以交互以及透明度是否大于0.01,如果满足条件则进入下一步,否则返回nil
- 调用pointInside: withEvent:方法来判断这个点是否在当前视图范围内,如果满足条件则进入下一步,否则返回nil
- 然后以倒序方式遍历子视图,每个视图调用hitTest:withEvent:,如果有最终响应视图,那么就将这个视图返回给当前视图,否则当前视图作为最终响应视图返回。
注意:如果Touch位置超过了视图边界,hitTest:withEvent方法将忽略这个视图和它的所有子视图。也就是说,即使子视图超过了视图边界(视图的ciipsToBounds属性为NO)并且包含了发生的Touch也不会返回自己。
(四)重写方法
重写事件传递的hitTest:withEvent:方法可以让视图修改返回给上一级的视图对象,需要考虑是否还需要调用super方法来获得子视图的结果。比如点击一个透明的容器视图,如果点击到没有子视图的部分,可以返回nil来让上一级继续事件传递。
重写pointInside: withEvent:则可以让视图针对自己的点击事件返回自定义的值。比如在点击到按钮的透明部分时返回NO,认为没有点击到按钮。
(五)视图响应链流程
事件传递和响应过程是相反的。
如果hitTest:withEvent:找到了第一响应者initial view,但是该响应者没有处理该事件,那么事件会沿着响应者链向上传递:第一响应者 -> 父视图 -> 视图控制器,如果传递到最顶级视图还没处理事件,那么就传递给UIWindow去处理,若window对象也不处理那么就交给UIApplication处理,如果UIApplication对象还不处理,就丢弃该事件(但是并不会引起崩溃)
(六)视图响应的系统回调方法
能够响应事件的对象都是UIResponder的子类对象(之前说过UIView继承自UIResponder)
UIResponder提供了四个用户点击的回调方法,分别对应用户点击开始、移动、点击结束以及取消点击