Event programming guide

Phone中处理触摸屏的操作,在3.2之前是主要使用的是由UIResponder而来的如下4种方式:
- (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;

猜你喜欢

转载自guafei.iteye.com/blog/1842593