事件响应
入门阶段,你只要学会 Target-Action 即可,基本能满足简单的交互要求
- 如果使用 Xib/Storyboard,直接拖拽添加 IBAction 方法即可
- 如果纯代码,那么需要调用控件的 addTarget:action:forControlEvents: 方法
这两种方式是等价的,与前端 addEventListenter 的方式也很类似
不过你会发现,很多控件是没有 addTarget:action:forControlEvents: 方法的(比如 UIImageView),因为这个方法源自 UIControl,真正的继承结构其实是这样的: UIButton < UIControl < UIView
那如何为 UIImageView 之类的控件添加触摸事件呢?可以使用 UIGestureRecognizer,请看最常用的 UITapGestureRecognizer 子类
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(imgTapped:)];
[imgView addGestureRecognizer:tapRecognizer];
imgView.userInteractionEnabled = YES; // 对于 UIImageView 默认是 NO
可以从 UIGestureRecognizer 的文档中发现更多的子类,涵盖了各种常见手势,比如 Tap/Pinch/Swipe 等等。相比于前端还需要自己通过组合 TouchUp/TouchMove 之类的原始事件来实现手势,iOS 显得方便的多。
学会以上两种方式就能解决很多问题了,不过内在的原理是怎样的呢?有没有类似事件冒泡的机制呢?UITapGestureRecognizer 与 UIControlEventTouchUpInside 有没有关系呢?
想要搞懂这些问题,我推荐仔细阅读这几篇文档: Event Delivery: The Responder Chain , Gesture Recognizers
下面我对原理的简单总结:
- 用户触摸开始/移动/结束,都会产生 UITouch 事件
- 系统会首先寻找事件对应的 UIView,方法是从外向里通过 hit-test 链(类似捕获)寻找
- 找到对应的 UIView 之后,会根据事件的阶段调用相应方法,如 touchesBegan:withEvent:,方法内如果调用了 super,那么系统会寻找 nextResponder,通常是父 View 或者其对应的 ViewController,再调用相应的 touches 处理方法,这就是从内向外的 Responder 链 (类似冒泡)
- 如果 UIView 没有调用 super,那么事件不再继续传递(类似 stopPropagation)
- UIGestureRecognizer 与 UIControl 实际上是对上述机制的更高级封装(手势事件),例如从 UIControlEventTouchUpInside 的名字就可以大概推测出,他其实通过组合 TouchBegin 和 TouchEnd 来封装出的事件(像不像 Zepto 里的 tap 事件封装?不过 iOS 这是内置的)
- 为 UIView 添加了 UIGestureRecognizer 之后,UITouch 事件会交给 UIGestureRecognizer,而不再交给 UIView,因此添加了 UITapGestureRecognizer 之后,UIButton 的 UIControlEventTouchUpInside 就不会触发了