美文网首页闻道丶iOS(大杂烩)timeiOS
【iOS】API之UIGestureRecognizer及自定义

【iOS】API之UIGestureRecognizer及自定义

作者: HoyaWhite | 来源:发表于2016-11-10 15:24 被阅读2323次

    在unity开发中,触屏手势在我看来是以手指ID号(手指的身份)以及手指状态为标准界定判断手势的类型的;一些插件感觉和iOS中苹果封装的差不多,因此就UIGestureRecognizer这个类关于API使用进行一下初步探索。。。


    一、UIGestureRecognizer

    1. 给手势添加多个Target; 触发后执行顺序按照添加的顺序执行


    2. 获取手指相对于某一个view位置




      第二个方法中touchIndex是手指的序号(比如手势是两个手指触发,手指接触屏幕肯定有先后,第一个接触的系统内部自动给其标为第0个,第二个手指给其标记为第1个)

    3. 不太明白的东西

    4. UIGestureRecognizer.state介绍

    • UIGestureRecognizerStatePossible 尚未识别是何种手势操作(但可能已经触发了触摸事件),默认状态
    • UIGestureRecognizerStateCancelled 这个状态可以在手势被触发之后:1.当前手指没发生变化,又增加了手指(这个手势没有结束的时候又多了手势进来)2.结束之前设置手势enable属性为NO触发
    • UIGestureRecognizerStateFailed 不知道怎么触发这个状态,但是利用方法requireGestureRecognizerToFail:可以保证在other Gesture failed状态下this Gesture才可以触发,代理方法gestureRecognizerShouldBegin:亦与之相关
    • UIGestureRecognizerStateRecognized 在识别结束之后,end状态结束以后,个人理解为表示这个手势结束
      +示例: 对于离散型手势 UITapGestureRecgnizer 要么被识别,要么失败,点按(假设点按次数设置为1,并且没有添加长按手势)下去一次不松开则此时什么也不会发生,松开手指立即识别并调用操作事件,并且状态为3(已完成)。但是连续型手势要复杂一些,就拿旋转手势来说,如果两个手指点下去不做任何操作,此时并不能识别手势(因为我们还没旋转)但是其实已经触发了触摸开始事件,此时处于状态0;如果此时旋转会被识别,也就会调用对应的操作事件,同时状态变成1(手势开始),但是状态1只有一瞬间;紧接着状态变为2(因为我们的旋转需要持续一会),并且重复调用操作事件(如果在事件中打印状态会重复打印2);松开手指,此时状态变为3,并调用1次操作事件。
    1. UIGestureRecognizer.delegate介绍
    • gestureRecognizerShouldBegin: 在手势想要从UIGestureRecognizerStatePossible状态变化的时候调用,返回NO则表示手势状态是UIGestureRecognizerStateFailed
    • gestureRecognizer: shouldRecognizeSimultaneouslyWithGestureRecognizer: 在手势冲突时候调用,默认返回NO表示默认不能同时识别两个手势
    • gestureRecognizer: shouldRequireFailureOfGestureRecognizer:每次尝试识别手势都会调用,一般只会在手势冲突时候,比如同时添加轻击手势和长按手势,轻击手势设置代理,在区分是轻击手势或者长按手势还没有确定结果的时候调用,而不设置轻击手势代理而设置长按代理的时候就不会调用
    • gestureRecognizer: shouldBeRequiredToFailByGestureRecognizer:调用时机同上
    • gestureRecognizer: shouldReceiveTouch:在上面两个方法之前调用
    • gestureRecognizer: shouldReceivePress:同上

    二、UIGestureRecognizer子类

    UIGestureRecognizer子类有:

    UITapGestureRecognizer(轻击)
    UILongPressGestureRecognizer(长按)
    ​UISwipeGestureRecognizer(轻扫)
    UIPanGestureRecognizer(拖动)
    UIPinchGestureRecognizer(捏合)
    UIRotationGestureRecognizer(旋转)
    UIScreenEdgePanGestureRecognizer(屏幕边缘手势继承于pan手势)

    轻击UITapGestureRecognizer
    • 可以通过修改numberOfTapsRequired(敲击次数默认1)和numberOfTouchesRequired(手指个数默认1)两个参数进行轻击手势的配置,不配置按默认.
    长按UILongPressGestureRecognizer
    • 长按手势numberOfTapsRequired手指敲击次数默认为0,如果设置1需要先轻吉再长按才能触发
    • 长按手势可以通过numberOfTouchesRequired(手指个数)minimumPressDuration(最小按压时间)allowableMovement(允许移动像素单位范围:在识别手势期间移动超过范围手势识别失效)进行配置
    轻扫​UISwipeGestureRecognizer
    • 可以配置的有numberOfTouchesRequired(手指的个数默认1) direction(轻扫的方向:上下左右;默认右表示手指在屏幕从左到右滑动和边缘滑动手势方向不同)
    拖动UIPanGestureRecognizer
    • minimumNumberOfTouches最少可以识别到的手指数量>=1 maximumNumberOfTouches最多可以识别到的手指数量
    • - (CGPoint)translationInView:(nullable UIView *)view; 相对于began时候手指位置的偏移量
    • - (void)setTranslation:(CGPoint)translation inView:(nullable UIView *)view; 作用用于重新定位手指 偏移量的计算
    • - (CGPoint)velocityInView:(nullable UIView *)view; 手指移动速度
    //拖动触发后调用
    - (void)panGes:(UIPanGestureRecognizer *)ges{
        if (ges.state == UIGestureRecognizerStateChanged) {
            //注意这个方法获取到的偏移量是相对于开始识别到时候手指的位置并不会实时更新需要再调用setTranslation: inView:
            CGPoint p = [ges translationInView:self.redView];
            [ges setTranslation:CGPointZero inView:self.redView];
            self.redView.frame = CGRectMake(self.redView.frame.origin.x + p.x, self.redView.frame.origin.y + p.y, self.redView.frame.size.width, self.redView.frame.size.height);
        }
    }
    
    捏合UIPinchGestureRecognizer
    • scale表示形变量;velocity只读 表示单位时间scale改变的速度
    //捏合触发后调用
    - (void)pinchAction:(UIPinchGestureRecognizer *)ges{
        if (ges.state == UIGestureRecognizerStateChanged) {
            self.redView.transform = CGAffineTransformMakeScale(ges.scale, ges.scale);
        }
        if(ges.state == UIGestureRecognizerStateEnded)
        {
            [UIView animateWithDuration:0.5 animations:^{
                self.redView.transform = CGAffineTransformIdentity;//取消一切形变
            }];
        }
    }
    
    旋转UIRotationGestureRecognizer
    //旋转触发后调用
    - (void)rotaAction:(UIRotationGestureRecognizer *)ges{
        if (ges.state==UIGestureRecognizerStateChanged)
        {
            self.redView.transform=CGAffineTransformMakeRotation(ges.rotation);
        }
        if(ges.state==UIGestureRecognizerStateEnded)
        {
            [UIView animateWithDuration:1 animations:^{
                self.redView.transform=CGAffineTransformIdentity;//取消形变
            }];
        }
    }
    
    屏幕边缘手势UIScreenEdgePanGestureRecognizer
    • 继承于pan手势,一般用于左侧滑动返回。吐槽一下,给自定义View添加之后,我发现触发特别难,貌似只有UIRectEdgeLeftUIRectEdgeRight触发情况会好一点,不知道为啥。。。。我绝对没有说fuck

    三、自定义手势

    好吧,我触发不了系统屏幕边缘手势我自己写一个行了吧。。。。自定义手势需要注意几点:

    • 继承自UIGestureRecognizer
    • .m文件包含头文件#import <UIKit/UIGestureRecognizerSubclass.h>
    • 手势判断之后设置self.state = UIGestureRecognizerStateEnded;就会自动调用对应方法了

    WPGesture.h

    #import <UIKit/UIKit.h>
    
    @interface WPGesture : UIGestureRecognizer
    //需要几根手指
    @property(nonatomic, assign)NSUInteger wpNumberOfTouchesRequired;//默认1
    //需要几次点击
    @property(nonatomic, assign)NSUInteger wpNumberOfTapsRequired;//默认0
    //哪一边
    @property (readwrite, nonatomic, assign) UIRectEdge wpEdges;//默认UIRectEdgeAll
    
    @end
    

    WPGesture.m

    #import "WPGesture.h"
    #import <UIKit/UIGestureRecognizerSubclass.h>
    
    @interface WPGesture()
    @property(nonatomic, assign)BOOL isCorrected;
    
    @end
    @implementation WPGesture
    //在began里面识别手势是否正确: 几根手指 几次点击 哪一边  此方法左右:作为手势识别器
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        [super touchesBegan:touches withEvent:event];
        NSArray *allTouches = [touches allObjects];
        //0.判断手指的tapCount  是否在move里面给出移动初始点
        for (UITouch * tou in allTouches) {
            if(tou.tapCount != (self.wpNumberOfTapsRequired + 1)){
                //不符合的话直接return 不再继续判断
                return;
            }
        }
    
        //1.判断手指个数是否符合
        if(allTouches.count != self.wpNumberOfTouchesRequired){
            //忽略不正确手指 防止干扰到识别
            for (UITouch * tou in allTouches) {
                [self ignoreTouch:tou forEvent:event];
            }
            return;
        }
        
        //2.手指个数满足情况下 判断手指位置是否满足条件
        //用临时变量topM等接收 防止屏幕旋转出现
        CGFloat topM = self.view.bounds.size.height *0.25;//满足点要小于top
        CGFloat butM = self.view.bounds.size.height *0.75;//满足点要大于but
        CGFloat lefM = self.view.bounds.size.width *0.25;//满足点要小于lef
        CGFloat rigM = self.view.bounds.size.width *0.75;//满足点要大于rig
        if (topM >= 100) {
            topM = 100;
            butM = self.view.bounds.size.height - 100;
        }
        if (lefM >= 100) {
            lefM = 100;
            rigM = self.view.bounds.size.width - 100;
        }
        // 判断手指位置是否满足条件
        for (UITouch * tou in allTouches) {
            if (!self.view) {
                return;
            }//貌似能触发began就肯定有view
            CGPoint touchP = [tou locationInView:self.view];//手指位置
            if (self.wpEdges == UIRectEdgeNone) {
                return;//UIRectEdgeNone什么也不做
            }
            if (self.wpEdges == UIRectEdgeTop) {
                if (touchP.y > topM) {
                    [self ignoreTouch:tou forEvent:event];
                    return;
                }
            }
            if (self.wpEdges == UIRectEdgeBottom) {
                if (touchP.y < butM) {
                    [self ignoreTouch:tou forEvent:event];
                    return;
                }
            }
            if (self.wpEdges == UIRectEdgeLeft) {
                if (touchP.x > lefM) {
                    [self ignoreTouch:tou forEvent:event];
                    return;
                }
            }
            if (self.wpEdges == UIRectEdgeRight) {
                if (touchP.x < rigM) {
                    [self ignoreTouch:tou forEvent:event];
                    return;
                }
            }
            if (self.wpEdges == UIRectEdgeAll) {
                if (touchP.y > topM && touchP.y < butM && touchP.x > lefM && touchP.x < rigM) {
                    [self ignoreTouch:tou forEvent:event];
                    return;
                }
            }
        }
        self.isCorrected = YES;//识别正确,当前状态UIGestureRecognizerStatePossible
    }
    
    - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        [super touchesMoved:touches withEvent:event];
        if (!self.isCorrected) {
            //不满足began的情况 直接返回
            return;
        }
        //识别正确 移动既是began
        self.state = UIGestureRecognizerStateBegan;//自动内部会自动改变状态为move 
    }
    
    - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        [super touchesEnded:touches withEvent:event];
        if(self.isCorrected){
            self.state = UIGestureRecognizerStateEnded;
            self.state = UIGestureRecognizerStateRecognized;
            [self reset];
        }
    }
    
    #pragma mark - 重写方法
    - (void)reset{
        [super reset];
        self.isCorrected = NO;
    }
    
    //没有end状态 又识别到其他手势的时候  多了手指出来
    - (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer{
        BOOL res = [super canPreventGestureRecognizer:preventedGestureRecognizer];
        self.state = UIGestureRecognizerStateCancelled;
        [self reset];
        return res;
    }
    
    //第一次识别 touchBegan之后调用
    - (BOOL)shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
        BOOL res = [super shouldRequireFailureOfGestureRecognizer:otherGestureRecognizer];
        return res;
    }
    
    #pragma mark - 默认值设置
    //重写init 设置默认值
    - (instancetype)init{
        if(self = [super init]){
            self.wpEdges = UIRectEdgeAll;
            self.wpNumberOfTapsRequired = 0;
            self.wpNumberOfTouchesRequired = 1;
            self.isCorrected = NO;
        }
        return self;
    }
    
    - (instancetype)initWithTarget:(id)target action:(SEL)action{
        if (self = [super initWithTarget:target action:action]) {
            self.wpEdges = UIRectEdgeAll;
            self.wpNumberOfTapsRequired = 0;
            self.wpNumberOfTouchesRequired = 1;
            self.isCorrected = NO;
        }
        return self;
    }
    @end
    

    相关文章

      网友评论

      • PGOne爱吃饺子:大佬 - (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer
        这个方法如果返回No表示什么啊
      • 汉水之畔:不错不错
        HoyaWhite:@汉水之畔 谢谢,这些是比较基础的东西 :grin:

      本文标题:【iOS】API之UIGestureRecognizer及自定义

      本文链接:https://www.haomeiwen.com/subject/byrmsttx.html