自定义手势解锁锁控件

一、控件的使用

模仿市面上app的手势解锁功能,实现的小控件,将控件封装到了一个UIView上

二、核心原理技术

 1、触摸事件

(1)UIView的触摸三个触摸响应事件:开始、移动、结束

(2)CGRectContainsPoint 判断触摸点的位置

2、Quartz2D绘图

(1)drawRect 的重绘

(2)UIBezierPath 贝塞尔曲线

3、block成功和失败的回调

三、实现思路

1、解锁键盘中的9个小图标,会根据验证过程而变化颜色,所以考虑用UIButton实现,因为UIButton可以根据设置不同状态,而获得不同的图片。按钮本身不需要点击事件的实现。

2、触摸过程中,实现触摸事件的三个方法:

(1)在开始时判断触摸点是否在某个按钮上,进而改变按钮的状态,从而实现“点亮”。

(2)建立数组,记录每一个被“点亮”的按钮

(3)按照按钮的中心点绘制连线

(4)移动过程中,继续“点亮”按钮并记录

(5)触摸结束,进行逻辑判断,是否解锁成功,解锁密码用按钮的tag拼接(整数串)。根据成功与否,更改界面上按钮的状态,然后再重绘

(6)进行成功或失败的回调

四、源码

1、.h文件

@interface ZQGestureUnlockView : UIView

/**
 *  实例化解锁键盘,宽高320*320,9个按钮,背景黑
 *
 *  @param frame    x,y可用
 *  @param password 输入密码由1~9的数组组成的字符串,不可重复
 *  @param success  解锁成功的回调
 *  @param fail     解锁失败的回调
 *
 *  @return 返回手势锁键盘
 */
+(instancetype)unlockWithFrame:(CGRect)frame Password:(NSString *)password successBlock:(void(^)())success failBlock:(void(^)())fail;

@end

2、宏定义及私有变量定义

//按钮显示列数
#define col 3

//按钮总数
#define sum 9

//按钮的宽高
#define iconWH 80

//默认状态下连线的颜色
#define ZQLineColor [UIColor colorWithRed:0.0 green:170/255.0 blue:255/255.0 alpha:0.5]

@interface ZQGestureUnlockView ()


//记录选中的按钮
@property(nonatomic,strong) NSMutableArray * clickBtnArray;

//记录连线的颜色
@property(nonatomic,strong) UIColor * lineColor;

//记录时刻的触摸点坐标,时刻是最新的点
@property(nonatomic,assign) CGPoint currentPoint;

//用户指定的密码
@property(nonatomic,copy) NSString * password;

//成功回调的block
@property(nonatomic,copy) void(^success)();

//失败回调 block
@property(nonatomic,copy) void(^fail)();


@end

3、.m文件中的方法实现

//初始化方法
+(instancetype)unlockWithFrame:(CGRect)frame Password:(NSString *)password successBlock:(void(^)())success failBlock:(void(^)())fail{
    ZQGestureUnlockView * mainView = [[self alloc]init]; 
    mainView.frame = CGRectMake(frame.origin.x, frame.origin.y, 320, 320);
    mainView.password = password;
    mainView.success = success; 
    mainView.fail = fail;
    
    //生成按钮    
    [mainView setupButtons];
    return mainView;
}


//生成按钮
-(void)setupButtons
{
    //生成按钮
    for (int i = 1; i<sum+1; i++) {
        UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];   
        [button setImage:[UIImage imageNamed:@"gesture_node_normal"] forState:UIControlStateNormal];
        [button setImage:[UIImage imageNamed:@"gesture_node_highlighted"] forState:UIControlStateHighlighted];
        [button setImage:[UIImage imageNamed:@"gesture_node_error"] forState:UIControlStateDisabled];
    
        //增加按钮的tag
        button.tag = i ;
        //初始状态,让按钮不可交互,按钮就是显示用的,没有点击事件
        button.userInteractionEnabled=NO;   
        [self addSubview:button];
}
//记录一下默认的线的颜色 self.lineColor = ZQLineColor; //设置透明 self.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"bg"]]; } //布局子控件 -(void)layoutSubviews { [super layoutSubviews]; CGFloat margin = (self.frame.size.width - col*iconWH)/2; [self.subviews enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { CGFloat x= idx%col *(iconWH + margin); CGFloat y= idx/col * (iconWH +margin); obj.frame=CGRectMake(x, y, iconWH, iconWH); }]; } #pragma mark - 触摸开始,记录开始的点 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { UITouch * touch = touches.anyObject; CGPoint startPoint = [touch locationInView:self]; //遍历所有按钮,判断触摸的位置是否在button范围内 [self.subviews enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { BOOL isContain = CGRectContainsPoint(obj.frame, startPoint); //如果在某个按钮范围内,同时这个按钮没有被点亮 if (isContain && obj.highlighted==NO) { //改变按钮状态为高亮 obj.highlighted = YES; //将这个按钮记录到“选中按钮标记数组” [self.clickBtnArray addObject:obj]; } //如果不在某个按钮内,那么把这个按钮设置为不高亮(不管它是否原来是高亮状态) else { obj.highlighted=NO; } }]; //保存此刻的坐标,用于绘制连线 self.currentPoint = startPoint; } #pragma mark - 触摸移动,同样判断按钮的位置 -(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { UITouch * touch = touches.anyObject; CGPoint movePoint = [touch locationInView:self]; //判断触摸move的位置是否在button范围内,遍历所有按钮 [self.subviews enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { BOOL isContain = CGRectContainsPoint(obj.frame, movePoint); //如果触摸范围在某个按钮上,并且该按钮没有被点亮 if (isContain && obj.highlighted==NO) { obj.highlighted = YES; [self.clickBtnArray addObject:obj]; } }]; //保存此刻的坐标 self.currentPoint = movePoint; //调用view重绘,绘制连线 [self setNeedsDisplay]; } #pragma mark - 触摸结束,进行逻辑判断,重绘连线,显示判断后的连线状态 -(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { //拼装用户手势结束后的密码 NSMutableString * passWordString =[NSMutableString string]; [self.clickBtnArray enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { //合成密码 [passWordString appendFormat:@"%ld",obj.tag]; }]; //证明密码正确 if ([passWordString isEqualToString:self.password]) { //加延迟,是为了密码正确后给一个短暂的停顿 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //先把按钮取消高亮 [self.clickBtnArray enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { obj.highlighted=NO; }]; //再把数组清空 [self.clickBtnArray removeAllObjects]; //最后重绘 [self setNeedsDisplay]; }); //执行验证成功的回调 self.success(); } //密码错误 else { //首先关闭view交互,避免错误操作 self.userInteractionEnabled=NO; //先把按钮高亮去掉,设置成enabled状态,然后把连线变红 [self.clickBtnArray enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { obj.enabled=NO; obj.highlighted=NO; }]; self.lineColor = [UIColor redColor]; [self setNeedsDisplay]; //延迟一下再清除 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //先把按钮enable [self.clickBtnArray enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { obj.enabled=YES; }]; //再把高亮按钮数组清空 [self.clickBtnArray removeAllObjects]; //重绘 [self setNeedsDisplay]; //最后在开启交互 self.userInteractionEnabled=YES; //还原默认连线颜色 self.lineColor=ZQLineColor; }); } //将currentPoint 设置成和最后的高亮按钮中心一样,这样可以在释放触摸后,连线不显示多余的“尾巴” UIButton * btn = self.clickBtnArray.lastObject; self.currentPoint= btn.center; } //view的重绘 -(void)drawRect:(CGRect)rect { UIBezierPath * path = [UIBezierPath bezierPath]; [self.clickBtnArray enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { //设置起始点 if (idx==0) { [path moveToPoint:obj.center]; } //设置增加点 else { [path addLineToPoint:obj.center]; } }]; //线条颜色、宽 [path setLineWidth:10]; UIColor * color = self.lineColor; [color setStroke]; //设置连接 [path setLineJoinStyle:kCGLineJoinRound]; [path setLineCapStyle:kCGLineCapRound]; //再添加实时的“线头” if (self.clickBtnArray!=nil) { [path addLineToPoint:self.currentPoint]; } //绘图 [path stroke]; } //懒加载 -(NSMutableArray *)clickBtnArray { if (!_clickBtnArray) { _clickBtnArray=[NSMutableArray array]; } return _clickBtnArray; }

五、扩展

1、解锁键盘的大小可以根据实际情况定义

2、触摸手势的绘制采用的是不可重复的点,这个可以改进

3、不限验证次数 

4、本例用于娱乐、学习、交流

原文地址:https://www.cnblogs.com/cleven/p/5374429.html