思路:
1.自定义一个View,在View的控制器中添加9格按钮,三行三列,每个按钮设一个tag值;
2.监听down与move事件,将触控点坐标与9个按钮中心点范围进行匹配,符合则加入列表中,并缓存当前移动点的坐标;
3.取出已选中列表与当前临时移动的点,在绘制方法中绘制path路径。
4.监听up事件,从已选中列表中取出每个按钮的tag值,拼成字符串,这就是手势最终结果值,然后再清空按钮选中状态与列表,重绘。
实现方法:
一、在Main.storyboard中拖入一个UIIView,用于存放手势各圆点的子View。
二、新建UIView的控制器类,GestureLockView.h和GestureLockView.m,在内部实现手势绘制:
1.GestureLockView.m中,重写initWithFrame和initWithCoder,用代码方法创建三行三列的9个UIButton:
/*
UIView的初始化方法:通过代码创建时触发
*/
- (id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if(self){
[self initView];
}
return self;
}
/*
UIView的初始化方法:通过xib或storyboard创建时触发
*/
- (id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if(self){
[self initView];
}
return self;
}
/*
自定义初始化方法:初始化一些参数
*/
- (void)initView{
for(int i=0;i<9;i++){
//1.创建item View
UIButton *itemV = [UIButton buttonWithType:UIButtonTypeCustom];
//设置tab,用于最后手松开时获取手势结果值,每个子View一个值,连起来就是手势设置过的值
itemV.tag = i;
//禁用按钮接收触控事件
itemV.userInteractionEnabled = NO;
//默认背景图片
UIImage *unSelectImg = [UIImage imageNamed:@"lock_unselect"];
//2.设置按钮默认背景
[itemV setBackgroundImage:unSelectImg forState:UIControlStateNormal];
//选中背景图片
UIImage *selectedImg = [UIImage imageNamed:@"lock_selected"];
//2.设置按钮选中背景
[itemV setBackgroundImage:selectedImg forState:UIControlStateSelected];
//3.将按钮加到当前View中
[self addSubview:itemV];
}
}
2.实现layoutSubviews方法,获取9个子UIButton,计算子View的位置与大小并布局:
/*
布局子View位置与大小(类似onLayout)
*/
- (void)layoutSubviews{
//调父类此方法初始化
[super layoutSubviews];
int width = 70;
int height = 70;
CGFloat marginTopItem = (self.frame.size.height - height*3) / 4;
CGFloat marginLeftItem = (self.frame.size.width - width*3) / 4;
//循环取出子View,设置每个子View大小与位置
NSInteger count = self.subviews.count;
for (int i=0; i<count; i++) {
//获取每个子View
UIButton *itemV = self.subviews[i];
/*
计算子View的xy坐标
*/
CGFloat x = (i % 3 + 1) * marginLeftItem + (i % 3) * width;
CGFloat y = ((int)(i / 3) + 1) * marginTopItem + ((int)(i / 3)) * height;
//设置子View的frame
itemV.frame = CGRectMake(x, y, width, width);
}
}
3.实现touchesBegan与touchesMoved,判断当前触控的点是否在9个按钮中心坐标范围内,是则加入成员变量列表中备用,以及缓存当前移动的点坐标:
//存放选中子View列表
@property (nonatomic, strong) NSMutableArray *selectViews;
//缓存当前拖动的点
@property (nonatomic, assign) CGPoint curPoint;
/*
懒加载,初始化列表
*/
- (NSMutableArray *)selectViews{
if(_selectViews == nil){
_selectViews = [NSMutableArray array];
}
return _selectViews;
}
/*
自定义方法,处理触控事件
*/
- (void) processTouch:(NSSet<UITouch *> *)touches{
//获取UITouch
UITouch *touch = [touches anyObject];
//根据触控的View获取点xy
CGPoint point = [touch locationInView:touch.view];
//缓存当前移动的点坐标
self.curPoint = point;
for(UIButton *itemV in self.subviews){
//判断当前触控的点是否在子View范围内,并且未选择时执行
if([self isContains:itemV point:point] && itemV.selected == NO){
//设置触控到的子View为选中状态
itemV.selected = YES;
//将选中子View的加入列表中
[self.selectViews addObject:itemV];
}
}
//重绘
[self setNeedsDisplay];
}
/*
自定义方法,判断当前触控是否在每个按钮范围内,是则返回YES,否则NO
*/
- (BOOL)isContains:(UIButton *)itemV point:(CGPoint)point{
//获取子item View的中心点范围
CGRect itemCenterFrame = CGRectMake(itemV.center.x - 20, itemV.center.y - 20, 40, 40);
//判断中心范围与当前触控的点是否重合
if(CGRectContainsPoint(itemCenterFrame, point)){
return YES;
}
return NO;
}
/*
指手按下时触发
*/
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//执行自定义方法
[self processTouch:touches];
}
/*
指手移动时触发
*/
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//执行自定义方法
[self processTouch:touches];
}
4.实现drawRect方法,取出已选按钮列表与临时点CGPoint,绘制path路径:
/*
UIView的初始化方法:绘制path
*/
- (void)drawRect:(CGRect)rect {
if(self.selectViews == nil || self.selectViews.count == 0){
return;
}
//创建路劲path
UIBezierPath *path = [UIBezierPath bezierPath];
NSInteger count = self.selectViews.count;
//循环获取所有选中的子View,根据这些View的点xy坐标,创建手势路径path
for (int i =0; i<count; i++) {
UIButton *itemV = self.selectViews[i];
if(i == 0){
//设置起点
[path moveToPoint:itemV.center];
} else {
//连接其他点
[path addLineToPoint:itemV.center];
}
}
//绘制当前移动的点
if(CGPointEqualToPoint(self.curPoint, CGPointZero) == NO){
[path addLineToPoint:self.curPoint];
}
//设置path宽度
path.lineWidth = 8;
//设置圆角
path.lineCapStyle = kCGLineCapRound;
path.lineJoinStyle = kCGLineCapRound;
//设置path颜色
[[UIColor blueColor] set];
//绘制path
[path stroke];
}
5.实现touchesEnded和touchesCancelled,在手指松开时,取出选中列表,获取每个子View的tag值,拉成字符串,就是一个手势轨迹值,然后重置所有按钮为未选状态,清空已选列表,重绘界面:
/*
指手松开时触发
*/
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
/*
将手势轨迹拼成字符串
*/
NSMutableString *sb = [NSMutableString string]; //可变字符串, 类似StringBuilder
for(UIButton *itemV in self.selectViews){
//拼接每个选中的子View的tab
[sb appendFormat:@"%d", itemV.tag];
//重置为未选中状态
[itemV setSelected:NO];
}
//打印手势轨迹
NSLog(@"手势轨迹为:%@", sb);
//调用列表中所有View的setSelected方法,重置为未选中状态
// [self.selectViews makeObjectsPerformSelector:@selector(setSelected:) withObject:@(NO)];
//清空列表,主要为了清空绘制路径
[self.selectViews removeAllObjects];
//重绘
[self setNeedsDisplay];
}
/*
触控取消时触发
*/
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//取消时执行松开操作
[self touchesEnded:touches withEvent:event];
}