- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
但是这种方式甄别不同的手势操作实在是麻烦,需要你自己计算做不同的手势分辨。后来。。。
苹果就给出了一个比较简便的方式,就是使用UIGestureRecognizer。
六种手势识别的class:
UITapGestureRecognizer
UIPinchGestureRecognizer
UIPanGestureRecognizer
UISwipeGestureRecognizer
UIRotationGestureRecognizer
UILongPressGestureRecognizer
点击手势响应
单次响应和持续响应
//使用xib创建手势事件 @interface APLGestureRecognizerViewController () @property (nonatomic, strong) IBOutlet UITapGestureRecognizer *tapRecognizer; @end @implementation - (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer // Will implement method later... } @end //通过代码创建 - (void)viewDidLoad { [super viewDidLoad]; // Create and initialize a tap gesture UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(respondToTapGesture:)]; // Specify that the gesture must be a single tap tapRecognizer.numberOfTapsRequired = 1; // Add the tap gesture recognizer to the view [self.view addGestureRecognizer:tapRecognizer]; // Do any additional setup after loading the view, typically from a nib } //处理双击事件响应的方法 - (IBAction)showGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer { // Get the location of the gesture CGPoint location = [recognizer locationInView:self.view]; // Display an image view at that location [self drawImageForGestureRecognizer:recognizer atPoint:location]; // Animate the image view so that it fades out [UIView animateWithDuration:0.5 animations:^{ self.imageView.alpha = 0.0; }]; } //处理左移右移事件 // Respond to a swipe gesture - (IBAction)showGestureForSwipeRecognizer:(UISwipeGestureRecognizer *)recognizer { } // Get the location of the gesture CGPoint location = [recognizer locationInView:self.view]; // Display an image view at that location [self drawImageForGestureRecognizer:recognizer atPoint:location]; // If gesture is a left swipe, specify an end location // to the left of the current location if (recognizer.direction == UISwipeGestureRecognizerDirectionLeft) { location.x -= 220.0; } else { location.x += 220.0; } // Animate the image view in the direction of the swipe as it fades out [UIView animateWithDuration:0.5 animations:^{ self.imageView.alpha = 0.0; self.imageView.center = location; }]; //处理旋转事件 // Respond to a rotation gesture - (IBAction)showGestureForRotationRecognizer:(UIRotationGestureRecognizer *)recognizer { // Get the location of the gesture CGPoint location = [recognizer locationInView:self.view]; // Set the rotation angle of the image view to // match the rotation of the gesture CGAffineTransform transform = CGAffineTransformMakeRotation([recognizer rotation]); self.imageView.transform = transform; // Display an image view at that location [self drawImageForGestureRecognizer:recognizer atPoint:location]; // If the gesture has ended or is canceled, begin the animation // back to horizontal and fade out if (([recognizer state] == UIGestureRecognizerStateEnded) || ([recognizer state] == UIGestureRecognizerStateCancelled)) { [UIView animateWithDuration:0.5 animations:^{ self.imageView.alpha = 0.0; self.imageView.transform = CGAffineTransformIdentity; }]; } }
手势识别几种状态的转化:
UIGestureRecognizerStateRecognized
UIGestureRecognizerStateBegan
UIGestureRecognizerStateChanged
UIGestureRecognizerStateEnded
UIGestureRecognizerStateCancelled
UIGestureRecognizerStateFailed
如果在一个view上有多个手势,可以使用UIGestureRecognizer和它的delegate方法来处理。
手势识别是具有互斥的原则的,比如单击和双击,如果它识别出一种手势,其后的手势将不被识别。可以使用requireGestureRecognizerToFail:方法来取消手势
//Preventing a gesture recognizer from receiving a touch - (void)viewDidLoad { [super viewDidLoad]; // Add the delegate to the tap gesture recognizer self.tapGestureRecognizer.delegate = self; } // Implement the UIGestureRecognizerDelegate method -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { // Determine if the touch is inside the custom subview if ([touch view] == self.customSubview){ // If it is, prevent all of the delegate's gesture recognizers // from receiving the touch return NO; } return YES; }
允许同时手势识别:
gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
如果两个手势有单项关系,就是一个手势阻止另外一个手势,那么使用:
canPreventGestureRecognizer:或者canBePreventedByGestureRecognizer:
在IOS6之后,许多控件只支持单种手势:
UIButton, UISwitch, UIStepper, UISegmentedControl, and UIPageControl支持单击
A single finger swipe on the knob of a UISlider, in a direction parallel to the slider.
A single finger pan gesture on the knob of a UISwitch, in a direction parallel to the switch.
多点触控和触摸阶段:
触摸传送路径:
如果被touch对象被识别到,那么window不会传送给view了。
UIGestureRecognizer有两个属性:
delaysTouchesBegan(默认是NO):让手势识别到这个动作,如果设置成yes,那么view的响应会比较慢
delaysTouchesEnded(默认是YES):手势可能会取消,留足时间。
//定义头文件 #import <UIKit/UIGestureRecognizerSubclass.h> //重写UIGestureRecognizer的方法 - (void)reset; - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; //实现部分 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesBegan:touches withEvent:event]; if ([touches count] != 1) { self.state = UIGestureRecognizerStateFailed; return; } } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesMoved:touches withEvent:event]; if (self.state == UIGestureRecognizerStateFailed) return; UIWindow *win = [self.view window]; CGPoint nowPoint = [touches.anyObject locationInView:win]; CGPoint nowPoint = [touches.anyObject locationInView:self.view]; CGPoint prevPoint = [touches.anyObject previousLocationInView:self.view]; // strokeUp is a property if (!self.strokeUp) { } } // On downstroke, both x and y increase in positive direction if (nowPoint.x >= prevPoint.x && nowPoint.y >= prevPoint.y) { self.midPoint = nowPoint; // Upstroke has increasing x value but decreasing y value } else if (nowPoint.x >= prevPoint.x && nowPoint.y <= prevPoint.y) { self.strokeUp = YES; } else { self.state = UIGestureRecognizerStateFailed; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesEnded:touches withEvent:event]; if ((self.state == UIGestureRecognizerStatePossible) && self.strokeUp) { self.state = UIGestureRecognizerStateRecognized; } } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesCancelled:touches withEvent:event]; self.midPoint = CGPointZero; self.strokeUp = NO; self.state = UIGestureRecognizerStateFailed; } //手势重置 - (void)reset { [super reset]; self.midPoint = CGPointZero; self.strokeUp = NO; }
事件传送,Responder chain:
1. The touch is within the bounds of view A, so it checks subviews B and C.
2. The touch is not within the bounds of view B, but it’s within the bounds of view C, so it checks subviews
D and E.
3. The touch is not within the bounds of view D, but it’s within the bounds of view E.
View E is the lowest view in the view hierarchy that contains the touch, so it becomes the hit-test view.
The responder chain是一系列关联的responder对象
得先重写canBecomeFirstResponder方法,返回YES;
responder对象包括:Touch events、Motion events(sharking)、Remote control events、Action messages、Editing-menu messages、Text editing
The responder chain on iOS
通过方法nextResponder来传递响应
只要是UIResponder的子类都可以处理事件,就像UIView、UIViewController、UIControl、UIApplication or UIWindow,处理类需要实现touch对应的方法,并且设置userInteractionEnabled为YES,而且处理的view等不能为空或者隐藏
多点触碰:
触摸对象的检索和查询:
multipleTouchEnabled属性默认为NO,表示只能响应第一个触碰事件。通过locationInView:获取位置信息,通过allTouches方法获取所有的touch,指定一个Window,使用touchesForWindow:获取所有的touch,指定一个view,使用touchesForView:获取所有的touch
//检测一个双击动作 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { for (UITouch *aTouch in touches) { if (aTouch.tapCount >= 2) { // The view responds to the tap [self respondToDoubleTapGesture:aTouch]; } } } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { } //Tracking a swipe gesture in a view #define HORIZ_SWIPE_DRAG_MIN 12 #define VERT_SWIPE_DRAG_MAX 4 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *aTouch = [touches anyObject]; // startTouchPosition is a property self.startTouchPosition = [aTouch locationInView:self]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *aTouch = [touches anyObject]; CGPoint currentTouchPosition = [aTouch locationInView:self]; // Check if direction of touch is horizontal and long enough if (fabsf(self.startTouchPosition.x - currentTouchPosition.x) >= HORIZ_SWIPE_DRAG_MIN && fabsf(self.startTouchPosition.y - currentTouchPosition.y) <= VERT_SWIPE_DRAG_MAX) { // If touch appears to be a swipe if (self.startTouchPosition.x < currentTouchPosition.x) {[self myProcessRightSwipe:touches withEvent:event]; } else { [self myProcessLeftSwipe:touches withEvent:event]; } self.startTouchPosition = CGPointZero; } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { self.startTouchPosition = CGPointZero; } //Dragging a view using a single touch - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *aTouch = [touches anyObject]; CGPoint loc = [aTouch locationInView:self]; CGPoint prevloc = [aTouch previousLocationInView:self]; CGRect myFrame = self.frame; float deltaX = loc.x - prevloc.x; float deltaY = loc.y - prevloc.y; myFrame.origin.x += deltaX; myFrame.origin.y += deltaY; [self setFrame:myFrame]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { } //Storing the beginning locations of multiple touches - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self cacheBeginPointForTouches:touches]; } - (void)cacheBeginPointForTouches:(NSSet *)touches { 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]; } //Retrieving the initial locations of touch objects - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { CGAffineTransform newTransform = [self incrementalTransformWithTouches:touches]; } - (CGAffineTransform)incrementalTransformWithTouches:(NSSet *)touches { NSArray *sortedTouches = [[touches allObjects] sortedArrayUsingSelector:@selector(compareAddress:)]; // Other code here CGAffineTransform transform = CGAffineTransformIdentity;UITouch *touch1 = [sortedTouches objectAtIndex:0]; UITouch *touch2 = [sortedTouches objectAtIndex:1]; CGPoint beginPoint1 = *(CGPoint *)CFDictionaryGetValue(touchBeginPoints, touch1); CGPoint currentPoint1 = [touch1 locationInView:view.superview]; CGPoint beginPoint2 = *(CGPoint *)CFDictionaryGetValue(touchBeginPoints, touch2); CGPoint currentPoint2 = [touch2 locationInView:view.superview]; // Compute the affine transform return transform; } //Handling a complex multitouch sequence - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // App supports only single touches, so anyObject retrieves just // that touch from touches UITouch *touch = [touches anyObject]; // Move the placard view only if the touch was in the placard view if ([touch view] != placardView) { // In case of a double tap outside the placard view, update // the placard's display string if ([touch tapCount] == 2) { [placardView setupNextDisplayString]; } return; } // Animate the first touch CGPoint touchPoint = [touch locationInView:self]; [self animateFirstTouchAtPoint:touchPoint]; } } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; // If the touch was in the placardView, move the placardView to its location if ([touch view] == placardView) { CGPoint location = [touch locationInView:self]; placardView.center = location; } } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; // If the touch was in the placardView, bounce it back to the center if ([touch view] == placardView) { } } // Disable user interaction so subsequent touches // don't interfere with animation self.userInteractionEnabled = NO; [self animatePlacardViewToCenter]; - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { // To impose as little impact on the device as possible, simply set // the placard view's center and transformation to the original values placardView.center = self.center; placardView.transform = CGAffineTransformIdentity; } //Determining when the last touch in a multitouch sequence has ended - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { if ([touches count] == [[event touchesForView:self] count]) { // Last finger has lifted } }
要处理复杂的手势事件,需要把属性multipleTouchEnabled设置为YES,设置属性exclusiveTouch(默认是NO),不会阻止其他view接受触摸,设置为YES就会阻止其他view接受触碰
B和C都不能接受touch
把userInteractionEnabled设置成NO就关闭了事件的响应链。不过也可以通过beginIgnoringInteractionEvents和endIgnoringInteractionEvents来间断的阻止响应链(一般动画的时候,不需要触碰)
如果要拦截touch,可以重写方法hitTest:withEvent:,不需要实现 touchesBegan:withEvent:, touchesEnded:withEvent:, or touchesMoved:withEvent:
转发touch事件
//转发touch事件 - (void)sendEvent:(UIEvent *)event { for (TransformGesture *gesture in transformGestures) { // Collect all the touches you care about from the event NSSet *touches = [gesture observedTouchesForEvent:event]; NSMutableSet *began = nil; NSMutableSet *moved = nil; NSMutableSet *ended = nil; NSMutableSet *canceled = nil; // Sort touches by phase to handle—-similar to normal event dispatch for (UITouch *touch in touches) { switch ([touch phase]) { case UITouchPhaseBegan: if (!began) began = [NSMutableSet set]; [began addObject:touch]; break; case UITouchPhaseMoved: if (!moved) moved = [NSMutableSet set]; [moved addObject:touch]; break; case UITouchPhaseEnded: if (!ended) ended = [NSMutableSet set]; [ended addObject:touch]; break; case UITouchPhaseCancelled: if (!canceled) canceled = [NSMutableSet set]; [canceled addObject:touch]; break; default: break; // Call methods to handle the touches if (began) if (moved) if (ended) if (canceled) [gesture touchesCancelled:canceled withEvent:event]; [gesture touchesBegan:began withEvent:event]; [gesture touchesMoved:moved withEvent:event]; [gesture touchesEnded:ended withEvent:event]; } [super sendEvent:event]; }
Best Practices for Handling Multitouch Events
非常实用
屏幕方向改变识别
//Responding to changes in device orientation -(void) viewDidLoad { // Request to turn on accelerometer and begin receiving accelerometer events [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil]; } - (void)orientationChanged:(NSNotification *)notification { // Respond to changes in device orientation } -(void) viewDidDisappear { // Request to stop receiving accelerometer events and turn off accelerometer [[NSNotificationCenter defaultCenter] removeObserver:self]; [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]; }
Motion Events
//Becoming first responder - (BOOL)canBecomeFirstResponder { return YES; } - (void)viewDidAppear:(BOOL)animated { [self becomeFirstResponder]; } //Handling a motion event - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event { if (motion == UIEventSubtypeMotionShake) { // User was shaking the device. Post a notification named "shake." [[NSNotificationCenter defaultCenter] postNotificationName:@"shake" object:self]; } }
如果应用程序想要使用陀螺仪或者加速器等硬件,那么需要在plist文件中定义
key:UIRequiredDeviceCapabilities value:accelerometer、gyroscope
获取设备的移动:Motion events是通过三个类来呈现移动的
CMAccelerometerData:
CMGyroData:
CMDeviceMotion:
CMMotionManager是核心的管理类,它提供两种方式来获取数据,推、拉(推荐)
Common update intervals for acceleration events
Accessing accelerometer data in MotionGraphs
static const NSTimeInterval accelerometerMin = 0.01; - (void)startUpdatesWithSliderValue:(int)sliderValue { // Determine the update interval NSTimeInterval delta = 0.005; NSTimeInterval updateInterval = accelerometerMin + delta * sliderValue; // Create a CMMotionManager CMMotionManager *mManager = [(APLAppDelegate *)[[UIApplication sharedApplication] delegate] sharedManager]; APLAccelerometerGraphViewController * __weak weakSelf = self; // Check whether the accelerometer is available if ([mManager isAccelerometerAvailable] == YES) { // Assign the update interval to the motion manager [mManager setAccelerometerUpdateInterval:updateInterval]; [mManager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) { [weakSelf.graphView addX:accelerometerData.acceleration.x y:accelerometerData.acceleration.y z:accelerometerData.acceleration.z]; [weakSelf setLabelValueX:accelerometerData.acceleration.x y:accelerometerData.acceleration.y z:accelerometerData.acceleration.z]; }]; } self.updateIntervalLabel.text = [NSString stringWithFormat:@"%f", updateInterval]; } - (void)stopUpdates { CMMotionManager *mManager = [(APLAppDelegate *)[[UIApplication sharedApplication] delegate] sharedManager]; if ([mManager isAccelerometerActive] == YES) { [mManager stopAccelerometerUpdates]; } }
Accessing gyroscope data in MotionGraphs
static const NSTimeInterval gyroMin = 0.01; - (void)startUpdatesWithSliderValue:(int)sliderValue {// Determine the update interval NSTimeInterval delta = 0.005; NSTimeInterval updateInterval = gyroMin + delta * sliderValue; // Create a CMMotionManager CMMotionManager *mManager = [(APLAppDelegate *)[[UIApplication sharedApplication] delegate] sharedManager]; APLGyroGraphViewController * __weak weakSelf = self; // Check whether the gyroscope is available if ([mManager isGyroAvailable] == YES) { // Assign the update interval to the motion manager [mManager setGyroUpdateInterval:updateInterval]; [mManager startGyroUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMGyroData *gyroData, NSError *error) { [weakSelf.graphView addX:gyroData.rotationRate.x y:gyroData.rotationRate.y z:gyroData.rotationRate.z]; [weakSelf setLabelValueX:gyroData.rotationRate.x y:gyroData.rotationRate.y z:gyroData.rotationRate.z]; }]; } self.updateIntervalLabel.text = [NSString stringWithFormat:@"%f", updateInterval]; } - (void)stopUpdates{ CMMotionManager *mManager = [(APLAppDelegate *)[[UIApplication sharedApplication] delegate] sharedManager]; if ([mManager isGyroActive] == YES) { [mManager stopGyroUpdates]; } }
Starting and stopping device motion updates
- (void)startDeviceMotion { // Create a CMMotionManager motionManager = [[CMMotionManager alloc] init]; // Tell CoreMotion to show the compass calibration HUD when required // to provide true north-referenced attitude motionManager.showsDeviceMovementDisplay = YES; motionManager.deviceMotionUpdateInterval = 1.0 / 60.0; // Attitude that is referenced to true north [motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXTrueNorthZVertical]; } - (void)stopDeviceMotion { [motionManager stopDeviceMotionUpdates]; }
远程控制
//Preparing to receive remote control events - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; // Turn on remote control event delivery [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; // Set itself as the first responder [self becomeFirstResponder]; } //Ending the receipt of remote control events - (void)viewWillDisappear:(BOOL)animated { // Turn off remote control event delivery [[UIApplication sharedApplication] endReceivingRemoteControlEvents]; // Resign as first responder [self resignFirstResponder]; [super viewWillDisappear:animated]; } //Handling remote control events - (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;