iOS开发iOS开发-开源类库

iOS手势密码开发

2017-09-27  本文已影响1391人  KingTortoise

前序

最近公司APP要做改版,其中要使用手势密码锁,所以提前写了一个小Demo练练手,该Demo参考了DBGuestureLock。这里将详细阐述代码细节,希望可以帮助到需要帮助的人。

相关界面GIF

Gesture.gif

代码实现

一、创建相关类

二、绘制界面

从GIF图可以看出,手势密码一共有三种状态(正常、选择、不正确),因此现在GesturePasswordView.h文件中创建一个枚举类型

typedef NS_ENUM(NSInteger, GesturePasswordButtonState) {
    GesturePasswordButtonStateNormal = 0,
    GesturePasswordButtonStateSelected,
    GesturePasswordButtonStateIncorrect,
};

根据Button的形状,可以在GesturePasswordView.h中定义以下属性(之所以写在GesturePasswordView.h中是为了根据不同的状态改变button的样式)

@property (nonatomic, assign)CGFloat strokeWidth; //圆弧的宽度
@property (nonatomic, assign)CGFloat circleRadius; //半径
@property (nonatomic, assign)CGFloat centerPointRadius; //中心圆半径
@property (nonatomic, assign)CGFloat lineWidth; //连接线宽度
@property (nonatomic, strong)UIColor *strokeColor; //圆弧的填充颜色
@property (nonatomic, strong)UIColor *fillColor; //除中心圆点外 其他部分的填充色
@property (nonatomic, strong)UIColor *centerPointColor; //中心圆点的颜色
@property (nonatomic, strong)UIColor *lineColor; //线条填充颜色
@property (nonatomic, assign)BOOL showCenterPoint; //是否显示中心圆
@property (nonatomic, assign)BOOL fillCenterPoint; //是否填充中心圆

在GesturePasswordView.m文件中定义以下属性,用于后面的代码编写。(GesturePasswordStatus枚举类型将会在后面写出)

@property (nonatomic, strong) NSMutableArray *selectorAry;//存储已经选择的按钮
@property (nonatomic, assign)CGPoint currentPoint;//当前处于哪个按钮范围内
@property (nonatomic, assign)GesturePasswordStatus status;//当前控件器所处状态(设置、重新设置、登录)
@property (nonatomic, assign)NSInteger inputNum;//输入的次数
@property (nonatomic, assign)NSInteger resetInputNum;//重置密码时验证旧密码 输入的次数
@property (nonatomic, strong)NSString *firstPassword;//表示设置密码时 第一次输入的手势密码

在GesturePasswordView.m文件中写一个方法来改变button当前的属性值

- (void)setPropertiesByState:(GesturePasswordButtonState)buttonState{
    switch (buttonState) {
        case GesturePasswordButtonStateNormal:
            self.fillCenterPoint = YES;
            self.showCenterPoint = YES;
            self.strokeWidth = 1.f;
            self.centerPointRadius = 10.f;
            self.lineWidth = 2.f;
            self.lineColor = [UIColor whiteColor];
            self.fillColor = [UIColor whiteColor];
            self.strokeColor = [UIColor whiteColor];
            self.centerPointColor = [UIColor grayColor];
            break;
        case GesturePasswordButtonStateSelected:
            self.fillCenterPoint = YES;
            self.showCenterPoint = YES;
            self.strokeWidth = 1.f;
            self.centerPointRadius = 10.f;
            self.lineWidth = 2.f;
            self.lineColor = [UIColor greenColor];
            self.fillColor = RGBA(127, 255, 212, 1);
            self.strokeColor = RGBA(127, 255, 212, 1);
            self.centerPointColor = [UIColor greenColor];
            break;
        case GesturePasswordButtonStateIncorrect:
            self.fillCenterPoint = YES;
            self.showCenterPoint = YES;
            self.strokeWidth = 1.f;
            self.centerPointRadius = 10.f;
            self.lineWidth = 2.f;
            self.lineColor = [UIColor orangeColor];
            self.fillColor = RGBA(255, 245, 238, 1);
            self.strokeColor = RGBA(255, 245, 238, 1);
            self.centerPointColor = [UIColor orangeColor];
            break;
        default:
            break;
    }
}

在GesturePasswordView.m文件中使用initWithFrame:(CGRect)frame方法绘制手势图,这里GesturePasswordView的大小以(320,320)为基准。

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.selectorAry = [[NSMutableArray alloc] init];
        [self setPropertiesByState:GesturePasswordButtonStateNormal];
        NSInteger distance = 320/3;
        NSInteger size = distance/1.5;
        NSInteger margin = size/4;
        float ins = (SCREEN_WIDTH-320)/2;
        self.circleRadius = size/2;
        for (int i = 0; i < 9; i++) {
            NSInteger row = i/3;
            NSInteger col = i%3;
            GesturePasswordButton *gesturePasswordButton = [[GesturePasswordButton alloc] initWithFrame:CGRectMake(ins+col*distance+margin, row*distance+margin, size, size)];
            [gesturePasswordButton setTag:i+1];
            [self addSubview:gesturePasswordButton];
        }
        [self setBackgroundColor:[UIColor clearColor]];
    }
    return self;
}

在GesturePasswordButton.m文件中重新initWithFrame:(CGRect)frame方法。

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.userInteractionEnabled = NO;
    }
    return self;
}

在GesturePasswordButton.m文件中重新- (void)drawRect:(CGRect)rect方法。

    __weak GesturePasswordView *gesView = nil;
    if ([self.superview isKindOfClass:[GesturePasswordView class]]) {
        gesView = (GesturePasswordView *)self.superview;
    }
    CGFloat radius = gesView.circleRadius - gesView.strokeWidth;
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, gesView.strokeWidth);
    CGPoint centerPoint = CGPointMake(rect.size.width/2, rect.size.height/2);
    CGFloat startAngle = -((CGFloat)M_PI/2);
    CGFloat endAngle = ((2 * (CGFloat)M_PI) + startAngle);
    [gesView.strokeColor setStroke];
    CGContextAddArc(context, centerPoint.x, centerPoint.y, radius+gesView.strokeWidth/2, startAngle, endAngle, 0);
    CGContextStrokePath(context);//CGContextStrokePath来描线,即形状 
if (gesView.showCenterPoint) {
        [gesView.fillColor set];//同时设置填充和边框色
        CGContextAddArc(context, centerPoint.x, centerPoint.y, radius, startAngle, endAngle, 0);
        CGContextFillPath(context);//CGContextFillPath来填充形状内的颜色
        if (gesView.fillCenterPoint) {
            [gesView.centerPointColor set];//同时设置填充和边框色
        }else{
            [gesView.centerPointColor setStroke];//设置边框色
        }
        CGContextAddArc(context, centerPoint.x, centerPoint.y, gesView.centerPointRadius, startAngle, endAngle, 0);
        if (gesView.fillCenterPoint) {
            CGContextFillPath(context);//CGContextFillPath来填充形状内的颜色
        }else{
            CGContextStrokePath(context);//CGContextStrokePath来描线,即形状 
        }
    }

整体drawRect方法如下:

- (void)drawRect:(CGRect)rect
{
    [super drawRect:rect];
    __weak GesturePasswordView *gesView = nil;
    if ([self.superview isKindOfClass:[GesturePasswordView class]]) {
        gesView = (GesturePasswordView *)self.superview;
    }
    CGFloat width = rect.size.height > rect.size.width ? rect.size.width : rect.size.height;
    CGFloat radius = (width - 2*gesView.strokeWidth)/2;
    if (radius > (gesView.circleRadius - gesView.strokeWidth)) {
        radius = gesView.circleRadius - gesView.strokeWidth;
    }
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, gesView.strokeWidth);
    CGPoint centerPoint = CGPointMake(rect.size.width/2, rect.size.height/2);
    CGFloat startAngle = -((CGFloat)M_PI/2);
    CGFloat endAngle = ((2 * (CGFloat)M_PI) + startAngle);
    [gesView.strokeColor setStroke];
    CGContextAddArc(context, centerPoint.x, centerPoint.y, radius+gesView.strokeWidth/2, startAngle, endAngle, 0);
    CGContextStrokePath(context);//CGContextStrokePath来描线,即形状 
    
    if (gesView.showCenterPoint) {
        [gesView.fillColor set];//同时设置填充和边框色
        CGContextAddArc(context, centerPoint.x, centerPoint.y, radius, startAngle, endAngle, 0);
        CGContextFillPath(context);//CGContextFillPath来填充形状内的颜色
        
        if (gesView.fillCenterPoint) {
            [gesView.centerPointColor set];//同时设置填充和边框色
        }else{
            [gesView.centerPointColor setStroke];//设置边框色
        }
        
        CGContextAddArc(context, centerPoint.x, centerPoint.y, gesView.centerPointRadius, startAngle, endAngle, 0);
        if (gesView.fillCenterPoint) {
            CGContextFillPath(context);//CGContextFillPath来填充形状内的颜色
        }else{
            CGContextStrokePath(context);//CGContextStrokePath来描线,即形状 
        }
    }
}

这样的话只需要在GesturePasswordViewController中加载GesturePasswordView就可以显示出手势密码的界面。

三、实现三种手势状态

为了实现手势的三种状态,我们需要获取到用户的触摸动作,这里我们将使用UIResponder类,UIResponder类是专门用来响应用户的操作处理各种事件的,所以我们需要在GesturePasswordView.m实现touchesBegan、touchesMoved、touchesEnded方法。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];
    UITouch *touch = [touches anyObject];
    CGPoint point = [touch locationInView:self];//获取触摸点在view中的位置
    self.currentPoint = point;
    for (GesturePasswordButton *btn in self.subviews) {
        if (CGRectContainsPoint(btn.frame, point)) {//判断触摸点是否在当前btn的范围之内
            [btn setSelected:YES];
            if (![self.selectorAry containsObject:btn]) {
                [self.selectorAry addObject:btn];
                [self setPropertiesByState:GesturePasswordButtonStateSelected];//改变该btn的状态
            }
        }
    }
    [self setNeedsDisplay];//重绘view来画线
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];
    UITouch *touch = [touches anyObject];
    CGPoint point = [touch locationInView:self];
    self.currentPoint = point;
    for (GesturePasswordButton *btn in self.subviews) {
        if (CGRectContainsPoint(btn.frame, point)) {
            [btn setSelected:YES];
            if (![self.selectorAry containsObject:btn]) {
                [self.selectorAry addObject:btn];
                [self setPropertiesByState:GesturePasswordButtonStateSelected];
            }
        }
    }
    [self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];
    GesturePasswordButton *btn = [self.selectorAry lastObject];
    [self setCurrentPoint:btn.center];
    [self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect{
    [super drawRect:rect];
    if ([self.selectorAry count] == 0) {
        return;
    }
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path setLineWidth:self.lineWidth];
    [self.lineColor set];
    [path setLineJoinStyle:kCGLineJoinRound];
    [path setLineCapStyle:kCGLineCapRound];
    for (NSInteger i = 0; i < self.selectorAry.count; i ++) {
        GesturePasswordButton *btn = self.selectorAry[i];
        if (i == 0) {
            [path moveToPoint:[btn center]];
        }else{
            [path addLineToPoint:[btn center]];
        }
        [btn setNeedsDisplay];
    }
    [path addLineToPoint:self.currentPoint];
    [path stroke];
}
case GesturePasswordButtonStateNormal:
            [self setUserInteractionEnabled:YES];\\设置当前用户可以进行触摸操作
            [self resetButtons];
            self.fillCenterPoint = YES;
case GesturePasswordButtonStateIncorrect:
            [self setUserInteractionEnabled:NO];\\设置当前用户不可进行触摸操作
            self.fillCenterPoint = YES;
            self.showCenterPoint = YES;
            self.strokeWidth = 1.f;
            self.centerPointRadius = 10.f;
            self.lineWidth = 2.f;
            self.lineColor = [UIColor orangeColor];
            self.fillColor = RGBA(255, 245, 238, 1);
            self.strokeColor = RGBA(255, 245, 238, 1);
            self.centerPointColor = [UIColor orangeColor];
            [self performSelector:@selector(lockState:) withObject:[NSArray arrayWithObject:[NSNumber numberWithInteger:GesturePasswordButtonStateNormal]] afterDelay:0.5f];\\0.5秒后执行lockState方法
\\重置selectorAry数组
- (void)resetButtons {
    for (NSInteger i=0; i<[self.selectorAry count]; i++) {
        GesturePasswordButton *button = self.selectorAry[i];
        [button setSelected:NO];
    }
    [self.selectorAry removeAllObjects];
    [self setNeedsDisplay];
}
\\切换btn状态
- (void)lockState:(NSArray *)states {
    NSNumber *stateNumber = [states objectAtIndex:0];
    [self setPropertiesByState:[stateNumber integerValue]];
}

四、调用GesturePasswordView

现在我们要明确一下这个GesturePasswordView主要用于手势登录、设置手势密码、重置手势密码这三种情况。那么在GesturePasswordViewController.h中可以创建一个枚举类型。

typedef NS_ENUM(NSInteger, GesturePasswordStatus) {
    GesturePasswordStatusSet           = 0,\\设置
    GesturePasswordStatusReset         = 1,\\重置
    GesturePasswordStatusLogin         = 2,\\登录
};

登录、设置、重置三种状态下的流程图

流程图.png

根据流程图我们可以在GesturePasswordView.h中创建六种类型的Block方法

@property (nonatomic, copy) void(^verificationPassword)();//验证旧密码正确
@property (nonatomic, copy) void(^verificationError)();//验证旧密码错误
@property (nonatomic, copy) void(^onPasswordSet)();//第一次输入密码
@property (nonatomic, copy) void(^onGetCorrectPswd)();//第二次输入密码且和第一次一样
@property (nonatomic, copy) void(^onGetIncorrectPswd)();//第二次输入密码且和第一次不一样
@property (nonatomic, copy) void(^errorInput)();//手势密码小于四位数

根据三种状态我们可以在GesturePasswordView.h中创建三种初始化方法

+(instancetype)status:(GesturePasswordStatus)status frame:(CGRect)frame onGetCorrectPswd:(void (^)())GetCorrectPswd onGetIncorrectPswd:(void (^)())GetIncorrectPswd errorInput:(void (^)())errorInput;//用于登录

+(instancetype)status:(GesturePasswordStatus)status frame:(CGRect)frame  onPasswordSet:(void (^)())onPasswordSet onGetCorrectPswd:(void (^)())GetCorrectPswd onGetIncorrectPswd:(void (^)())GetIncorrectPswd errorInput:(void (^)())errorInput;//用于设置

+(instancetype)status:(GesturePasswordStatus)status frame:(CGRect)frame verificationPassword:(void (^)())verificationPassword verificationError:(void (^)())verificationError onPasswordSet:(void (^)())onPasswordSet onGetCorrectPswd:(void (^)())GetCorrectPswd onGetIncorrectPswd:(void (^)())GetIncorrectPswd errorInput:(void (^)())errorInput;//用于重置

在GesturePasswordView.m中实现这三种方法

+(instancetype)status:(GesturePasswordStatus)status frame:(CGRect)frame onGetCorrectPswd:(void (^)())GetCorrectPswd onGetIncorrectPswd:(void (^)())GetIncorrectPswd errorInput:(void (^)())errorInput
{
    return [self status:status frame:frame onPasswordSet:nil onGetCorrectPswd:GetCorrectPswd onGetIncorrectPswd:GetIncorrectPswd errorInput:errorInput];
}

+(instancetype)status:(GesturePasswordStatus)status frame:(CGRect)frame  onPasswordSet:(void (^)())onPasswordSet onGetCorrectPswd:(void (^)())GetCorrectPswd onGetIncorrectPswd:(void (^)())GetIncorrectPswd errorInput:(void (^)())errorInput
{
    return [self status:status frame:frame verificationPassword:nil verificationError:nil onPasswordSet:onPasswordSet onGetCorrectPswd:GetCorrectPswd onGetIncorrectPswd:GetIncorrectPswd errorInput:errorInput];
}

+(instancetype)status:(GesturePasswordStatus)status frame:(CGRect)frame verificationPassword:(void (^)())verificationPassword verificationError:(void (^)())verificationError onPasswordSet:(void (^)())onPasswordSet onGetCorrectPswd:(void (^)())GetCorrectPswd onGetIncorrectPswd:(void (^)())GetIncorrectPswd errorInput:(void (^)())errorInput
{
    GesturePasswordView *gesView = [[GesturePasswordView alloc] initWithFrame:frame];
    gesView.status = status;
    gesView.verificationPassword = verificationPassword;
    gesView.verificationError = verificationError;
    gesView.onPasswordSet = onPasswordSet;
    gesView.onGetCorrectPswd = GetCorrectPswd;
    gesView.onGetIncorrectPswd = GetIncorrectPswd;
    gesView.errorInput = errorInput;
    return gesView;
}

在touchesEnded方法中,我们可以根据当前出入哪种状态做出相应的处理,所以touchesEnded方法应该是这样的:

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];
    if (self.selectorAry.count < 4) {
        self.errorInput();
        [self setPropertiesByState:GesturePasswordButtonStateNormal];
    }else if (self.status == GesturePasswordStatusSet) {
        [self setPasswordBlock];
    }else if(self.status == GesturePasswordStatusReset){
        NSString *password = [self getPassword];
        NSString *inputPassword  = [[NSString alloc] init];
        if (self.resetInputNum == 0) {
            for (GesturePasswordButton *btn in self.selectorAry) {
                inputPassword = [inputPassword stringByAppendingFormat:@"%@",@(btn.tag)];
            }
            if ([inputPassword isEqualToString:password]) {
                self.verificationPassword();
                self.resetInputNum += 1;
                [self performSelector:@selector(lockState:) withObject:[NSArray arrayWithObject:[NSNumber numberWithInteger:GesturePasswordButtonStateNormal]] afterDelay:0.3f];
            }else{
                self.verificationError();
                [self setPropertiesByState:GesturePasswordButtonStateIncorrect];
            }
        }else if(self.resetInputNum == 1){
            [self setPasswordBlock];
        }
    }else if(self.status == GesturePasswordStatusLogin){
        NSString *password = [self getPassword];
        NSString *inputPassword  = [[NSString alloc] init];
        for (GesturePasswordButton *btn in self.selectorAry) {
            inputPassword = [inputPassword stringByAppendingFormat:@"%@",@(btn.tag)];
        }
        if ([inputPassword isEqualToString:password]) {
            self.onGetCorrectPswd();
            [self setPropertiesByState:GesturePasswordButtonStateNormal];
        }else{
            self.onGetIncorrectPswd();
            [self setPropertiesByState:GesturePasswordButtonStateIncorrect];
        }
    }
    GesturePasswordButton *btn = [self.selectorAry lastObject];
    [self setCurrentPoint:btn.center];
    [self setNeedsDisplay];
}
- (void)setPasswordBlock
{
    if (self.inputNum == 0) {
        self.firstPassword = [[NSString alloc] init];
        for (GesturePasswordButton *btn in self.selectorAry) {
            self.firstPassword = [self.firstPassword stringByAppendingFormat:@"%@",@(btn.tag)];
        }
        self.onPasswordSet();
        self.inputNum += 1;
        [self performSelector:@selector(lockState:) withObject:[NSArray arrayWithObject:[NSNumber numberWithInteger:GesturePasswordButtonStateNormal]] afterDelay:0.3f];
    }else{
        NSString *secondPassword = [[NSString alloc] init];
        for (GesturePasswordButton *btn in self.selectorAry) {
            secondPassword = [secondPassword stringByAppendingFormat:@"%@",@(btn.tag)];
        }
        if ([self.firstPassword isEqualToString:secondPassword]) {
            [self savePassWord:secondPassword];
            self.onGetCorrectPswd();
            [self performSelector:@selector(lockState:) withObject:[NSArray arrayWithObject:[NSNumber numberWithInteger:GesturePasswordButtonStateNormal]] afterDelay:0.3f];
        }else{
            self.onGetIncorrectPswd();
            [self setPropertiesByState:GesturePasswordButtonStateIncorrect];
            self.inputNum -= 1;
        }
    }
}

存取密码的方法

- (void)savePassWord:(NSString *)password
{
    NSUserDefaults *userdefault = [NSUserDefaults standardUserDefaults];
    [userdefault setObject:@{@"password":password} forKey:GESTUREPASSWORD];
    [userdefault synchronize];
}

- (NSString *)getPassword
{
    NSDictionary *dic = [[NSUserDefaults standardUserDefaults] objectForKey:GESTUREPASSWORD];
    return [dic objectForKey:@"password"];
}

在GesturePasswordViewController.h这样调用

if (self.status == GesturePasswordStatusSet) {
        self.infoLabel.text = @"请设置手势密码";
        GesturePasswordView *view = [GesturePasswordView status:GesturePasswordStatusSet frame:CGRectMake(0, 200,  SCREEN_WIDTH, 400) onPasswordSet:^() {
            self.infoLabel.text = @"请重新输入刚才设置的手势密码";
        } onGetCorrectPswd:^ {
            self.infoLabel.text = @"设置成功";
            [weakSelf setGesturePasswordSwitchOpen];
        } onGetIncorrectPswd:^ {
            self.infoLabel.text = @"与上一次输入不一致,请重新设置";
        } errorInput:^{
            self.infoLabel.text = @"请至少连接4个点";
        }];
        [self.view addSubview:view];
    }else if (self.status == GesturePasswordStatusReset){
        self.infoLabel.text = @"请验证旧密码";
        GesturePasswordView *view = [GesturePasswordView status:GesturePasswordStatusReset frame:CGRectMake(0, 200,  SCREEN_WIDTH, 400) verificationPassword:^{
            self.infoLabel.text = @"请输入新的手势密码";
        } verificationError:^{
            self.infoLabel.text = @"旧密码验证错误";
        }  onPasswordSet:^ {
            self.infoLabel.text = @"请重新输入刚才设置的手势密码";
        } onGetCorrectPswd:^ {
            self.infoLabel.text = @"设置成功";
            [weakSelf setGesturePasswordSwitchOpen];
        } onGetIncorrectPswd:^ {
            self.infoLabel.text = @"与上一次输入不一致,请重新设置";
        } errorInput:^{
            self.infoLabel.text = @"请至少连接4个点";
        }];
        [self.view addSubview:view];
    }else if (self.status == GesturePasswordStatusLogin){
        self.infoLabel.text = @"请输入手势密码";
        GesturePasswordView *view = [GesturePasswordView status:GesturePasswordStatusLogin frame:CGRectMake(0, 200,  SCREEN_WIDTH, 400) onGetCorrectPswd:^ {
            self.infoLabel.text = @"解锁成功";
            [self loginSuccess];
        } onGetIncorrectPswd:^ {
            self.infoLabel.text = @"密码错误,请重新输入";
        } errorInput:^{
            self.infoLabel.text = @"请至少连接4个点";
        }];
        [self.view addSubview:view];
    }

结束语

整个思路大致就是这样的,主体实现的代码已经在文章中写出来了,当然还有手势密码锁的开启与关闭等一些细节代码没有写出,如果需要完整代码的可以前往github下载Demo。如果有什么不对的地方,希望大家可以及时的指出,谢谢!!!

上一篇下一篇

猜你喜欢

热点阅读