最终效果图:
控制器:
//
// BeyondViewController.m
// 39_触摸解锁
//
// Created by beyond on 14-9-17.
// Copyright (c) 2014年 com.beyond. All rights reserved.
//
#import "BeyondViewController.h"
#import "LockView.h"
#import "LockViewDelegate.h"
// 遵守协议,用于获知,用户在LockView上画的解锁路径
@interface BeyondViewController ()<LockViewDelegate>
@end
@implementation BeyondViewController
#pragma mark - LockView代理方法
// 用于获知,用户在LockView上画的解锁路径
- (void)lockView:(LockView *)lockView didFinishPath:(NSString *)path
{
[MBProgressHUD showSuccess:path];
NSLog(@"获得用户的手势路径:%@", path);
}
@end<span style="font-family:Courier New;color:#393939;"><span style="font-size: 24px; line-height: 26px; background-color: rgb(245, 245, 245);"><strong>
</strong></span></span>
自定义CircleButton
//
// Circle.m
// 39_触摸解锁
//
// Created by beyond on 14-9-17.
// Copyright (c) 2014年 com.beyond. All rights reserved.
//
#import "Circle.h"
@implementation Circle
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// 自定义方法,初始化
[self setup];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
// 自定义方法,初始化
[self setup];
}
return self;
}
// 自定义方法,初始化
- (void)setup
{
// 设置按钮不可用(不可点击)
self.userInteractionEnabled = NO;
// 设置默认的背景图片
[self setBackgroundImage:[UIImage imageNamed:@"gesture_node_normal"] forState:UIControlStateNormal];
// 设置选中时的背景图片(selected)
[self setBackgroundImage:[UIImage imageNamed:@"gesture_node_highlighted"] forState:UIControlStateSelected];
}
@end
LockView定义的协议
//
// LockViewDelegate.h
// 39_触摸解锁
//
// Created by beyond on 14-9-17.
// Copyright (c) 2014年 com.beyond. All rights reserved.
// 自定义的LockView的代理必需 遵守的协议
#import <Foundation/Foundation.h>
@class LockView;
@protocol LockViewDelegate <NSObject>
@optional
// 锁屏手势绘制完成时,调用本方法,通知外界
- (void)lockView:(LockView *)lockView didFinishPath:(NSString *)path;
@end
核心代码,自定义的LockView
//
// LockView.h
// 39_触摸解锁
//
// Created by beyond on 14-9-17.
// Copyright (c) 2014年 com.beyond. All rights reserved.
//
#import <UIKit/UIKit.h>
@protocol LockViewDelegate;
@interface LockView : UIView
// 代理,为当前控制器,目的是,解锁手势绘制完成后,通知它
@property (nonatomic, weak) IBOutlet id<LockViewDelegate> delegate;
@end
//
// LockView.m
// 39_触摸解锁
//
// Created by beyond on 14-9-17.
// Copyright (c) 2014年 com.beyond. All rights reserved.
// 重点,核心~~~
#import "LockView.h"
#import "Circle.h"
#import "LockViewDelegate.h"
@interface LockView()
// 数组,记住所有的touchMove经过的circle
@property (nonatomic, strong) NSMutableArray *circleArr;
// 手指一直与屏幕保持触摸的点,活动的点,用于绘制最后一个circle的中心到 用户触摸点的线段
@property (nonatomic, assign) CGPoint activePoint;
@end
@implementation LockView
#pragma mark - 懒加载
// 数组,记住所有的touchMove经过的circle
- (NSMutableArray *)circleArr
{
if (_circleArr == nil) {
_circleArr = [NSMutableArray array];
}
return _circleArr;
}
#pragma mark - 初始化
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// 调用自定义方法,一次性添加所有的按钮
[self addAllCircles];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]){
// 调用自定义方法,一次性添加所有的按钮
[self addAllCircles];
}
return self;
}
// 调用自定义方法,一次性添加所有的按钮
- (void)addAllCircles
{
for (int index = 0; index<9; index++) {
// 创建自定义按钮,绑定tag目的是,将来用于验证锁的顺序
Circle *btn = [Circle buttonWithType:UIButtonTypeCustom];
btn.tag = index;
// 添加按钮
[self addSubview:btn];
}
}
#pragma mark - 父类方法
// 每添加一个circle,就调整一下所有子控件的frame
- (void)layoutSubviews
{
// 必须先调用父类的方法
[super layoutSubviews];
// 遍历所有的子按钮,重新调整frame
for (int i = 0; i<self.subviews.count; i++) {
// 取出按钮
Circle *btn = self.subviews[i];
// 设置frame
CGFloat btnW = 74;
CGFloat btnH = 74;
// 共三列
int totalColumns = 3;
// 第i个按钮 所在的列
int col = i % totalColumns;
// 第i个按钮 所在的行
int row = i / totalColumns;
// 间距均分
CGFloat marginX = (self.frame.size.width - totalColumns * btnW) / (totalColumns + 1);
CGFloat marginY = marginX;
// 设置frame
CGFloat btnX = marginX + col * (btnW + marginX);
CGFloat btnY = row * (btnH + marginY);
btn.frame = CGRectMake(btnX, btnY, btnW, btnH);
}
}
#pragma mark - 触摸方法
// 手指按下
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// 1.必须先清空上一次(手指与屏幕一直接触的点)
self.activePoint = CGPointZero;
// 2.调用自定义方法,获得触摸开始的这个点
CGPoint startPoint = [self pointWithTouches:touches];
// 3.调用自定义方法,获得触摸到的按钮(如果有的话)
Circle *btn = [self buttonWithPoint:startPoint];
// 4.设置该被触摸到的按钮的状态(如果有)
if (btn && btn.selected == NO) {
btn.selected = YES;
// 按钮数组中没有,才要添加到对象数组
[self.circleArr addObject:btn];
}
// 5.刷新界面,下面方法会自动调用drawRect
[self setNeedsDisplay];
}
// 手指移动(调用频率相当高)
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// 1.调用自定义方法,获得触摸过程中的点
CGPoint pos = [self pointWithTouches:touches];
// 2.调用自定义方法,获得触摸移动时碰到的按钮(如果有的话)
Circle *btn = [self buttonWithPoint:pos];
// 3.设置该被触摸移动时碰到的按钮的状态(如果有)
if (btn && btn.selected == NO) {
// 摸到了按钮
btn.selected = YES;
// 按钮数组中没有,才要添加到对象数组
[self.circleArr addObject:btn];
} else {
// 没有摸到按钮,就更新activePoint的位置,以便drawRect
self.activePoint = pos;
}
// 4.刷新界面,下面方法会自动调用drawRect
[self setNeedsDisplay];
}
// 触摸结束(手抬起了),通知代理,
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// 1.通知代理
if ([self.delegate respondsToSelector:@selector(lockView:didFinishPath:)]) {
NSMutableString *path = [NSMutableString string];
for (Circle *btn in self.circleArr) {
// tag此时,起到关键作用
[path appendFormat:@"%d", btn.tag];
}
[self.delegate lockView:self didFinishPath:path];
}
// 2.可有可无,清空所有的按钮选中状态(恢复原样)
[self.circleArr makeObjectsPerformSelector:@selector(setSelected:) withObject:@(NO)];
// 3.清空选中的按钮对象数组(为下一次手指按下做准备)
[self.circleArr removeAllObjects];
// 4.刷新界面,下面方法会自动调用drawRect
[self setNeedsDisplay];
}
// 触摸被打断(类似结束)
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesEnded:touches withEvent:event];
}
#pragma mark - 抽取的自定义方法
// 自定义方法,根据touches集合获得对应的触摸点位置坐标
- (CGPoint)pointWithTouches:(NSSet *)touches
{
UITouch *touch = [touches anyObject];
// 返回手指触摸点在对应view中的坐标
return [touch locationInView:touch.view];
}
// 自定义方法,根据触摸点位置坐标,遍历所有按钮,获得被碰手指到的按钮(如果有)
- (Circle *)buttonWithPoint:(CGPoint)point
{
for (Circle *btn in self.subviews) {
// CGFloat wh = 24;
// CGFloat frameX = btn.center.x - wh * 0.5;
// CGFloat frameY = btn.center.y - wh * 0.5;
// if (CGRectContainsPoint(CGRectMake(frameX, frameY, wh, wh), point)) {
if (CGRectContainsPoint(btn.frame, point)) {
return btn;
}
}
return nil;
}
#pragma mark - 核心~绘图
// 最后一步
- (void)drawRect:(CGRect)rect
{
// 如果没有碰到任何按钮,直接返回,不用绘线
if (self.circleArr.count == 0) return;
// 1.开始一个贝塞尔路径
UIBezierPath *path = [UIBezierPath bezierPath];
// 2.遍历所有的按钮,将碰到的按钮的中心添加到路径中
for (int i = 0; i<self.circleArr.count; i++) {
Circle *btn = self.circleArr[i];
if (i == 0) {
// 设置路径起点
[path moveToPoint:btn.center];
} else {
// 添加到路径
[path addLineToPoint:btn.center];
}
}
// 3.连接最后一条,即最后一个碰到的按钮中心 至 activePoint
if (CGPointEqualToPoint(self.activePoint, CGPointZero) == NO) {
// 必须先进行检测
[path addLineToPoint:self.activePoint];
}
// 4.设置绘图属性
path.lineWidth = 8;
// 圆接头
path.lineJoinStyle = kCGLineJoinBevel;
// 线条天蓝色
[[UIColor colorWithRed:32/255.0 green:210/255.0 blue:254/255.0 alpha:0.5] set];
// 5.闭合贝塞尔路径
[path stroke];
}
@end
storyboard