iOS UIBezierPath实现手势解锁

一、先来看一下最终效果

        

二、需要用到的主要知识

  1. viewController中点击,移动,点击结束事件的处理
  2. UIBezierPath的使用
  3. 重写drawRect的使用

三、实现的具体步骤

1.ViewController中

我们直接使用view的layer的contents属性来设置背景图片

- (void)viewDidLoad {
    [super viewDidLoad];
    //设置背景
    self.view.layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"Home_refresh_bg"].CGImage);
}

 这里需要注意的是CGImage,同时还需要前面的 (__bridge id _Nullable) 桥接。

2.自定义类UnlockView

2.1 画线

为什么要自定义UnlockView呢?为什么不直接在ViewContrller中写呢?是,是可以写到ViewController中,但是那样模块性就不强,如果以后需要用到这个类的时候,直接就可以使用已经写好的类的了,同时新写一个类可以让我们的代码更加专注于管理这个类,如果写在ViewController中就会很冗杂。

UnlockView.m
-(void)awakeFromNib{
    [super awakeFromNib];
    self.selectedArray = [NSMutableArray array];
    self.backgroundColor = [UIColor clearColor];
    for(int i =0;i<9;i++){
       //创建按钮对象
        UIButton * button = [[UIButton alloc]initWithFrame:CGRectZero];
        //设置图片
        [button setImage:[UIImage imageNamed:@"gesture_node_normal"]             forState:UIControlStateNormal];
        [button setImage:[UIImage imageNamed:@"gesture_node_selected"] forState:UIControlStateSelected];
        //关闭按钮的交互能力
        button.userInteractionEnabled = NO;
        button.tag = i;
        [self addSubview:button];
    }
}

由于我们的UnlockView的整个背景View是在storyboard中拖拽上去的,当我们需要进行按钮的初始化的时候,需要在awakeFromNib方法中写,在这个方法中的 selectArray 表示的是我们选择到的按钮。为什么创建按钮时给的frame是 CGRectzero呢?因为控件是通过storyboard或者xib来创建的,需要自己添加的控件的frame在awakeFromNib中可能因为获取不及时而导致不正确,最终布局出现偏差,所以我们在  layoutSubviews中做按钮的布局:

-(void)layoutSubviews{
    for(int i = 1;i<self.subviews.count;i++){
        UIButton * button = [self.subviews objectAtIndex:i];
        //确定是第几列
        int column = (i-1)%3;
        int row = (i-1)/3;
        button.frame = CGRectMake(kPadding+(74+kPadding)*column, kPadding+(74+kPadding)*row, 74, 74);
    }
}

这里就是典型的九宫格布局,不用多说。至于为什么从1开始,因为最后我们还需要加一个提示的UILabel,这个UILabel是加在按钮的前面的,所以从1开始。

按钮布局好了之后,我们就开始做响应按钮点击的事件,有三个,touchesBegain,touchMoved,touchesEnded:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //获取触摸点坐标
    UITouch * touch = [touches anyObject];
    CGPoint location = [touch locationInView:self];
    //判断某一个点是否在区域内
    for (UIButton *button in self.subviews) {
        if (CGRectContainsPoint(button.frame, location)) {
            //设置按钮的状态
            button.selected = YES;
            [self.selectedArray addObject:button];
        }
    }
}

在刚刚点击上去的时候,我们需要判断当前的点击点是否包含在button的区域内,如果在就改变button的selected属性,然后将这个选中的按钮加入selectArray数组中。

-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //获取触摸点坐标
    UITouch * touch = [touches anyObject];
    CGPoint location = [touch locationInView:self];
    //保存当前手的触摸点
    self.lastPoint = location;
    //判断某一个点是否在区域内
    for (UIButton *button in self.subviews) {
        if (CGRectContainsPoint(button.frame, location)) {
            if (button.selected==NO) {
                //设置按钮的状态
                button.selected = YES;
                [self.selectedArray addObject:button];
            }
        }
    }
    //刷新界面
    //相当于触发drawRect方法
    [self setNeedsDisplay];
}

在touchesMoved方法中,同样判断是否点击在了button中,同时为了保证点亮过的button不会重复加入selectArray中,还需要加一个判断,判断当前的点击点所在的button是否已经被点亮过了。只有当当前button没有被点亮过的情况下才将它点亮,然后加入selectArray数组中。在这个方法中,我们还加入了一个lastpoint变量,它用来记录每次move的坐标,实际上就是每次手在滑动的时的位置,在划线的保证画到我们手的点击处。最后的setNeedDisplay方法调用  drawRect方法,我们在drawRect方法中写具体的画线路径,以达动态画线的效果。

接下来的touchededEnd主要是判断密码的正确与否的逻辑代码,我们后面再说,先来看看画线的代码:

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    //在这里面画线
    //1.确定画的路径
    UIBezierPath * bpath = [UIBezierPath bezierPath];
    //确定线的起始点数组里面第一个按钮
    for (int i =0; i<self.selectedArray.count; i++) {
        UIButton * button = [self.selectedArray objectAtIndex:i];
        //如果是第一个 只需要将path的起始点设到这个按钮的中心
        if (i==0) {
            [bpath moveToPoint:button.center];
        }else{
            //否则就需要画线到按钮的中心点
            [bpath addLineToPoint:button.center];
        }
    }
    //在最后一个按钮和当前触摸点之间画一条线
    [bpath addLineToPoint:_lastPoint];
    //2.画上去
    //设置线条的宽度
    bpath.lineWidth = 5;
    //设置线条的连接处样式  为圆滑
    bpath.lineJoinStyle = kCGLineJoinRound;
    //设置线条的颜色
    [[UIColor whiteColor]set];
    //按照路径画线条
    [bpath stroke];
    
}

这里我们重写drawRect方法,使用UIBezierPath进行路径的定义。首先我们我们需要确定我们画线的起点,我们画线的起点应该是我们加入到selectArray中的第一个button,然后我们将路径的起点移到这个button中心点(可以理解为将画笔移到这个button的中心点),然后其余的点就依次 addLineto 就可以了。这样只有选中的button之间才有连接的线,我们希望手指移到button的外部时,也能在selectArray中的最后一个按钮与当前触摸点之间有一条线,所以我们在循环外面又加上了一个 addlineto ,到达的点就是我们之前在touchesMoved里面定义的lastPoint。之后就是关于画上去的线条的一些设置,不用多说。

重写的drawRect方法会在程序加载起来的时候调用一次,如果需要手动调用,我们需要 使用  setNeedsDisplay 方法。

2.2 密码操作

这样一来画线的操作就算完成了,接下来就是记录和设置密码的操作。首先我们创建一个label来提示用户的操作:

//UnlockView.m
self.titleLabel = [self viewWithTag:1000];

我们的label是通过stroyboard添加上去的,我们用tag值将它取出。

接下来需要确定label需要显示的内容,我们从 NSUserdefaults 中取出按照 "pwd"键名存入的密码,这里需要加入一步判断,如果取出来有东西,说明之前设置过密码了;反之就没有密码,labeld的内容根据密码的有无来确定:

//UnlockView.m
   self.oldPassword = [[NSUserDefaults standardUserDefaults]objectForKey:@"pwd"];
   if (!self.oldPassword) {
        //请绘制密码
        self.titleLabel.text = @"请设置图案密码";
        
    }else{
        //有密码啦,请输入密码
        self.titleLabel.text = @"请绘制密码";
    }

接下来就是绘制图案时记录选中的按钮,由于我们之前给按钮设置了tag值,所以最终的密码可以由一组tag值确定(我们这里设置的tag值都是一位的):

//touchesBegin
//判断某一个点是否在区域内
    for (UIButton *button in self.subviews) {
        if (CGRectContainsPoint(button.frame, location)) {
            //设置按钮的状态
            button.selected = YES;
            [self.selectedArray addObject:button];
            //记录密码
            [self.pwdString appendFormat:@"%d",(int)button.tag];
        }
    }


//touchesMoved
for (UIButton *button in self.subviews) {
        if (CGRectContainsPoint(button.frame, location)) {
            if (button.selected==NO) {
                //设置按钮的状态
                button.selected = YES;
                [self.selectedArray addObject:button];
                //记录密码
                [self.pwdString appendFormat:@"%d",(int)button.tag];
            }
        }
    }

我们使用一个 pwdString来保存绘制的密码。

最后当手指离开屏幕时,说明密码绘制完成了,这时我们需要判断此时是第一次设置密码还是输入解锁的密码:

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    if (self.oldPassword.length>0) {
        if ([_oldPassword isEqualToString:self.pwdString]) {
            self.titleLabel.text = @"解锁成功";
        }else{
            self.titleLabel.text = @"解锁失败,请重新绘制";
        }
    }else{
        if (self.firstString.length==0) {
            self.firstString = [NSString stringWithString:self.pwdString];
            self.titleLabel.text = @"请确认刚刚绘制的密码图案";
        }else{
            if (![self.firstString isEqualToString:self.pwdString]) {
                self.titleLabel.text = @"两次密码绘制不相同,请重新绘制";
                self.firstString = @"";
            }else{
                self.titleLabel.text = @"密码设置成功";
                [[NSUserDefaults standardUserDefaults]setObject:self.firstString forKey:@"pwd"];
            }
        }
    }
    
    //现将所有点亮的按钮的状态改变一下
    for (UIButton * button in self.selectedArray) {
        button.selected = NO;
    }
    //i清空数组
    [self.selectedArray removeAllObjects];
    //刷新屏幕
    [self setNeedsDisplay];
    [self.pwdString setString:@""];
}

基本的逻辑就是首先判断是第一次设置密码,还是直接绘制密码解锁,而在前一种情况下还要判断是第一次设置密码,还是第二次确认密码,而在第二次确认密码的时候,又要分确认密码正确和确认密码错误。密码的逻辑操作之后我们需要将所有按钮的选中状态设置为no,清空数组,刷新屏幕,还要把临时性的 pwdString设置为 “”。

具体demo地址:iOS UIBezierPath实现手势解锁

发布了32 篇原创文章 · 获赞 8 · 访问量 9498
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览