Touch events and gestures developed by iOS

  • 2020-06-01 11:02:55
  • OfStack

Events in iOS fall into three categories: touch events, accelerometer events, and remote control events. Only objects that inherit from UIResponder can receive and process events, called "responder objects." UIApplication, UIViewController, UIView all inherit from UIResponder. UIResponder internally provides methods to handle events:

Touch events: touchesBegan, touchesMoved, touchesEnded, touchesCancelled

Accelerometer events: motionBegan, motionEnded, motionCancelled

Remote control event: remoteControlReceivedWithEvent

UIVeiw's touch event processing process:


/**
 *  When the finger begins to touch view Called when the 
 *
 * @param touches <#touches description#>
 * @param event  <#event description#>
 */
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
   
  NSLog(@"%s",__func__);
}
 
/**
 *  When a finger in view Called when moving up 
 *
 * @param touches <#touches description#>
 * @param event  <#event description#>
 */
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  NSLog(@"%s",__func__);
}
 
/**
 *  When the fingers are gone view Called when the 
 *
 * @param touches <#touches description#>
 * @param event  <#event description#>
 */
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
   
  NSLog(@"%s",__func__);
}
 
/**
 *  Called when a touch event is interrupted by a system event 
 *
 * @param touches <#touches description#>
 * @param event  <#event description#>
 */
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
   
  NSLog(@"%s",__func__);
}

A touch action must call touchesBeagn, touchesMoved, and touchesEnded.

Speaking of touch methods, the first thing to know is that UITouch is an object. When one finger touches the screen, it generates an UITouch object associated with it, and one finger corresponds to one UITouch object. This object holds the information of this touch, such as the position, time, stage, etc. When the finger moves, the system will update the same UITouch object. Enables it to hold the touch location information of the finger directly. When the finger leaves the screen, the corresponding UITouch object is destroyed.


@interface UITouch : NSObject
 
@property(nonatomic,readonly) NSTimeInterval   timestamp;
@property(nonatomic,readonly) UITouchPhase    phase;
@property(nonatomic,readonly) NSUInteger     tapCount;  // touch down within a certain point within a certain amount of time
 
// majorRadius and majorRadiusTolerance are in points
// The majorRadius will be accurate +/- the majorRadiusTolerance
@property(nonatomic,readonly) CGFloat majorRadius NS_AVAILABLE_IOS(8_0);
@property(nonatomic,readonly) CGFloat majorRadiusTolerance NS_AVAILABLE_IOS(8_0);
 
@property(nullable,nonatomic,readonly,strong) UIWindow            *window;
@property(nullable,nonatomic,readonly,strong) UIView             *view;
@property(nullable,nonatomic,readonly,copy)  NSArray <UIGestureRecognizer *> *gestureRecognizers NS_AVAILABLE_IOS(3_2);
 
// Get current location 
- (CGPoint)locationInView:(nullable UIView *)view;
// To get on 1 The location of the touch points 
- (CGPoint)previousLocationInView:(nullable UIView *)view;
 
// Force of the touch, where 1.0 represents the force of an average touch
@property(nonatomic,readonly) CGFloat force NS_AVAILABLE_IOS(9_0);
// Maximum possible force with this input mechanism
@property(nonatomic,readonly) CGFloat maximumPossibleForce NS_AVAILABLE_IOS(9_0);
 
@end


eg: make 1 view move with your finger


/**
 *  When a finger in view Called when moving up 
 *
 * @param touches <#touches description#>
 * @param event  <#event description#>
 */
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  NSLog(@"%s",__func__);
   
  // To obtain UITouch object 
  UITouch *touch = [touches anyObject];
   
  // Gets the position of the current point 
  CGPoint curP = [touch locationInView:self];
   
  // To get on 1 The position of the point 
  CGPoint preP = [touch previousLocationInView:self];
   
  // To calculate x The offset 
  CGFloat offsetX = curP.x - preP.x;
   
  // To calculate y The offset 
  CGFloat offsetY = curP.y = preP.y;
   
  // Modify the view The location of the 
  self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
}


This is based on the location information stored in the UITouch object.

Event generation and transmission:

When a touch event is generated, it is added to an event queue managed by UIApplication. UIApplication pulls the first event out of the queue and sends it to the processing of the application's main window. The main window finds the most appropriate view in the view hierarchy and calls the touches method to handle the touch event. The passing of the touch event is from the parent control to the child control. If the parent control cannot receive a touch event, then the child control cannot receive a touch event.

How do you find the most appropriate control to handle events? First determine if you can receive a touch event? Are the touch points on yourself? Walk through the child control from back to front, repeating the previous two steps, if there is no child control that meets the criteria, then handle it yourself.

The control USES the hitTest:withEvent: method to find the most suitable view, and pointInside to determine if the point is on the control or not on the method caller.

The underlying implementation of the hitTest method:


- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
   
  // Determines whether the current control can receive touch events 
  if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
    return nil;
  }
   
  // Determines if the touch point is on the current control 
  if ([self pointInside:point withEvent:event] == NO) {
    return nil;
  }
   
  // Iterate over your child controls from back to front 
  NSInteger count = self.subviews.count;
  for (NSInteger i = count - 1; i >= 0; i--) {
    UIView *childView = self.subviews[i];
     
    // Converts coordinates on the current control to coordinates on the child control 
    CGPoint childPoint = [self convertPoint:point toView:childView];
     
    // Recursive calls hitTest Find the most suitable method view
    UIView *fitView = [childView hitTest:childPoint withEvent:event];
     
    if (fitView) {
      return fitView;
    }
  }
   
  // At the end of the cycle, nothing is better than yourself view , return to oneself 
  return self;
   
}

However, listening for touch events using the touches method has its drawbacks, such as customizing view, so iOS3.2 was followed by apple's launch of UIGestureRecognizer gesture recognition. UIGestureRecognizer is an abstract class whose subclasses can handle a specific gesture.

There are the following gestures:


// Click on the sign 
//  UITapGestureRecognizer *tap = [UITapGestureRecognizer alloc]initWithTarget:<#(nullable id)#> action:<#(nullable SEL)#>
   
  // Long press gesture   The default is to trigger twice 
//  UILongPressGestureRecognizer *longP = [UILongPressGestureRecognizer alloc]initWithTarget:<#(nullable id)#> action:<#(nullable SEL)#>
   
  // Swept gently gestures   The default direction is to the right 
//  UISwipeGestureRecognizer *swipe = [UISwipeGestureRecognizer alloc]initWithTarget:<#(nullable id)#> action:<#(nullable SEL)#>
   
  // Rotation gestures 
//  UIRotationGestureRecognizer *rotation = [UIRotationGestureRecognizer alloc]initWithTarget:<#(nullable id)#> action:<#(nullable SEL)#>
 
  // Kneading gestures 
//  UIPinchGestureRecognizer *pinch = [UIPinchGestureRecognizer alloc]initWithTarget:<#(nullable id)#> action:<#(nullable SEL)#>
   
  // Drag and drop gesture 
//  UIPanGestureRecognizer *pan = [UIPanGestureRecognizer alloc]initWithTarget:<#(nullable id)#> action:<#(nullable SEL)#>

Practical application:


@interface ViewController ()<UIGestureRecognizerDelegate>
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
 
@end
 
@implementation ViewController
 
- (void)viewDidLoad {
  [super viewDidLoad];
 
  [self setUpPinch];
   
  [self setUpRotation];
 
  [self setUpPan];
   
}
#pragma mark -  Gesture proxy method 
//  Is it allowed to start triggering gestures 
//- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
//{
//  return NO;
//}
 
//  If multiple gestures are allowed, the default is not to support multiple gestures 
//  return yes Supports multiple gestures 
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
  return YES;
}
 
//  Is it allowed to receive finger touch points 
//- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
//  //  Gets the current touch point 
//  CGPoint curP = [touch locationInView:self.imageView];
//  
//  if (curP.x < self.imageView.bounds.size.width * 0.5) {
//    return NO;
//  }else{
//    return YES;
//  }
//}
 
 
#pragma mark -  Click on the sign 
 
- (void)setUpTap
{
  //  Create a click gesture 
  UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
   
  tap.delegate = self;
   
  [_imageView addGestureRecognizer:tap];
}
 
- (void)tap:(UITapGestureRecognizer *)tap
{
  NSLog(@"%s",__func__);
}
 
#pragma mark -  Long press gesture 
//  It will be triggered twice by default 
- (void)setUpLongPress
{
  UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
   
  [self.imageView addGestureRecognizer:longPress];
}
 
 
- (void)longPress:(UILongPressGestureRecognizer *)longPress
{
   
  if (longPress.state == UIGestureRecognizerStateBegan) {
     
    NSLog(@"%s",__func__);
  }
}
 
#pragma mark -  Swept gently 
- (void)setUpSwipe
{
  //  The default direction of the swipe is to the right 
  UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe)];
   
  swipe.direction = UISwipeGestureRecognizerDirectionUp;
   
  [self.imageView addGestureRecognizer:swipe];
   
  //  If you want it later 1 Three controls support swiping in multiple directions, so you must create multiple swiping gestures, 1 A swipe gesture is only supported 1 A direction 
  //  The default direction of the swipe is to the right 
  UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe)];
   
  swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
   
  [self.imageView addGestureRecognizer:swipeDown];
 
   
}
 
- (void)swipe
{
  NSLog(@"%s",__func__);
}
 
#pragma mark -  Rotation gestures 
- (void)setUpRotation
{
  UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotation:)];
  rotation.delegate = self;
  [self.imageView addGestureRecognizer:rotation];
}
 
//  The default is to pass the rotation Angle relative to the starting position 
- (void)rotation:(UIRotationGestureRecognizer *)rotation
{
   
  self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, rotation.rotation);
   
  //  reset 
  rotation.rotation = 0;
   
  //  Gets the Angle of rotation of the gesture 
  NSLog(@"%f",rotation.rotation);
}
 
#pragma mark -  kneading 
- (void)setUpPinch
{
  UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinch:)];
  pinch.delegate = self;
  [self.imageView addGestureRecognizer:pinch];
}
 
- (void)pinch:(UIPinchGestureRecognizer *)pinch
{
  self.imageView.transform = CGAffineTransformScale(self.imageView.transform, pinch.scale, pinch.scale);
   
  //  reset 
   
  pinch.scale = 1;
}
 
#pragma mark -  Drag and drop 
- (void)setUpPan
{
  UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
   
   
  [self.imageView addGestureRecognizer:pan];
}
 
- (void)pan:(UIPanGestureRecognizer *)pan
{
  //  Gets the touch point of a gesture 
  // CGPoint curP = [pan locationInView:self.imageView];
   
  //  Mobile view 
  //  Get the movement of the gesture, also relative to the initial position 
  CGPoint transP = [pan translationInView:self.imageView];
   
  self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, transP.x, transP.y);
   
  //  reset 
  [pan setTranslation:CGPointZero inView:self.imageView];
   
 // NSLog(@"%@",NSStringFromCGPoint(curP));
}
 
@end

The above is iOS touch event and gesture related content introduction, hope to help you learn iOS programming.


Related articles: