iOS全屏手势

862 阅读5分钟

前几天接到一个需求,在SDK中加入一个画圈的手势,第三方在接入后可通过这个手势来触发某个事件,而且这个手势在任何界面都可以触发.当时第一感觉就是想睡会...

完成需求就需要解决以下几个问题: Q1:如何在全屏任何界面触发,并在触发手势的时候,不影响UIScrollView及其子类的响应. Q2:如何识别用户画的是一个圈

##Q1:如何识别全屏手势 1.第一想法就是直接重写UIWindow使用-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;获取全屏点击坐标,但可能会影响到第三方应用(方案pass,此方案也会出现手势冲突问题) 2.最终解决方案就是创建一个NSObject类,获取当前keyWindow,然后在keyWindow上增加一个pan手势,通过pan手势事件获取CGPoint.

CGPoint touchPoint = [recognizer translationInView:[UIApplication sharedApplication].keyWindow];

    switch (recognizer.state) {
        case UIGestureRecognizerStateBegan://手势开始
            break;
        case UIGestureRecognizerStateChanged://移动
            break;
        case UIGestureRecognizerStateEnded://停止
            break;
        case UIGestureRecognizerStateCancelled://取消
            break;
        case UIGestureRecognizerStateFailed://失败
            break;
        default:
            break;
        }

代码完成,美美的command R.在页面完美的描绘出各个point,返回首页的时候,遇到了问题,UITableView滑动的时候我完美的point消失了.为啥?凭啥!拥护啥? 查阅了很多资料,我的理解就是当手势滑动到UIScrollView时,第一响应者会被其拦截,来响应UIScrollView的滚动事件(个人理解).最终找到使用手势的代理方法解决此问题.

//手势共存支持
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}


##Q2:如何识别用户画的是一个圈 解决完手势问题后,就可以收集一堆数据,拿着这一把数据源又陷入沉思.应该如何处理来认定用户画的是一个圆?怎么求圆心? 突然想到中学时候学到求圆心的方法,把相邻的两个点分为一组,然后通过这两点划直线.再让下一组两点划直线...画出的所有直线再取垂线,所有垂线的焦点就是圆心(好像是寒假作业上一个求破碎镜子的题.....TMD暴露年龄了).这个算法至少需要4次循环,而且计算出的所有垂线焦点也是各处都有,果断放弃默默的拿起了高数书... 找到一个"最小二乘法". 最小二乘法(又称最小平方法)是一种数学优化技术。它通过最小化误差....(自行百度去吧~皮卡丘~~后面会贴代码). 最终计算步骤: 1.把points里的x和y分别取出来,然后去x和y的平均数 2.用最小二乘法计算圆心点 3.取各个点到圆心的距离,取平均数作为平均半径 4.设定一个置信区间(就是你能容忍大于或者小于平均半径的范围) 5.当置信区间大于95%的时候,就可以认为是一个圆形

以下是实现代码:

//
//  GestureRecognizer.h
//
//  Created by Andrew
//  Copyright © 2017年. All rights reserved.
//

#import "GestureRecognizer.h"
#import <AVFoundation/AVFoundation.h>

#define reasonable_distance 40  //可容忍范围
#define reasonable_finger_distance 200

@interface GestureRecognizer() <UIGestureRecognizerDelegate>
{
    NSMutableArray *array;
    UIPanGestureRecognizer *pan;
}

@property (nonatomic, copy) EnableGestureSuccessBlock enableGestureSuccessBlock;

@end

@implementation GestureRecognizer

static GestureRecognizer *_instance;
+ (instancetype)sharedGestureRecognizer
{
    static dispatch_once_t onceToken_GestureRecognizer;
    
    dispatch_once(&onceToken_GestureRecognizer, ^{
        _instance = [[self alloc]init];
        
    });
    return _instance;
}

- (void)startGestureRecognitionWithEnableGestureBlock:(EnableGestureSuccessBlock)enableGesture
{
    _enableGestureSuccessBlock = enableGesture;
    pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(getCoordinates:)];
    pan.delegate = self;
    [[UIApplication sharedApplication].keyWindow addGestureRecognizer:pan];
}

- (void)stopGestureRecognition
{
    [[UIApplication sharedApplication].keyWindow removeGestureRecognizer:pan];
}

- (void)resumeGestureRecogniton
{
    [[UIApplication sharedApplication].keyWindow addGestureRecognizer:pan];
}

//手势共存支持
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

- (void)getCoordinates:(UIPanGestureRecognizer *)recognizer
{
    CGPoint touchPoint = [recognizer translationInView:[UIApplication sharedApplication].keyWindow];
    switch (recognizer.state) {
        case UIGestureRecognizerStateBegan://手势开始
            array = [NSMutableArray array];
            break;
        case UIGestureRecognizerStateChanged://移动
            //记录每一个移动坐标
            [array addObject:@[[NSString stringWithFormat:@"%.f",touchPoint.x],[NSString stringWithFormat:@"%.f",touchPoint.y]]];
            break;
        case UIGestureRecognizerStateEnded://停止
            
            //结束手势,计算圆心
            if (array.count > 10) {//数据大于10,计算才有意义
                [self getCircleCenter:array];
            } else {
                TILog(@"手势识别数据量小于10组");
            }
            break;
        case UIGestureRecognizerStateCancelled://取消
            break;
        case UIGestureRecognizerStateFailed://失败
            
            break;
            
        default:
            break;
    }
}

#pragma mark - 计算圆心方法

- (void)getCircleCenter:(NSMutableArray *)dataSource
{
    NSMutableArray __block *xArr = [NSMutableArray array];
    NSMutableArray __block *yArr = [NSMutableArray array];
    double __block max_X, min_X, max_Y, min_Y = 0.0;
    //分别取 x 和 y坐标的集合
    [dataSource enumerateObjectsUsingBlock:^(NSArray *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if (obj.count >= 2) {
            
            double kx = [[obj objectAtIndex:0] doubleValue];
            double ky = [[obj objectAtIndex:1] doubleValue];
            
            if (kx > max_X) { max_X = kx; }
            if (kx < min_X) { min_X = kx; }
            if (ky > max_Y) { max_Y = ky; }
            if (ky < min_Y) { min_Y = ky; }
            
            [xArr addObject:[obj objectAtIndex:0]];
            [yArr addObject:[obj objectAtIndex:1]];
        }
    }];
    
    //去平均数
    double _x = [[xArr valueForKeyPath:@"@avg.floatValue"] doubleValue];
    double _y = [[yArr valueForKeyPath:@"@avg.floatValue"] doubleValue];
    
    double __block Suuu, Svvv, Suu, Svv, Suv, Suuv, Suvv = 0.0;
    
    //计算圆心
    [dataSource enumerateObjectsUsingBlock:^(NSArray *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        double Ui = [[obj objectAtIndex:0] doubleValue] - _x;
        double Vi = [[obj objectAtIndex:1] doubleValue] - _y;
        
        Suuu = Suuu + pow(Ui, 3);
        Svvv = Svvv + pow(Vi, 3);
        Suu = Suu + pow(Ui, 2);
        Svv = Svv + pow(Vi, 2);
        Suv = Suv + Ui * Vi;
        Suuv = Suuv + pow(Ui, 2) * Vi;
        Suvv = Suvv + pow(Vi, 2) * Ui;
    }];
    
    //圆心x值
    double Xc = (Suuv * Suv - Suuu * Svv - Suvv * Svv + Suv * Svvv) / ((pow(Suv, 2) - Suu * Svv) * 2) + _x;
    //圆心y值
    double Yc = (Suuu * Suv - Suu * Suuv - Suu * Svvv + Suv * Suvv) / ((pow(Suv, 2) - Suu * Svv) * 2) + _y;
    
    if (!isnan(Xc) && !isnan(Yc)
        && Xc > - reasonable_distance && Xc < SCREEN_WIDTH       //圆点x坐标在屏幕范围之内
        && Yc > - reasonable_distance && Yc < SCREEN_HEIGHT      //圆点y坐标在屏幕范围之内
        && Xc < max_X
        && Xc > min_X
        && Yc > min_Y
        && Yc < max_Y) {
        [self determineCirclePointX:Xc withPointY:Yc withDataSource:dataSource];
    } else {
        
        if (_enableGestureSuccessBlock) {
            _enableGestureSuccessBlock(NO);
        }
    }
}

//判定是否是圆形
- (void)determineCirclePointX:(double)point_x withPointY:(double)point_y withDataSource:(NSMutableArray *)dataSource
{
    NSMutableArray __block *distanceArr = [NSMutableArray array];
    
    //计算每个点到圆心的距离
    [dataSource enumerateObjectsUsingBlock:^(NSArray *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if (obj.count >= 2) {
            double x = [[obj objectAtIndex:0] doubleValue];
            double y = [[obj objectAtIndex:1] doubleValue];
            double distance = [self distanceFromPointX:CGPointMake(point_x, point_y) distanceToPointY:CGPointMake(x, y)];
            [distanceArr addObject:[NSString stringWithFormat:@"%.f",distance]];
        }
    }];
    
    //取平均距离
    double avgDistance = [[distanceArr valueForKeyPath:@"@avg.floatValue"] doubleValue];
    
    //计算置信值 >95%可认为是圆
    int __block count = 0;
    [distanceArr enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if (fabs([obj doubleValue] - avgDistance) <= reasonable_distance) {
            count ++;
        }
    }];
    
    if (count / distanceArr.count > 0.80) {
        if (_enableGestureSuccessBlock) {
            //震动提醒
            AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
            //声音提醒
            AudioServicesPlaySystemSound(1109);
            _enableGestureSuccessBlock(YES);
        }
    } else {
        if (_enableGestureSuccessBlock) {
            _enableGestureSuccessBlock(NO);
        }
    }
}


//计算两点之间距离
- (float)distanceFromPointX:(CGPoint)start distanceToPointY:(CGPoint)end
{
    float distance;
    CGFloat xDist = (end.x - start.x);
    CGFloat yDist = (end.y - start.y);
    distance = sqrt((xDist * xDist) + (yDist * yDist));
    return distance;
}
@end

只是一个随笔,希望给一些人提供一些思路.如果有什么不对的地方,望指正.