一.事件的类型和传递
1.UIEventType(Touches,Motion,RemoteControl),UIEventSubType,
2.touch event的传递规则:touchTest,pointInside:withEvent.
以下是我的一些分析:可能有出入
baseView中连续添加viewA,viewB,viewC,viewA中添viewD,viewD中添加viewE;
先测试hittest:
例子A:viewA,viewB,viewC都实现了 hittest,假设直接返回self,则点击baseView任何地方,则响应viewC,因为是第一层的最上层,如果viewC的hittest返回nil,则传递给viewB,则viewB响应,以此类推。当然你可以再对应方法中调用super hitTest方法,这样的话就是系统默认的方式去判断返回的view是哪个,如果不实现pointInView,则是返回点中的view.顺便说一句,如果baseView实现hitTest并且返回self,则直接响应baseView了,返回nil,则结束了,不会去自动寻找下层。hitTest是先寻找当前层,再寻找下一层的。此外假设viewA超过了baseView,则点击baseView外地方,则没有任何响应。点击viewA也是。
例子B:viewA,viewB,viewC都实现 hitTest并调用super hitTest或者干脆就不实现,viewD,viewE实现了pointInside方法,当viewD返回true时,则viewE也会触发以此pointInside方法,如果返回YES,则viewE触发touch,返回NO,则viewD触发touch,如果viewD方法中返回NO,则viewA触发了touch。
结论1:调用super hitTest其实就是按照事件传递的基本规则去寻找事件的响应者,在你自己实现了的方法中不调用super方法,则不会去传递到下一层,所以当baseView实现hitTest但是返回nil则没有调用subview的hittest,但是如果viewC返回nil,则还会调用viewB,因为当baseView没有实现hitTest方法时,则会默认执行[super hittest].
结论2:pintInside和hitTest传递的方式都是从上层传递到下层的捕获方式的传递,当pointInside返回yes,则会判断其子view是否返回yes,如果有,则再继续寻找,直到都返回no,或者返回yes,并且已经是最后一层,才能确定该view是触发view.而hitTest则是调用super hitTest方法来触发这样的传递,如果你返回的view不是你想要的view,还可以决定其他view来作为触发的view,反正最后关键是看方法返回的是哪个UIView作为触发对象。
3.first responder是指第一个处理应用事件的对象(通常是UIView),这些事件不仅仅是touch事件
a)Motion events
b)remote-control events
c)Action messages:用户在应用中创建了一个control(button或者slider),并且没有为其指定target.
d)editing-menu message:
e)text-editing:UIKit会自动将text field或者text view 设置成first responder
二.多触摸事件
1.UIEvent提供了整个事件过程中的touch事件a)allTouches
b)touchesForWindow:返回针对某个window的touch对象
c)touchesForView:返回针对某个view的touch对象
2.UITouch某个touch事件
a)anyObject:返回一个touch对象,NSSet是一个无序的集合,他是用hashcode来保存数据的,不同于NSArray
b)locationInView,previousLocationView:传递self的话,则是返回这个view中的位置
c)tapCount:
d)timstamp
3.touch相关的一些方法
a)userInteractionEnabled
b)UIApplication的beginIgnoringInteractionEvents和endIgnoringInteractionEvents
c)multipleTouchEnabled
d)exclusiveTouch
e)hitTest:withEvent来限制多点触摸事件
4.处理复杂的多触摸事件
a)multipleTouchEnabled=YES
b)要用Core Foundation Dictionary (CFDictionaryRef)来跟踪touches以你为UITouch是没有遵循NSCopying协议,所以不能使用NSDictionary
if ([touches count] > 0) {
for (UITouch *touch in touches) {
CGPoint *point = (CGPoint *)CFDictionaryGetValue(touchBeginPoints, touch);
if (point == NULL) {
point = (CGPoint *)malloc(sizeof(CGPoint));
CFDictionarySetValue(touchBeginPoints, touch, point);
}
*point = [touch locationInView:view.superview];
}
}
从touchBeginPoints查看是否有touch为key的value,如果没有,则创建它,然后在touch上设置该point指针,注意这个是指针,真正赋值的是*point=[touch……],注意给赋值的时候是需要加一个*来赋值。
5.如何判断多触摸事件的结束
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
if ([touches count] == [[event touchesForView:self] count]) {
// last finger has lifted....
}
}
6.转发touch events,sendEvent(这个可能需要进一步获取资料)
7.其他一些注意点有助于处理触摸事件
a)总是实现event-cancel method,因为你的真个触摸事件流程中可能都会遇到cancel的情况,比方说接到一个邮件或者call.你所要做的就是恢复之前的状态
b)如果你的event方法是是现在一个UIView,UIViewController或者很少见的UIResponder的子类的话,你需要实现所有touch方法,不要调用super方法
如果是其他UIKit responder class,比如UIImageView,UISlider,则不用实现所有touch方法,但是需要调用super对应的方法
c)不要往其它UIKit框架对象转发事件,而是只有你自己的custom View of UIView
d)所有事件方法中不要涉及绘制部分,只需要标记状态,所有绘制代码应该让drawRect来帮助实现
三.手势
1.在发送touch事件的结束阶段(Ended Phase)到hit-test view前,window对象会将它发送到手势识别器所涉及的view及其subviews
2.locationInView:和locationOfTouch:inView
3.处理多个手势并存的情况
a)接受另外一个手势前需要另外一个手势失败:
比如说单击手势是需要双击手势确定失败后才执行:[singleTap requireGestureRecognizerToFail:doubleTap];
b)通过分析多触摸touch事件来阻止某些手势识别器
UIGestureRecognizerDelegate里
gestureRecognizerShouldBegin:当一个手势识别器准备将状态标识为UIGestureRecognizerStatePossible前调用,如果返回NO,则标记为失败
gestureRecognizer:shouldReceiveTouch:这个方法是在window调用touchesBegan事件前调用,返回NO,可以阻止guesture接收这些touch事件
此外还有在UIGestureRecognizerSubclass.h中的2个方法类似
-(BOOL)canPreventGestureRecognizer:(UIGestureRecognizer*)preventedGestureRecognizer;
-(BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer*)preventingGestureRecognizer;
c)允许多个手势识别
UIGestureRecognizerDelegate里gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
4.touches事件和手势事件的流程
假设一个2指的操作:
a)发送2个began phase touch事件给识别器,但是识别器不认,则发送给UIView.
b)发送2个move phase touch事件给识别器,识别器依旧无法识别,然后发给UIView.
c)一个手指离开view,发送一个touch事件给ended phase touch事件给识别器,由于识别器资料不够,还是不能确认,不过这个事件被暂时阻止传送给UIView
d)第二个手指离开,又发送了个touch事件,识别器识别出了手势,将状态标记成UIGuestureRecognizerStateRecognized了,view会接受touchesCancelled:withEvent:
假设最后一步识别器没有识别出,则会将2个end phase touch事件发给touchesEnded:withEvent:方法
此外如果是一个连续的手势识别,则这个状态可能会被标识为UIGuestureRecognizerStateBegan,然后所有事件都会发送给手势识别器,而不发送给view了
影响事件的几个参数:
cancelsTouchesInView(默认为YES):NO的话,手势识别失败的话,也不会调用cancel,那么之前的touch事件也不会是无效的了
delaysTouchesBegan(默认为NO) 在手势识别结束前,不会有任何手势事件发送给view
delaysTouchesEnded(默认为YES)如果是NO,原本view接受的顺序是touchesBegan,touchesEnded,touchesBegan,touchesCancelled;YES的话则是touchesBegan,touchesBegan,touchesCancelled,touchesEnded
5.创建自定义手势识别
手势状态的变化(不连续的(如tap),连续的(如pan)),详细代码可以看Event handling Guide或者《iphone4开发基础》。
四.摇动事件
1.声明是first Responder
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)viewDidAppear:(BOOL)animated {
[self becomeFirstResponder];
}
2.实现代理方法
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
self.view.transform = CGAffineTransformIdentity;
for (UIView *subview in self.view.subviews) {
subview.transform = CGAffineTransformIdentity;
}
[UIView commitAnimations];
for (TransformGesture *gesture in [window allTransformGestures]) {
[gesture resetTransform];
}
}
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
}
3.此外我在开发相关程序时以为这个一定要作为rootcontroller才能响应,其实不是的,假设我有一个rootViewController,然后rootViewController里显示了其他controller的View,那么那个controller同样可以成为第一响应者,只是如果该controller里如果不实现motion方法,则会传递上去,那么就会调用rootViewController的motion方法。
4.coreMotion的使用,详细看文档或者看《iphone4开发基础》
大致就是使用陀螺仪,表示方向,加速记表示偏移量。该类服务的使用可以分为推送和主动获取方法。
五.远程控制多媒体
1.声明first responder
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated {
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
[super viewWillDisappear:animated];
}
- (void) remoteControlReceivedWithEvent: (UIEvent *) receivedEvent {
if (receivedEvent.type == UIEventTypeRemoteControl) {
switch (receivedEvent.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
[self playOrStop: nil];
break;
case UIEventSubtypeRemoteControlPreviousTrack:
[self previousTrack: nil];
break;
case UIEventSubtypeRemoteControlNextTrack:
[self nextTrack: nil];
break;
default:
break;
}
}
}