iOS开发:手势解锁(带路线相交检测)

一个普通的手势解锁插件,可以判断路线交叉

预览



思路

(1)画点画线
dot和line,用ios自带绘图来做
[objc]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #pragma mark - 搭建初始UI  
  2. - (void)createUI  
  3. {  
  4.     // 提示语  
  5.     tipLabel = [[UILabel alloc] initWithFrame:CGRectMake(self.frame.size.width / 2 - 1006020030)];  
  6.     tipLabel.text = @"请输入手势";  
  7.     tipLabel.textAlignment = NSTextAlignmentCenter;  
  8.     tipLabel.textColor = [UIColor redColor];  
  9.     [self addSubview:tipLabel];  
  10.       
  11.     // 按钮  
  12.     UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];  
  13.     button.frame = CGRectMake(self.frame.size.width / 2 - 5012010030);  
  14.     [button setTitle:@"隐藏手势" forState:UIControlStateNormal];  
  15.     [button setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];  
  16.     [button addTarget:self  
  17.                action:@selector(hide)  
  18.      forControlEvents:UIControlEventTouchUpInside];  
  19.     [self addSubview:button];  
  20.       
  21.     // 九宫格点阵,每个点用view替代,用tag设置索引(其实可以设置图片,用数组存起来索引)  
  22.     CGFloat dotSpace = (self.frame.size.width - kCol * kDotSize) / (kCol + 1); // 点之间的间距  
  23.       
  24.     for (int i = 0; i < kRow; i++)  
  25.     {  
  26.         for (int j = 0; j < kCol; j++)  
  27.         {  
  28.             UIView *dotView = [[UIView alloc] initWithFrame:CGRectMake(dotSpace + (kDotSize + dotSpace) * j, kBoardTop + dotSpace + (kDotSize + dotSpace) * i, kDotSize, kDotSize)];  
  29.             dotView.backgroundColor = [UIColor lightGrayColor]; // 初始颜色  
  30.             dotView.tag = (i * kCol + j) + 1000// 索引  
  31.             dotView.layer.cornerRadius = kDotSize / 2// 切成圆形  
  32.             [self addSubview:dotView];  
  33.         }  
  34.     }  
  35. }  

[objc]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #pragma mark - 触摸事件  
  2. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event  
  3. {  
  4.     // 清除之前的轨迹  
  5.     [lineArray removeAllObjects];  
  6.       
  7.     // 把所有的点状态重置  
  8.     [gestureDotIndexArray removeAllObjects];  
  9.     for (int i = 0; i < kRow * kCol; i++)  
  10.     {  
  11.         UIView *dotView = [self viewWithTag:i + 1000];  
  12.           
  13.         dotView.userInteractionEnabled = YES;  
  14.         dotView.backgroundColor = [UIColor lightGrayColor];  
  15.     }  
  16.       
  17.     // 获取第一个点  
  18.     startPoint = [touches.anyObject locationInView:self];  
  19.       
  20.     // 判断如果在某个dot里面就开始记录  
  21.     for (int i = 0; i < kRow * kCol; i++)  
  22.     {  
  23.         UIView *dotView = [self viewWithTag:i + 1000];  
  24.         if (CGRectContainsPoint(dotView.frame, startPoint))  
  25.         {  
  26.             // 第一个点选中了  
  27.             isStartDotSelected = YES;  
  28.               
  29.             // 如果在里面就标记  
  30.             dotView.backgroundColor = [UIColor greenColor];  
  31.             dotView.userInteractionEnabled = NO// 可以用其他的标志字,这里就简单用这个属性好了  
  32.               
  33.             // 更改起始点为中心  
  34.             startPoint = dotView.center;  
  35.               
  36.             // dot添加到轨迹  
  37.             [gestureDotIndexArray addObject:[NSNumber numberWithInt:i]];  
  38.         }  
  39.     }  
  40.       
  41. }  
  42.   
  43.   
  44.   
  45. - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event  
  46. {  
  47.     // 终止点  
  48.     endPoint = [touches.anyObject locationInView:self];  
  49.       
  50.     // 一定在起始点选中的基础上才有轨迹  
  51.     if (isStartDotSelected)  
  52.     {  
  53.         // 临时轨迹  
  54.         tempLine = [UIBezierPath bezierPath];  
  55.         [tempLine moveToPoint:startPoint];  
  56.         [tempLine addLineToPoint:endPoint];  
  57.           
  58. #ifdef check_intersect  
  59.         // 判断与之前的线段是否相交,不算最近的一个有接点的线段(目前体验不够好)  
  60.         for (int i = 0; lineArray.count > 0 && i < lineArray.count - 1; i++)  
  61.         {  
  62.             UIBezierPath *path = lineArray[i];  
  63.               
  64.             // 得到线段端点数组  
  65.             NSArray *tempLinePoints = [self getPointsFromPath:tempLine];  
  66.             NSArray *pathPoints = [self getPointsFromPath:path];  
  67.               
  68.             // array里面都是元数据,value转成point,因为array里面只能存value  
  69.             NSValue *value1 = tempLinePoints.firstObject;  
  70.             CGPoint p1 = [value1 CGPointValue];  
  71.               
  72.             NSValue *value2 = tempLinePoints.lastObject;  
  73.             CGPoint p2 = [value2 CGPointValue];  
  74.               
  75.             NSValue *value3 = pathPoints.firstObject;  
  76.             CGPoint p3 = [value3 CGPointValue];  
  77.               
  78.             NSValue *value4 = pathPoints.lastObject;  
  79.             CGPoint p4 = [value4 CGPointValue];  
  80.               
  81.             // 相交测试  
  82.             if (checkLineIntersection(p1, p2, p3, p4))  
  83.             {  
  84.                 [self shakeAnimationForView:tipLabel];  
  85.                 [self resetGesture];  
  86.             }  
  87.         }  
  88. #endif  
  89.           
  90.         // 判断终点是否在dot里面,并且这个点没有划过  
  91.         for (int i = 0; i < kRow * kCol; i++)  
  92.         {  
  93.             UIView *dotView = [self viewWithTag:i + 1000];  
  94.               
  95.             // 必须两个条件一起判保证点不会重入  
  96.             if (CGRectContainsPoint(dotView.frame, endPoint) && dotView.userInteractionEnabled)  
  97.             {  
  98.                 // 如果在里面就标记  
  99.                 dotView.backgroundColor = [UIColor colorWithRed:(arc4random() % 256) / 256.0f  
  100.                                                           green:(arc4random() % 256) / 256.0f  
  101.                                                            blue:(arc4random() % 256) / 256.0f  
  102.                                                           alpha: 1];  
  103.                 dotView.userInteractionEnabled = NO// 可以用其他的标志字,这里就简单用这个属性好了  
  104.                   
  105.                 // dot添加到轨迹  
  106.                 [gestureDotIndexArray addObject:[NSNumber numberWithInt:i]];  
  107.                   
  108.                 // 重新规划路径  
  109.                   
  110.                 UIBezierPath *settledLine = [[UIBezierPath alloc] init];  
  111.                 [settledLine moveToPoint:startPoint];  
  112.                 [settledLine addLineToPoint:dotView.center];  
  113.                   
  114.                 // 存储路径  
  115.                 [lineArray addObject:settledLine];  
  116.                   
  117.                 // 此处判断一下线路是否相交  
  118.                   
  119.                   
  120.                 // 修改起始点  
  121.                 startPoint = dotView.center;  
  122.             }  
  123.         }  
  124.     }  
  125.     // 重绘  
  126.     [self setNeedsDisplay];  
  127. }  
  128.   
  129. - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event  
  130. {  
  131.     // 处理密码  
  132.     [self processPassword];  
  133.       
  134.     // 最后清除存储的密码轨迹  
  135.     [self resetGesture];  
  136.       
  137.     // 重绘  
  138.     [self setNeedsDisplay];  
  139.       
  140. }  

[objc]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #pragma mark - 绘制  
  2. - (void)drawRect:(CGRect)rect  
  3. {  
  4.     // 绘制临时路径  
  5.       
  6.     tempLine.lineWidth = 5;  
  7.     tempLine.lineJoinStyle = kCGLineJoinRound;  
  8.     [[UIColor redColor] set];  
  9.     [tempLine stroke];  
  10.       
  11.     // 绘制轨迹  
  12.     for (UIBezierPath *path in lineArray)  
  13.     {  
  14.         path.lineWidth = 5;  
  15.         path.lineJoinStyle = kCGLineJoinRound;  
  16.         [[UIColor blueColor] set];  
  17.         [path stroke];  
  18.     }  
  19. }  



(2)密码处理
密码可以是字符串,存到本地,或者用于二次加密
[objc]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #pragma mark - 处理手势得到的密码  
  2. - (void)processPassword  
  3. {  
  4.     // 得到密码  
  5.     NSMutableString *passwordStr = [[NSMutableString alloc] init];  
  6.     for (NSNumber *indexNumber in gestureDotIndexArray)  
  7.     {  
  8.         [passwordStr appendString:[NSString stringWithFormat:@"%d", indexNumber.intValue]];  
  9.     }  
  10.       
  11.     switch (_gestureState)  
  12.     {  
  13.         case CREATE_STATE:  
  14.         {  
  15.             pwdSetCount++;  
  16.               
  17.             if (pwdSetCount == 1)  
  18.             {  
  19.                 // 密码存文件(或者全局变量)  
  20.                 [[NSUserDefaults standardUserDefaults] setObject:passwordStr forKey:kPasswordKey];  
  21.                 [[NSUserDefaults standardUserDefaults] synchronize];  
  22.                   
  23.                 tipLabel.text = @"请再输一次";  
  24.                 [self shakeAnimationForView:tipLabel];  
  25.             }  
  26.             else if (pwdSetCount == kPwdCount)  
  27.             {  
  28.                 // 检验跟第一次是否一样  
  29.                 NSString *originalPwd = [[NSUserDefaults standardUserDefaults] objectForKey:kPasswordKey];  
  30.                 if ([passwordStr isEqualToString:originalPwd])  
  31.                 {  
  32.                     if (self.passwordSetBlock)  
  33.                     {  
  34.                         self.passwordSetBlock([NSString stringWithFormat:@"password created: %@", passwordStr]);  
  35.                     }  
  36.                     [self hide];  
  37.                 }  
  38.                 else  
  39.                 {  
  40.                     tipLabel.text = @"密码校验与第一次不同,重新输入";  
  41.                     [self shakeAnimationForView:tipLabel];  
  42.                     pwdSetCount--; // 减回去  
  43.                 }  
  44.             }  
  45.               
  46.               
  47.               
  48.         }  
  49.             break;  
  50.               
  51.         case VERIFY_STATE:  
  52.         {  
  53.             // 校验密码  
  54.             NSString *originalPwd = [[NSUserDefaults standardUserDefaults] objectForKey:kPasswordKey];  
  55.             if ([passwordStr isEqualToString:originalPwd])  
  56.             {  
  57.                 if (self.passwordSetBlock)  
  58.                 {  
  59.                     self.passwordSetBlock(@"password verify success!");  
  60.                 }  
  61.                 [self hide];  
  62.             }  
  63.             else  
  64.             {  
  65.                 if (self.passwordSetBlock)  
  66.                 {  
  67.                     self.passwordSetBlock(@"password verify failed!");  
  68.                 }  
  69.                   
  70.                 tipLabel.text = @"密码校验失败,重新输入";  
  71.                 [self shakeAnimationForView:tipLabel];  
  72.             }  
  73.               
  74.         }  
  75.             break;  
  76.               
  77.         default:  
  78.             break;  
  79.     }  
  80. }  

(3)线段相交检测
如果要求画出的手势路线不能相交,需要线段相交算法,从线段获得两个端点的坐标
[objc]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #pragma mark - 线段相交测试(p1,p2是线段1的端点,p3,p4是线段2的端点)  
  2. bool checkLineIntersection(CGPoint p1, CGPoint p2, CGPoint p3, CGPoint p4)  
  3. {  
  4.     CGFloat denominator = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);  
  5.       
  6.     // In this case the lines are parallel so we assume they don't intersect~  
  7.     if (denominator <= (1e-6) && denominator >= -(1e-6))  
  8.     {  
  9.         return true;  
  10.     }  
  11.       
  12.     // amazing~  
  13.     CGFloat ua = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denominator;  
  14.     CGFloat ub = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denominator;  
  15.       
  16.     if (ua >= 0.0f && ua <= 1.0f && ub >= 0.0f && ub <= 1.0f)  
  17.     {  
  18.         return true;  
  19.     }  
  20.     return false;  
  21. }  

[objc]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #pragma mark - 从贝塞尔曲线上得到点列表  
  2. // http://stackoverflow.com/questions/3051760/how-to-get-a-list-of-points-from-a-uibezierpath  
  3. - (NSMutableArray *)getPointsFromPath:(UIBezierPath *)path  
  4. {  
  5.     CGPathRef pathCGPath = path.CGPath;  
  6.     NSMutableArray *bezierPoints = [NSMutableArray array];  
  7.     CGPathApply(pathCGPath, (__bridge voidvoid * _Nullable)(bezierPoints), MyCGPathApplierFunc);  
  8.       
  9.     return bezierPoints.copy;  
  10. }  
  11.   
  12. void MyCGPathApplierFunc(voidvoid *arrayInfo, const CGPathElement *element)  
  13. {  
  14.     NSMutableArray *bezierPoints = (__bridge NSMutableArray *)arrayInfo;  
  15.       
  16.     CGPoint *points = element->points;  
  17.     CGPathElementType type = element->type;  
  18.       
  19.     switch(type)  
  20.     {  
  21.         case kCGPathElementMoveToPoint// contains 1 point  
  22.             [bezierPoints addObject:[NSValue valueWithCGPoint:points[0]]];  
  23.             break;  
  24.               
  25.         case kCGPathElementAddLineToPoint// contains 1 point  
  26.             [bezierPoints addObject:[NSValue valueWithCGPoint:points[0]]];  
  27.             break;  
  28.               
  29.         case kCGPathElementAddQuadCurveToPoint// contains 2 points  
  30.             [bezierPoints addObject:[NSValue valueWithCGPoint:points[0]]];  
  31.             [bezierPoints addObject:[NSValue valueWithCGPoint:points[1]]];  
  32.             break;  
  33.               
  34.         case kCGPathElementAddCurveToPoint// contains 3 points  
  35.             [bezierPoints addObject:[NSValue valueWithCGPoint:points[0]]];  
  36.             [bezierPoints addObject:[NSValue valueWithCGPoint:points[1]]];  
  37.             [bezierPoints addObject:[NSValue valueWithCGPoint:points[2]]];  
  38.             break;  
  39.               
  40.         case kCGPathElementCloseSubpath// contains no point  
  41.             break;  
  42.     }  
  43. }  

源代码下载

github: 手势解锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值