一个普通的手势解锁插件,可以判断路线交叉
预览
思路
(1)画点画线
dot和line,用ios自带绘图来做
- #pragma mark - 搭建初始UI
- - (void)createUI
- {
- // 提示语
- tipLabel = [[UILabel alloc] initWithFrame:CGRectMake(self.frame.size.width / 2 - 100, 60, 200, 30)];
- tipLabel.text = @"请输入手势";
- tipLabel.textAlignment = NSTextAlignmentCenter;
- tipLabel.textColor = [UIColor redColor];
- [self addSubview:tipLabel];
- // 按钮
- UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
- button.frame = CGRectMake(self.frame.size.width / 2 - 50, 120, 100, 30);
- [button setTitle:@"隐藏手势" forState:UIControlStateNormal];
- [button setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
- [button addTarget:self
- action:@selector(hide)
- forControlEvents:UIControlEventTouchUpInside];
- [self addSubview:button];
- // 九宫格点阵,每个点用view替代,用tag设置索引(其实可以设置图片,用数组存起来索引)
- CGFloat dotSpace = (self.frame.size.width - kCol * kDotSize) / (kCol + 1); // 点之间的间距
- for (int i = 0; i < kRow; i++)
- {
- for (int j = 0; j < kCol; j++)
- {
- UIView *dotView = [[UIView alloc] initWithFrame:CGRectMake(dotSpace + (kDotSize + dotSpace) * j, kBoardTop + dotSpace + (kDotSize + dotSpace) * i, kDotSize, kDotSize)];
- dotView.backgroundColor = [UIColor lightGrayColor]; // 初始颜色
- dotView.tag = (i * kCol + j) + 1000; // 索引
- dotView.layer.cornerRadius = kDotSize / 2; // 切成圆形
- [self addSubview:dotView];
- }
- }
- }
- #pragma mark - 触摸事件
- - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- {
- // 清除之前的轨迹
- [lineArray removeAllObjects];
- // 把所有的点状态重置
- [gestureDotIndexArray removeAllObjects];
- for (int i = 0; i < kRow * kCol; i++)
- {
- UIView *dotView = [self viewWithTag:i + 1000];
- dotView.userInteractionEnabled = YES;
- dotView.backgroundColor = [UIColor lightGrayColor];
- }
- // 获取第一个点
- startPoint = [touches.anyObject locationInView:self];
- // 判断如果在某个dot里面就开始记录
- for (int i = 0; i < kRow * kCol; i++)
- {
- UIView *dotView = [self viewWithTag:i + 1000];
- if (CGRectContainsPoint(dotView.frame, startPoint))
- {
- // 第一个点选中了
- isStartDotSelected = YES;
- // 如果在里面就标记
- dotView.backgroundColor = [UIColor greenColor];
- dotView.userInteractionEnabled = NO; // 可以用其他的标志字,这里就简单用这个属性好了
- // 更改起始点为中心
- startPoint = dotView.center;
- // dot添加到轨迹
- [gestureDotIndexArray addObject:[NSNumber numberWithInt:i]];
- }
- }
- }
- - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- {
- // 终止点
- endPoint = [touches.anyObject locationInView:self];
- // 一定在起始点选中的基础上才有轨迹
- if (isStartDotSelected)
- {
- // 临时轨迹
- tempLine = [UIBezierPath bezierPath];
- [tempLine moveToPoint:startPoint];
- [tempLine addLineToPoint:endPoint];
- #ifdef check_intersect
- // 判断与之前的线段是否相交,不算最近的一个有接点的线段(目前体验不够好)
- for (int i = 0; lineArray.count > 0 && i < lineArray.count - 1; i++)
- {
- UIBezierPath *path = lineArray[i];
- // 得到线段端点数组
- NSArray *tempLinePoints = [self getPointsFromPath:tempLine];
- NSArray *pathPoints = [self getPointsFromPath:path];
- // array里面都是元数据,value转成point,因为array里面只能存value
- NSValue *value1 = tempLinePoints.firstObject;
- CGPoint p1 = [value1 CGPointValue];
- NSValue *value2 = tempLinePoints.lastObject;
- CGPoint p2 = [value2 CGPointValue];
- NSValue *value3 = pathPoints.firstObject;
- CGPoint p3 = [value3 CGPointValue];
- NSValue *value4 = pathPoints.lastObject;
- CGPoint p4 = [value4 CGPointValue];
- // 相交测试
- if (checkLineIntersection(p1, p2, p3, p4))
- {
- [self shakeAnimationForView:tipLabel];
- [self resetGesture];
- }
- }
- #endif
- // 判断终点是否在dot里面,并且这个点没有划过
- for (int i = 0; i < kRow * kCol; i++)
- {
- UIView *dotView = [self viewWithTag:i + 1000];
- // 必须两个条件一起判保证点不会重入
- if (CGRectContainsPoint(dotView.frame, endPoint) && dotView.userInteractionEnabled)
- {
- // 如果在里面就标记
- dotView.backgroundColor = [UIColor colorWithRed:(arc4random() % 256) / 256.0f
- green:(arc4random() % 256) / 256.0f
- blue:(arc4random() % 256) / 256.0f
- alpha: 1];
- dotView.userInteractionEnabled = NO; // 可以用其他的标志字,这里就简单用这个属性好了
- // dot添加到轨迹
- [gestureDotIndexArray addObject:[NSNumber numberWithInt:i]];
- // 重新规划路径
- UIBezierPath *settledLine = [[UIBezierPath alloc] init];
- [settledLine moveToPoint:startPoint];
- [settledLine addLineToPoint:dotView.center];
- // 存储路径
- [lineArray addObject:settledLine];
- // 此处判断一下线路是否相交
- // 修改起始点
- startPoint = dotView.center;
- }
- }
- }
- // 重绘
- [self setNeedsDisplay];
- }
- - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- {
- // 处理密码
- [self processPassword];
- // 最后清除存储的密码轨迹
- [self resetGesture];
- // 重绘
- [self setNeedsDisplay];
- }
- #pragma mark - 绘制
- - (void)drawRect:(CGRect)rect
- {
- // 绘制临时路径
- tempLine.lineWidth = 5;
- tempLine.lineJoinStyle = kCGLineJoinRound;
- [[UIColor redColor] set];
- [tempLine stroke];
- // 绘制轨迹
- for (UIBezierPath *path in lineArray)
- {
- path.lineWidth = 5;
- path.lineJoinStyle = kCGLineJoinRound;
- [[UIColor blueColor] set];
- [path stroke];
- }
- }
密码可以是字符串,存到本地,或者用于二次加密
- #pragma mark - 处理手势得到的密码
- - (void)processPassword
- {
- // 得到密码
- NSMutableString *passwordStr = [[NSMutableString alloc] init];
- for (NSNumber *indexNumber in gestureDotIndexArray)
- {
- [passwordStr appendString:[NSString stringWithFormat:@"%d", indexNumber.intValue]];
- }
- switch (_gestureState)
- {
- case CREATE_STATE:
- {
- pwdSetCount++;
- if (pwdSetCount == 1)
- {
- // 密码存文件(或者全局变量)
- [[NSUserDefaults standardUserDefaults] setObject:passwordStr forKey:kPasswordKey];
- [[NSUserDefaults standardUserDefaults] synchronize];
- tipLabel.text = @"请再输一次";
- [self shakeAnimationForView:tipLabel];
- }
- else if (pwdSetCount == kPwdCount)
- {
- // 检验跟第一次是否一样
- NSString *originalPwd = [[NSUserDefaults standardUserDefaults] objectForKey:kPasswordKey];
- if ([passwordStr isEqualToString:originalPwd])
- {
- if (self.passwordSetBlock)
- {
- self.passwordSetBlock([NSString stringWithFormat:@"password created: %@", passwordStr]);
- }
- [self hide];
- }
- else
- {
- tipLabel.text = @"密码校验与第一次不同,重新输入";
- [self shakeAnimationForView:tipLabel];
- pwdSetCount--; // 减回去
- }
- }
- }
- break;
- case VERIFY_STATE:
- {
- // 校验密码
- NSString *originalPwd = [[NSUserDefaults standardUserDefaults] objectForKey:kPasswordKey];
- if ([passwordStr isEqualToString:originalPwd])
- {
- if (self.passwordSetBlock)
- {
- self.passwordSetBlock(@"password verify success!");
- }
- [self hide];
- }
- else
- {
- if (self.passwordSetBlock)
- {
- self.passwordSetBlock(@"password verify failed!");
- }
- tipLabel.text = @"密码校验失败,重新输入";
- [self shakeAnimationForView:tipLabel];
- }
- }
- break;
- default:
- break;
- }
- }
如果要求画出的手势路线不能相交,需要线段相交算法,从线段获得两个端点的坐标
- #pragma mark - 线段相交测试(p1,p2是线段1的端点,p3,p4是线段2的端点)
- bool checkLineIntersection(CGPoint p1, CGPoint p2, CGPoint p3, CGPoint p4)
- {
- CGFloat denominator = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);
- // In this case the lines are parallel so we assume they don't intersect~
- if (denominator <= (1e-6) && denominator >= -(1e-6))
- {
- return true;
- }
- // amazing~
- CGFloat ua = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denominator;
- CGFloat ub = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denominator;
- if (ua >= 0.0f && ua <= 1.0f && ub >= 0.0f && ub <= 1.0f)
- {
- return true;
- }
- return false;
- }
- #pragma mark - 从贝塞尔曲线上得到点列表
- // http://stackoverflow.com/questions/3051760/how-to-get-a-list-of-points-from-a-uibezierpath
- - (NSMutableArray *)getPointsFromPath:(UIBezierPath *)path
- {
- CGPathRef pathCGPath = path.CGPath;
- NSMutableArray *bezierPoints = [NSMutableArray array];
- CGPathApply(pathCGPath, (__bridge voidvoid * _Nullable)(bezierPoints), MyCGPathApplierFunc);
- return bezierPoints.copy;
- }
- void MyCGPathApplierFunc(voidvoid *arrayInfo, const CGPathElement *element)
- {
- NSMutableArray *bezierPoints = (__bridge NSMutableArray *)arrayInfo;
- CGPoint *points = element->points;
- CGPathElementType type = element->type;
- switch(type)
- {
- case kCGPathElementMoveToPoint: // contains 1 point
- [bezierPoints addObject:[NSValue valueWithCGPoint:points[0]]];
- break;
- case kCGPathElementAddLineToPoint: // contains 1 point
- [bezierPoints addObject:[NSValue valueWithCGPoint:points[0]]];
- break;
- case kCGPathElementAddQuadCurveToPoint: // contains 2 points
- [bezierPoints addObject:[NSValue valueWithCGPoint:points[0]]];
- [bezierPoints addObject:[NSValue valueWithCGPoint:points[1]]];
- break;
- case kCGPathElementAddCurveToPoint: // contains 3 points
- [bezierPoints addObject:[NSValue valueWithCGPoint:points[0]]];
- [bezierPoints addObject:[NSValue valueWithCGPoint:points[1]]];
- [bezierPoints addObject:[NSValue valueWithCGPoint:points[2]]];
- break;
- case kCGPathElementCloseSubpath: // contains no point
- break;
- }
- }