先上效果图:
-
开始思路分析:
前奏: 这个可以说是在简易的画板之上, 增加一些判断就可以完成, 首先我们肯定需要九个圆圈的放置, 我思考的就是用九个 UIView 循环的方法布置一下各自位置!
画图: 一个简单的想法就是, 在九个 View 所属的大 View 画线, 只要经过任意一个圆圈所属的范围我们就把他的颜色改变, 并且记录一下状态(避免重复选中). 问题是第一笔如不在任一个圆圈的范围的话, 那么我们就不能画线, 这就是说要判断起始点是否在九个圆圈的范围中! 还有一些其他的问题我们可以遇到了在解决.
开始: 现在就是布局九个 UIView, 每次依其中一个圆圈开始画图 , 触及到一个圆圈就选中一个圆圈
上代码解析:
// 重写数组的 getter 方法 懒加载
- (NSMutableArray *)pathArray
{
if (!_pathArray)
{
_pathArray = [NSMutableArray arrayWithCapacity:0];
}
return _pathArray;
}```
- 第 1 步: 自定义一个承载的 UIView 类 这里是 SignView 此时我们声明几个属性
```code
// 在这里先定义几个属性
{
// 开始是否选中了一个 圆圈 有的话 才能有下一步活动
BOOL _isSelectStartPoint;
// 记录每次 起点坐标
CGPoint _pointForBegin;
// 记录每次 终点坐标
CGPoint _pointForEnd;
}
# 记录路径的数组 用于画图
@property (strong, nonatomic) NSMutableArray *pathArray;
# 定义一个零时路径变量 接受中间游走的路径 这个不能放到了路径数组里面 用于不是两个圆圈之间的画线
@property (strong, nonatomic) UIBezierPath *tempPath;
# 每次点亮一个圆圈 我就放到数组里面 记录选中顺序 组合成密码
@property (strong, nonatomic) NSMutableArray *runningNumViews;
注意: 我们获取到密码之后, 需要告诉外界我们的密码是多少! 当然我们可以写个. h属性去记录然后到最后图案画好之后传递个需要的地方就行, 这里我写一个协议代理方法, 当绘制完毕之后代理人可以通过方法得到想要的数据! 稍后有体现!
@protocol SignViewDelegate
# 声明协议方法
- (void)SingnView:(SignView *)singnView getPassWordResultWith:(NSString *)signPassWord;
@end
# .h中声明一个属性代理
@property (assign, nonatomic) id delegate;
- 第 2 步: 构建子视图 也就是先把九个圆圈布局一下
- (void)createSubView
{
// 设置九个点 (view) 的大小 (长宽)
CGFloat height = 50;
CGFloat width = 50;
// 设置九个view 的 frame 利用循环
// 算一下相邻的view的间距 左右的话留的空隙和间距一样的空算出水平间距 算出水平的间距
CGFloat lineSpace = (selfWIDTH - 50*3) / 4.0;
// 竖直间距 上下留的空隙和间距一样
CGFloat columnSpace = (selfHEIGHT- 50*3) / 4.0;
for (int i = 0 ; i< 3 ; i++)
{
for (int j = 0 ; j < 3 ; j++)
{
// 算位置 并添加
UIView *tempView = [[UIView alloc] initWithFrame:CGRectMake(lineSpace + 50*j +lineSpace*j , columnSpace + 50*i + columnSpace*i, width, height)];
[self addSubview:tempView];
// 给 view 几个 tag 值加以区分 值是1000 + 1到9;
tempView.tag = 1001 + i*3 +j;
tempView.backgroundColor = [UIColor grayColor];
// 切成圆形
tempView.layer.cornerRadius = 25;
}
}
}
- 2.1 初始化方法中布局 建立九个view表示九个大点
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// 调用构建视图的方法
[self createSubView];
}
return self;
}```
- 并列2.1 可视化编程时候 构建子视图
```code
- (void)awakeFromNib
{
// 调用构建视图方法
[self createSubView];
}```
- 第 3 步: 在触摸开始的方法中获取开始点并且要判断是否在九个圆圈的范围中, 还要考虑的问题是, 第二次绘制的时候, 要在这里进行对数据重新清空再次绘制不同任务.
```code
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// 记录密码的数组每次都要重新去记忆添加 view
self.runningNumViews = [NSMutableArray arrayWithCapacity:0];
// 先把存储的路径清空 第一次花图案之后 再去画的话 就把之前的路径去掉
[self.pathArray removeAllObjects];
// 让九个圆圈恢复原来状态 颜色 和是 否选中 这里用交互的值去判断是否选中
for (UIView *tempView in self.subviews)
{
tempView.userInteractionEnabled = 1;
tempView.backgroundColor = [UIColor grayColor];
}
// 获取触摸的第一个为开始点
_pointForBegin = [touches.anyObject locationInView:self];
// 遍历检查一下开始点是否是在 九个圆圈中某一个的范围中
for (UIView *subView in self.subviews)
{
if (CGRectContainsPoint(subView.frame, _pointForBegin))
{
// 若是在 改变圆圈颜色
subView.backgroundColor = [UIColor greenColor];
// 记录一下有没有开始点
_isSelectStartPoint = 1;
// 更改开始点的坐标
_pointForBegin = subView.center;
// 用 view 的交互去记录是否选中
subView.userInteractionEnabled = 0;
[self.runningNumViews addObject:subView];
}
}
}
- 第 4 步: 在移动的过程中 我们需要不断的获取终点画直线 , 当这个移动点移动到九个圆圈的范围之内的时候我们就把圆圈点亮, 并且要重新绘制直线以圆圈的中点为一个点和起始的圆圈中心点连线.具体参看代码
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// 获取移动中的终点
_pointForEnd = [touches.anyObject locationInView:self];
// 看看有没有开始的圆圈被选中要是有的话才会有一系列的操作 否则不管
if (_isSelectStartPoint)
{
// 创建一个临时的路径再说
self.tempPath = [UIBezierPath bezierPath];
// 设置路径起点
[self.tempPath moveToPoint:_pointForBegin];
// 移动中的临时终点线连起来
[self.tempPath addLineToPoint:_pointForEnd ];
// 判断终点是否在 九个圆圈的范围中
for (UIView *subView in self.subviews)
{
if (CGRectContainsPoint(subView.frame, _pointForEnd) && subView.userInteractionEnabled)
{
// 改变颜色 并关闭 交互 表示选中了
subView.backgroundColor = [UIColor colorWithRed:(arc4random()%345)/346.0 green:(arc4random()%345)/346.0 blue:(arc4random()%345)/346.0 alpha: 1];
subView.userInteractionEnabled = 0;
[self.runningNumViews addObject:subView];
// 重新规划路径
self.tempPath = [UIBezierPath new];
[self.tempPath moveToPoint:_pointForBegin];
[self.tempPath addLineToPoint:subView.center];
// 把路径存放到数组中
[self.pathArray addObject:self.tempPath];
// 为找下一个圆圈位置做准备 要以这个选中圆圈位置中心开始点
_pointForBegin = subView.center;
}
}
}
// 不要忘了 去渲染绘制一下
[self setNeedsDisplay];
}
- 第 5 步: 当我们触摸结束的时候, 要把多余的线条去掉 (不是连接两个圆圈的线条) 而且移动结束也意味着输入密码的结束! 我们外界可以通过代理方法得到当前这次绘制的密码.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// 清除当前的路径 目的是 把多余的没有连接两个圈的线 去掉
self.tempPath = nil;
// 设置没有选中开始点 为下一次绘制做准备
_isSelectStartPoint = 0;
// 绘制渲染一下
[self setNeedsDisplay];
NSMutableString *resulet = [NSMutableString string];
// 可用 tag 值 依据数组中放入 view 的顺序得到密码.
for (UIView *tempView in self.runningNumViews)
{
[resulet appendFormat:@"%ld",tempView.tag - 1000];
}
// 这里去调用代理方法 向外界传递绘制的结果
if (resulet && ![resulet isEqualToString:@""])
{
[self.delegate SingnView:self getPassWordResultWith:resulet];
}
}
- 第 6 步: 关键一步, 那就是绘制方法的完善
// 重新绘图
- (void)drawRect:(CGRect)rect
{
// 找到所有连接两个圆圈的路径 渲染
for (UIBezierPath *path in self.pathArray)
{
[path setLineWidth:6];
[[UIColor redColor] set];
[path stroke];
}
// 临时路径渲染
self.tempPath.lineWidth = 6;
[[UIColor blueColor] set];
[self.tempPath stroke];
}
- 最后 1 步: 在ViewController 中使用这个 SignView 遵循他的代理
并实现方法即可:
- (void)SingnView:(SignView *)singnView getPassWordResultWith:(NSString *)signPassWord
{
NSLog(@"-------->%@",signPassWord);
if ([signPassWord isEqualToString:@"3548"])
{
[UIView animateWithDuration:1 animations:^{
self.signView.frame = CGRectMake(0, self.view.bounds.size.height, 0, 0);
} completion:^(BOOL finished) {
self.signView = nil;
}];
}
}