zoukankan      html  css  js  c++  java
  • iOS手势处理

    iOS手势处理

    iOS手势有着如下几种:

    上面的手势对应的操作是: 

    • Tap          (点一下)
    • Pinch        (二指往內或往外拨动,平时经常用到的缩放)  矩阵变换
    • Rotation    (旋转)                                                  矩阵变换
    • Swipe       (滑动,快速移动)
    • Pan          (拖移,慢速移动)                                     矩阵变换
    • LongPress (长按

    注意:以下示例均把手势封装进一个View当中

    UITapGestureRecognizer - 点击手势

    GestureView.h + GestureView.m

    #import <UIKit/UIKit.h>
    
    @interface GestureView : UIView
    
    @end
    GestureView.h
    #import "GestureView.h"
    
    @interface GestureView ()
    @property (nonatomic, strong) UITapGestureRecognizer *tapGesture;
    @property (nonatomic, strong) CALayer                *colorLayer;
    @end
    
    @implementation GestureView
    
    - (id)initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            // 初始化手势,给手势指定响应事件的对象
            _tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self
                                                                  action:@selector(gestureEvent:)];
            _colorLayer = [CALayer layer];
            _colorLayer.frame = self.bounds;
            
            [self.layer addSublayer:_colorLayer];
            
            // 将手势与区域绑定
            [self addGestureRecognizer:_tapGesture];
        }
        return self;
    }
    
    - (void)gestureEvent:(UIGestureRecognizer *)sender {
        _colorLayer.backgroundColor = [UIColor colorWithRed:arc4random() % 255 / 255.f
                                                      green:arc4random() % 255 / 255.f
                                                       blue:arc4random() % 255 / 255.f
                                                      alpha:1.0f].CGColor;
    }
    
    @end
    GestureView.m

    - (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer

    Attaching a gesture recognizer to a view defines the scope of the represented gesture, causing it to receive touches hit-tested to that view and all of its subviews. The view establishes a strong reference to the gesture recognizer.

    将手势识别器附着在一个view上,实际上定义了一个手势接收的区域,会将接收到的触摸事件传递给这个view以及这个view的所有的subviews.这个view会对这个手势识别器强引用.

    可以总结两点:

    1. 手势会传递给这个view中所有的subviews

    2. view会强引用手势识别器

    使用如下:

    点击手势有两个参数可以设置:

    numberOfTapsRequired         点击几次触发事件(默认是1)

    numberOfTouchesRequired    需要几个手指点击(默认是1)

    UIPinchGestureRecognizer - 缩放

    GestureView.h + GestureView.m

    #import <UIKit/UIKit.h>
    
    @interface GestureView : UIView
    
    @end
    GestureView.h
    #import "GestureView.h"
    
    @interface GestureView ()
    @property (nonatomic, strong) UIPinchGestureRecognizer *pinchGesture;
    @end
    
    @implementation GestureView
    
    - (id)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self)
        {
            // 初始化手势,给手势指定响应事件的对象
            _pinchGesture = 
                [[UIPinchGestureRecognizer alloc] initWithTarget:self
                                                          action:@selector(gestureEvent:)];
            
            // 将手势与区域绑定
            [self addGestureRecognizer:_pinchGesture];
        }
        return self;
    }
    
    - (void)gestureEvent:(UIPinchGestureRecognizer *)sender
    {
        //
        self.transform = CGAffineTransformScale(self.transform, sender.scale, sender.scale);
        sender.scale = 1;
    }
    
    @end
    GestureView.m

    缩放手势会用到矩阵变换.

    UIRotationGestureRecognizer - 旋转

    GestureView.h + GestureView.m

    #import <UIKit/UIKit.h>
    
    @interface GestureView : UIView
    
    @end
    GestureView.h
    #import "GestureView.h"
    
    @interface GestureView ()
    @property (nonatomic, strong) UIRotationGestureRecognizer *rotationGesture;
    @end
    
    @implementation GestureView
    
    - (id)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self)
        {
            // 初始化手势,给手势指定响应事件的对象
            _rotationGesture = 
                [[UIRotationGestureRecognizer alloc] initWithTarget:self
                                                             action:@selector(gestureEvent:)];
            
            // 将手势与区域绑定
            [self addGestureRecognizer:_rotationGesture];
        }
        return self;
    }
    
    - (void)gestureEvent:(UIRotationGestureRecognizer *)sender
    {
        // 此处用到了矩阵变换
        self.transform = CGAffineTransformRotate(self.transform, sender.rotation);
        sender.rotation = 0;
    }
    GestureView.m

    UISwipeGestureRecognizer - 滑动

    GestureView.h + GestureView.m

    #import <UIKit/UIKit.h>
    
    @interface GestureView : UIView
    
    @end
    GestureView.h
    #import "GestureView.h"
    
    @interface GestureView ()
    @property (nonatomic, strong) UISwipeGestureRecognizer *swipeGesture;
    @end
    
    @implementation GestureView
    
    - (id)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self)
        {
            // 初始化手势,给手势指定响应事件的对象
            _swipeGesture = 
                [[UISwipeGestureRecognizer alloc] initWithTarget:self
                                                          action:@selector(gestureEvent:)];
            _swipeGesture.direction = 
                UISwipeGestureRecognizerDirectionLeft | UISwipeGestureRecognizerDirectionRight;
            
            // 将手势与区域绑定
            [self addGestureRecognizer:_swipeGesture];
        }
        return self;
    }
    
    - (void)gestureEvent:(UISwipeGestureRecognizer *)sender
    {
        NSLog(@"left or right");
    }
    
    @end
    GestureView.m

    UIPanGestureRecognizer - 平移

    GestureView.h + GestureView.m

    #import <UIKit/UIKit.h>
    
    @interface GestureView : UIView
    
    @end
    GestureView.h
    #import "GestureView.h"
    
    @interface GestureView ()
    @property (nonatomic, strong) UIPanGestureRecognizer *panGesture;
    @end
    
    @implementation GestureView
    
    - (id)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self)
        {
            // 初始化手势,给手势指定响应事件的对象
            _panGesture = 
                [[UIPanGestureRecognizer alloc] initWithTarget:self
                                                        action:@selector(gestureEvent:)];
    
            // 将手势与区域绑定
            [self addGestureRecognizer:_panGesture];
        }
        return self;
    }
    
    - (void)gestureEvent:(UIPanGestureRecognizer *)sender
    {
        // 此处用到了矩阵变换
        CGPoint translation = [sender translationInView:self];
        
        self.center = CGPointMake(self.center.x + translation.x,
                                  self.center.y + translation.y);
        
        [sender setTranslation:CGPointZero
                        inView:self];
    }
    
    @end
    GestureView.m

    UILongPressGestureRecognizer - 长按手势

    GestureView.h + GestureView.m

    #import <UIKit/UIKit.h>
    
    @interface GestureView : UIView
    
    @end
    GestureView.h
    #import "GestureView.h"
    
    @interface GestureView ()
    @property (nonatomic, strong) UILongPressGestureRecognizer *longPressGesture;
    @end
    
    @implementation GestureView
    
    - (id)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self)
        {
            // 初始化手势,给手势指定响应事件的对象
            _longPressGesture = 
                [[UILongPressGestureRecognizer alloc] initWithTarget:self
                                                              action:@selector(gestureEvent:)];
            _longPressGesture.minimumPressDuration = 2.0f;
    
            // 将手势与区域绑定
            [self addGestureRecognizer:_longPressGesture];
        }
        return self;
    }
    
    - (void)gestureEvent:(UILongPressGestureRecognizer *)sender
    {
        NSLog(@"触发事件");
    }
    
    @end
    GestureView.m

    问题:如何处理一个view中添加了两个手势,1个是单击的手势,一个是双击的手势呢?

    可以使用这个方法requireGestureRecognizerToFail:

    #import "GestureView.h"
    
    @interface GestureView ()
    @property (nonatomic, strong) UITapGestureRecognizer *tapGesture1;
    @property (nonatomic, strong) UITapGestureRecognizer *tapGesture2;
    @end
    
    @implementation GestureView
    
    - (id)initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            // 单击手势
            _tapGesture1 = 
                [[UITapGestureRecognizer alloc] initWithTarget:self
                                                        action:@selector(gesture1Event:)];
            _tapGesture1.numberOfTapsRequired = 1;
            
            // 双击手势
            _tapGesture2 = 
                [[UITapGestureRecognizer alloc] initWithTarget:self
                                                        action:@selector(gesture2Event:)];
            _tapGesture2.numberOfTapsRequired = 2;
    
            // 注意: 判断双击手势需要时间,也就是说会有延时
            
            // 有事件触发时,先判断是不是 双击手势,如果不是就执行 单击手势
            [_tapGesture1 requireGestureRecognizerToFail:_tapGesture2];
            
            // 将手势与区域绑定
            [self addGestureRecognizer:_tapGesture1];
            [self addGestureRecognizer:_tapGesture2];
        }
        return self;
    }
    
    - (void)gesture1Event:(UIGestureRecognizer *)sender {
        NSLog(@"1");
    }
    
    - (void)gesture2Event:(UIGestureRecognizer *)sender {
        NSLog(@"2");
    }
    
    @end
    GestureView.m

    实际上,这种方式会有延时感-_-!!!!

    问题:如何将长按手势和拖拽手势合并在一起呢? 

    我们需要用代理实现,实现以下的方法:

    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer  shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer

    Asks the delegate if two gesture recognizers should be allowed to recognize gestures simultaneously.

    询问这个代理,是否允许两个手势同时触发.

    #import "GestureView.h"
    
    @interface GestureView ()<UIGestureRecognizerDelegate>
    
    {
        BOOL  shouldAllowPan;
    }
    
    @property (nonatomic, strong) UIPanGestureRecognizer       *panGesture;
    @property (nonatomic, strong) UILongPressGestureRecognizer *longPressGesture;
    @end
    
    @implementation GestureView
    
    - (id)initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            // 初始化时不允许拖拽
            shouldAllowPan = NO;
            
            _panGesture = 
            [[UIPanGestureRecognizer alloc] initWithTarget:self
                                                    action:@selector(panEvent:)];
            [self addGestureRecognizer:_panGesture];
            _panGesture.delegate = self;
            
            _longPressGesture = 
            [[UILongPressGestureRecognizer alloc] initWithTarget:self
                                                          action:@selector(longPressEvent:)];
            _longPressGesture.minimumPressDuration = 1.0f;
            [self addGestureRecognizer:_longPressGesture];
            _longPressGesture.delegate = self;
        }
        return self;
    }
    
    - (void)panEvent:(UIPanGestureRecognizer *)sender {
        
        if(shouldAllowPan == YES)
        {
            // 移动的操作
            CGPoint translation = [sender translationInView:self];
            self.center = CGPointMake(self.center.x + translation.x,
                                      self.center.y + translation.y);
            
            [sender setTranslation:CGPointZero
                            inView:self];
        }
        else if(sender.state == UIGestureRecognizerStateEnded || 
                sender.state == UIGestureRecognizerStateFailed || 
                sender.state == UIGestureRecognizerStateCancelled)
        {
            shouldAllowPan = NO;
        }
    }
    
    - (void)longPressEvent:(UIGestureRecognizer *)sender
    {
        // 长按开始
        if(UIGestureRecognizerStateBegan == sender.state)
        {
            NSLog(@"长按开始");
            self.backgroundColor = [UIColor redColor];
            shouldAllowPan = NO;
        }
        
        // 长按进行中
        if(UIGestureRecognizerStateChanged == sender.state)
        {
            NSLog(@"长按进行中");
            shouldAllowPan = YES;
        }
        
        // 长按结束
        if(UIGestureRecognizerStateEnded == sender.state)
        {
            NSLog(@"长按结束");
            self.backgroundColor = [UIColor blackColor];
            shouldAllowPan = NO;
        }
    }
    
    // 是否允许多个手势同时触发
    - (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
    shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
    {
        // 允许
        return YES;
    }
    
    // 是否允许继续跟踪触摸事件
    - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
    {
        // 条件满足的手势会被传递进来(如果是移动手势,)
        if([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && shouldAllowPan == NO)
        {
            return NO;
        }
        
        return YES;
    }
    
    
    @end
    GestureView.m

    根据手势状态来识别手势触发事件的全称细节是十分重要的.

    问题:如何让一个view的部分区域响应拖拽事件呢?

    比方说,我们只需要下面红色线指定的区域响应拖拽事件:

    #import "GestureView.h"
    
    @interface GestureView ()
    
    {
    
        BOOL  allowPan;
    
    }
    
    @property (nonatomic, strong) UIPanGestureRecognizer       *panGesture;
    @end
    
    @implementation GestureView
    
    - (id)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self)
        {
            // 初始化时不允许拖拽
            allowPan = NO;
            
            _panGesture = 
            [[UIPanGestureRecognizer alloc] initWithTarget:self
                                                    action:@selector(panEvent:)];
            [self addGestureRecognizer:_panGesture];
        }
        return self;
    }
    
    - (void)panEvent:(UIPanGestureRecognizer *)sender
    {
        // 获取到当前手势在当前视图坐标中触摸的点
        CGPoint point = [sender locationInView:self];
        
        // 手势开始时置位(手势事件开始过程中仅仅执行一回)
        if (sender.state == UIGestureRecognizerStateBegan)
        {
            // 设定响应的区域
            if (self.bounds.size.height / 2.f >= point.x && self.bounds.size.width / 2.f >= point.y)
            {
                allowPan = YES;
            }
        }
        
        // 手势持续(手势事件开始过程中执行多回)
        if (sender.state == UIGestureRecognizerStateChanged && allowPan == YES)
        {
            // 移动的操作
            CGPoint translation = [sender translationInView:self];
            self.center = CGPointMake(self.center.x + translation.x,
                                      self.center.y + translation.y);
            
            [sender setTranslation:CGPointZero
                            inView:self];
        }
        
        // 手势结束后置位(手势事件开始过程中仅仅执行一回)
        if (sender.state == UIGestureRecognizerStateEnded)
        {
            allowPan = NO;
        }
    }
    
    @end
    GestureView.m

     要实现那个效果,以下方法是核心方法,配合手势的状态使用:

    // 获取到当前手势在当前视图坐标中触摸的点
        CGPoint point = [sender locationInView:self];

    问题:如何在ViewController中获取到点击的坐标,让一个view跟随触摸点移动呢?

    可以使用这几个最原始的处理触摸事件的方法来达到效果.

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;

    #import "RootViewController.h"
    
    @interface RootViewController ()
    
    {
        UIView  *_panPoint;
        CALayer *_redLayer;
    }
    
    @end
    
    @implementation RootViewController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        // 初始化view
        _panPoint = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
        _panPoint.layer.cornerRadius = 25.f;
        _panPoint.layer.masksToBounds = YES;
        [self.view addSubview:_panPoint];
        
        // 初始化一个layer
        _redLayer = [CALayer layer];
        _redLayer.frame = _panPoint.bounds;
        _redLayer.backgroundColor = [UIColor redColor].CGColor;
        _redLayer.opacity = 0.f;
        [_panPoint.layer addSublayer:_redLayer];
    }
    
    // 一次完整的触摸事件中,touchesBegan只执行一回
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        // 获取触摸点坐标
        UITouch *touch = [touches anyObject];
        CGPoint touchPoint = [touch locationInView:self.view];
        
        _panPoint.center = touchPoint;
        _redLayer.opacity = 1.0f;
    }
    
    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    {
        // 获取触摸点坐标
        UITouch *touch = [touches anyObject];
        CGPoint touchPoint = [touch locationInView:self.view];
        
        _panPoint.center = touchPoint;
    }
    
    // 一次完整的触摸事件中,touchesEnded只执行一回
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    {
        // 获取触摸点坐标
        UITouch *touch = [touches anyObject];
        CGPoint touchPoint = [touch locationInView:self.view];
        
        _panPoint.center = touchPoint;
        _redLayer.opacity = 0.0f;
    }
    
    @end
    RootViewController.m

    也可以直接使用拖拽手势来实现的,不过不完美

    #import "RootViewController.h"
    
    @interface RootViewController ()
    
    {
        UIView  *_panPoint;
        CALayer *_redLayer;
    }
    
    @end
    
    @implementation RootViewController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        // 初始化view
        _panPoint = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
        _panPoint.layer.cornerRadius = 25.f;
        _panPoint.layer.masksToBounds = YES;
        [self.view addSubview:_panPoint];
        
        // 初始化一个layer
        _redLayer = [CALayer layer];
        _redLayer.frame = _panPoint.bounds;
        _redLayer.backgroundColor = [UIColor redColor].CGColor;
        _redLayer.opacity = 0.f;
        [_panPoint.layer addSublayer:_redLayer];
        
        // 定义手势
        UIPanGestureRecognizer *panGesture = 
            [[UIPanGestureRecognizer alloc] initWithTarget:self
                                                    action:@selector(panGestureEvent:)];
        [self.view addGestureRecognizer:panGesture];
    }
    
    - (void)panGestureEvent:(UIPanGestureRecognizer *)sender
    {
        CGPoint touchPoint = [sender locationInView:self.view];
        
        if (sender.state == UIGestureRecognizerStateBegan)
        {
            _panPoint.center = touchPoint;
            _redLayer.opacity = 1.0f;
        }
        else if (sender.state == UIGestureRecognizerStateChanged)
        {
            _panPoint.center = touchPoint;
        }
        else if (sender.state == UIGestureRecognizerStateEnded)
        {
            _panPoint.center = touchPoint;
            _redLayer.opacity = 0.0f;
        }
    }
    
    @end
    RootViewController.m

    他们两者的对比关系:

    手势处理中核心的地方:

    1.  UIGestureRecognizerState非常重要,触发事件时可以直接根据这个状态值来判断事件的发生顺序

    2.  处理多手势冲突时,可以使用依赖requireGestureRecognizerToFail:来处理,但效果不好

    3.  处理多个手势并发响应的时候,需要实现代理并执行方法,请参考上面的事例

    4.  仅仅处理一个view上局部的手势事件,需要用到手势的locationInView:方法,并与UIGestureRecognizerState状态值配合使用

    附录:

    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch

    这是手势的代理方法,在可以不移除手势的情况下关闭手势的响应,此方法涉及到响应链.

  • 相关阅读:
    查看 Linux 中文件打开情况(lsof)
    主从复制错误处理总结
    Mysql 在线新建或重做主从
    mysql binlog日志删除
    Mysql 删除从数据库的relay logs最佳方式、最安全方式
    k8s调度器kube-scheduler
    nginx应用场景,特性,目录结构,常用模块,内置变量,URL和URI,http状态码,配置文件详解
    nginx日志
    CentOS7单机部署lamp环境和apache虚拟主机
    CentOS7安装和配置dns服务器
  • 原文地址:https://www.cnblogs.com/YouXianMing/p/3715456.html
Copyright © 2011-2022 走看看