源码下载地址: https://github.com/coderZYGui/ZYGesturesUnlock
效果图:
实现思路:
分析界面,当手指在上面移动时,当移动到一个按钮范围内当中, 它会把按钮给成为选中的状态.
并且把第一个选中的按钮当做一个线的起点,当手指移动到某个按钮上时,就会添加一根线到选中的那妞上.
当手指松开时,所有按钮取消选中.所有的线都清空.
实现思路:
先判断点前手指在不在当前的按钮上.如果在按钮上,就把当前按钮成为选中状态.
并且把当前选中的按钮添加到一个数组当中.如果当前按钮已经是选中状态,就不需要再添加到数组中了.
每次移动时,都让它进行重绘.
在绘图当中,遍历出所有的选中的按钮,
判断数组当中的第一个无素,如果是第一个,那么就把它设为路径的起点.其它都在添加一根线到按钮的圆心.
如果当前点不在按钮上.那么就记录住当前手指所在的点.直接从起点添加一根线到当前手指所在的点.
实现步骤:
1.搭建界面
界面是一个九宫格的布局.九宫格实现思路.
先确定有多少列 cloum = 3;
计算出每列之间的距离
计算为: CGFloat margin = (当前View的宽度 - 列数 * 按钮的宽度) / 总列数 + 1
每一列的X的值与它当前所在的行有关
当前所在的列为:curColum = i % cloum
每一行的Y的值与它当前所在的行有关.
当前所在的行为:curRow = i / cloum
每一个按钮的X值为, margin + 当前所在的列 * (按钮的宽度+ 每个按钮之间的间距)
每一个按钮的Y值为 当前所在的行 * (按钮的宽度 + 每个按钮之间的距离)
具体代码为:
总列娄
int colum = 3;
每个按钮的宽高
CGFloat btnWH = 74;
每个按钮之间的距离
CGFloat margin = (self.bounds.size.width - colum * btnWH) / (colum + 1);
for(int i = 0; i < self.subviews.count; i++ ){
当前所在的列
int curColum = i % colum;
当前所在的行
int curRow = i / colum;
CGFloat x = margin + (btnWH + margin) * curColum;
CGFloat y = (btnWH + margin) * curRow;
取出所有的子控件
UIButton *btn = self.subviews[i];
btn.frame = CGRectMake(x, y, btnWH, btnWH);
}
2.监听手指在上面的点击,移动,松开都需要做操作.
2.1在手指开始点击屏幕时,如果当前手指所在的点在按钮上, 那就让按钮成为选中状态.
所以要遍历出所有的按钮,判断当前手指所在的点在不在按钮上,
如何判断当前点在不在按钮上?
当前方法就是判断一个点在不在某一个区域,如果在的话会返回Yes,不在的话,返回NO.
CGRectContainsPoint(btn.frame, point)
在手指点击屏幕的时候,要做的事分别有
1.获取当前手指所在的点.
UITouch *touch = [touches anyObject];
CGPoint curP = [touch locationInView:self];
2.判断当前点在不在按钮上.
for (UIButton *btn in self.subviews) {
if (CGRectContainsPoint(btn.frame, point)) {
return btn;
}
}
3.如果当前点在按钮上,并且当前按钮不是选中的状态.
那么把当前的按钮成为选中状态.
并且把当前的按钮添加到数组当中.
2.2 当手指在移动的时也需要判断.
判断当前点在按钮上,并且当前按钮不是选中的状态.
那么把当前的按钮成为选中状态.
并且把当前的按钮添加到数组当中.
在移动的时候做重绘的工作.
2.3 当手指离开屏幕时.
取出所有的选中按钮,把所有选中按钮取消选中状态.
清空选中按钮的数组.
绘重绘的工作.
3. 在绘图方法当中.
创建路径
遍历出有的选中按钮.如果是第一个按钮,把第一个按钮的中心点当做是路径的起点.
其它按钮都直接添加一条线,到该按钮的中心.
遍历完所有的选中按钮后.
最后添加一条线到当前手指所在的点.
代码如下:
storyboard文件
ZYLockView文件
//
// ZYClockView.m
// ZYGesturesUnlock
//
// Created by 朝阳 on 2017/11/11.
// Copyright © 2017年 sunny. All rights reserved.
//
#import "ZYClockView.h"
#import "ZYViewController.h"
@interface ZYClockView()
/** 当前选中的按钮数组 */
@property (nonatomic, strong) NSMutableArray *selectBtnArray;
@property (nonatomic, assign) CGPoint curPoint;
@property (nonatomic, strong) ZYViewController *ZYVC;
@end
@implementation ZYClockView
- (ZYViewController *)ZYVC
{
if (!_ZYVC) {
_ZYVC = [[ZYViewController alloc] init];
}
return _ZYVC;
}
- (NSMutableArray *)selectBtnArray
{
if (!_selectBtnArray) {
_selectBtnArray = [NSMutableArray array];
}
return _selectBtnArray;
}
/**
当从xib或storyboard中加载完后调用
*/
- (void)awakeFromNib
{
[super awakeFromNib];
self.backgroundColor = [UIColor clearColor];
[self setUp];
// 沙盒位置
NSLog(@"%@",NSHomeDirectory());
}
/**
初始化ZYClockView
*/
- (void)setUp
{
// 创建Button
for (int i = 0; i < 9; i++) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
// 因为在下面TouchesBegin:方法中,当点击btn的时候,btn来处理事件,因为这个类是lockView类\
TouchesBegin:方法只有在这个View内才可以响应.当让这个View上的子控件btn来处理事件时,\
TouchesBegin: 方法不能响应. 所以应该禁止btn处理事件
button.userInteractionEnabled = NO;
button.tag = i;
[button setImage:[UIImage imageNamed:@"gesture_node_normal"] forState:UIControlStateNormal];
[button setImage:[UIImage imageNamed:@"gesture_node_selected"] forState:UIControlStateSelected];
[self addSubview:button];
}
}
/**
布局子控件
*/
- (void)layoutSubviews
{
[super layoutSubviews];
// 取出每个button,设置其frame
CGFloat x = 0;
CGFloat y = 0;
CGFloat btnWH = 74;
// 九宫格
// 总共列
int column = 3;
// button之间的间距
CGFloat margin = (self.bounds.size.width - (btnWH * column)) / (column + 1);
// 记录当前行号 和 列号
int curRow = 0;
int curCol = 0;
for (int i = 0; i < self.subviews.count; ++i) {
curRow = i / column;
curCol = i % column;
x = margin + (btnWH + margin) * curCol;
y = margin + (btnWH + margin) * curRow;
// 取出每一个button
UIButton *button = self.subviews[i];
button.frame = CGRectMake(x, y, btnWH, btnWH);
}
}
/**
获取当前手指的点
*/
- (CGPoint)getCurrentPoint:(NSSet *)touches
{
UITouch *touch = [touches anyObject];
return [touch locationInView:self];
}
// 给定一个点,判断点是否在按钮身上
- (UIButton *)btnRectContainsPoint:(CGPoint)point
{
// 取出所有的按钮
for (UIButton *button in self.subviews) {
if (CGRectContainsPoint(button.frame, point)) {
return button;
}
}
return nil;
}
/**
开始点击的时候调用
@param touches 不规则的UITouch集合
@param event 触发的事件
*/
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//1. 获取当前手指所在的点
// UITouch *touch = [touches anyObject];
// CGPoint curPoint = [touch locationInView:self];
CGPoint curPoint = [self getCurrentPoint:touches];
//2. 取出所有的按钮
// for (UIButton *button in self.subviews) {
// // 判断当前手指所在的点,是否在按钮范围内
// // CGRectContainsRect方法: 某一个点是否在某个范围内
// // arg1: 某一范围 arg2: 某一点
// if (CGRectContainsPoint(button.frame, curPoint)) {
// button.selected = YES;
// }
// }
// 判断当前手指是否在按钮的frame内,如果在则设置按钮为选中状态
UIButton *button = [self btnRectContainsPoint:curPoint];
if (button && button.selected == NO) {
button.selected = YES;
// 保存当前选中的按钮
[self.selectBtnArray addObject:button];
}
}
/**
开始移动的时候调用
*/
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
//1. 获取当前手指所在的点
CGPoint curPoint = [self getCurrentPoint:touches];
// 记录当前手指所在的点
self.curPoint = curPoint;
//2. 取出所有的按钮
UIButton *button = [self btnRectContainsPoint:curPoint];
// 只是当前点在button上 和 button不是选中状态下
if (button && button.selected == NO) {
button.selected = YES;
// 保存当前选中的按钮
[self.selectBtnArray addObject:button];
}
// 重绘
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSMutableString *string = [NSMutableString string];
//1. 取消选中按钮的选中状态
for (UIButton *button in self.selectBtnArray) {
// NSLog(@"%ld",button.tag);
button.selected = NO;
// 拼接button的索引
[string appendFormat:@"%ld",button.tag];
}
//2. 移除绘制路径
[self.selectBtnArray removeAllObjects];
[self setNeedsDisplay];
// 查看是否是第一次设置密码
NSString *keyPwd = [[NSUserDefaults standardUserDefaults] objectForKey:@"keyPwd"];
if (!keyPwd) {
// 使用偏好设置保存用户的信息到沙盒Library/Preferences目录中
[[NSUserDefaults standardUserDefaults] setObject:string forKey:@"keyPwd"];
[[NSUserDefaults standardUserDefaults] synchronize];
}else{
if ([keyPwd isEqualToString:string]) {
NSLog(@"密码正确");
// UIAlertView *alertV =[[UIAlertView alloc] initWithTitle:@"手势输入正确" message:nil delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
// [alertV show];
// 主窗口的根控制器
UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController;
[rootVC presentViewController:self.ZYVC animated:YES completion:nil];
}else{
NSLog(@"密码错误");
UIAlertView *alertV =[[UIAlertView alloc] initWithTitle:@"手势输入错误,请重试" message:nil delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
[alertV show];
}
}
//3. 查看当前选中按钮的顺序
NSLog(@"选中按钮的顺序为:%@",string);
}
/**
绘制方法
*/
- (void)drawRect:(CGRect)rect
{
// 当程序一进来就调用drawRect方法,此时self.curPoint还没有值,选中按钮数组中没有值.
if (self.selectBtnArray.count) {
//1. 创建路径
UIBezierPath *path = [UIBezierPath bezierPath];
//2. 取出所有选中按钮
for (int i = 0; i < self.selectBtnArray.count; ++i) {
// 取出选中的button
UIButton *selectBtn = self.selectBtnArray[i];
// 如果是第一个按钮,就设置为起点
if (i == 0) {
[path moveToPoint:selectBtn.center];
}else{
// 连接一根线到btn的中心
[path addLineToPoint:selectBtn.center];
}
}
// 在当前手指所在的点上添加一根线
[path addLineToPoint:self.curPoint];
// 设置连线的状态
[path setLineWidth:5];
[[UIColor blueColor] set];
[path setLineJoinStyle:kCGLineJoinRound];
//3. 绘制
[path stroke];
}
}
@end
ZYLabelView文件
#import "ZYLabelView.h"
@implementation ZYLabelView
- (void)drawRect:(CGRect)rect {
NSString *str = @"请输入手势密码";
NSMutableDictionary *dictM = [NSMutableDictionary dictionary];
dictM[NSFontAttributeName] = [UIFont systemFontOfSize:20];
dictM[NSForegroundColorAttributeName] = [UIColor lightGrayColor];
[str drawAtPoint:CGPointZero withAttributes:dictM];
}
@end