一:效果如图:

二:代码:

#import "ClockView.h"

@interface ClockView()

/** 存放的都是当前选中的按钮 */
@property (nonatomic, strong) NSMutableArray *selectBtnArray; //当前手指所在的点
@property (nonatomic, assign) CGPoint curP; @end @implementation ClockView -(NSMutableArray *)selectBtnArray { if (_selectBtnArray == nil) {
_selectBtnArray = [NSMutableArray array];
}
return _selectBtnArray;
}
/**
* 1:在storyboard中拖入view,在自定义view再storyboard中与view进行类关联,在自定义的view中可以实现-(void)drawRect:(CGRect)rect方法,系统默认开启了上下文,
2:在xib加载完毕后会调用awakeFromNib,可以在此方法内设置界面的UI,或是设置xib或是stroryBoard中的内容
*/
-(void)awakeFromNib { //搭建界面添加按钮
[self setUp];
} /**
* 2:九宫格搭建连线按钮:1:先求出最大列数,按钮的宽高(等于图片宽高,Assets中可以查看图片的pix,就为图片的尺寸),并求出间距值 2:i % 最大列数,决定列号,i/最大列号,决定行号 ,设置九宫格每个按钮的frame
3:在此方法中创建按钮,并设置按钮的一些属性。在layoutSubview里设置按钮的frame
*/
- (void)setUp { for (int i = ; i < ; i++) { //创建按钮
UIButton *btn= [UIButton buttonWithType:UIButtonTypeCustom]; btn.userInteractionEnabled = NO; btn.tag = i; //设置按钮图片
[btn setImage:[UIImage imageNamed:@"gesture_node_normal"] forState:UIControlStateNormal];
//设置选中状态下的图片
[btn setImage:[UIImage imageNamed:@"gesture_node_selected"] forState:UIControlStateSelected]; [self addSubview:btn]; } } //获取当前手指所在的点
- (CGPoint)getCurrentPoint:(NSSet *)touches { //1.获取当前手指所在的点
UITouch *touch = [touches anyObject];
return [touch locationInView:self]; } //给定一个点,判断给定的点在不在按钮身上
//如果在按钮身,返回当前所在的按钮,如果不在,返回nil;
- (UIButton *)btnRectContainsPoint:(CGPoint)point { for (UIButton *btn in self.subviews) { if (CGRectContainsPoint(btn.frame, point)) {
//让当前按钮成为选中状态
//btn.selected = YES;
return btn;
} }
return nil;
} /**
* 4:拖拽手势:1:可以用pan手势,pan手势的方法中可以时刻获得平移的点,平移的view,平移的状态begin,change,end三种状态,还可以进行复位
2:也可以用touch方法来替代pan手势的方法,touch方法包括:touchbegin,touchmove,touchend,touchcancle(此方法是收到系统事件的时候会去调用,例如突然来了电话,关机的时候等),在touch方法中可获得UITouch对象,单个touch,anyObject,多个touch,touch.allObjects,还可以获得触摸点,view,经常使用的点是否在某个矩形框内,(可以重写pointInside,或是hitTest方法,返回触摸点和合适的view,一般在父类中重写,返回子类的触摸点或是view,还可以用CGRectContainsPoint(btn.frame, point),判断触摸点,当去判断触摸点的时候必须保证这些点和矩形框都在同一个坐标系下,如果不再同一个坐标系需要进行坐标系转换再去比较)
3:当项目中涉及大量重复的代码时,1:要想到抽方法封装,相同的部分封装在方法内部,不同的部分作为参数传递 2:在封装的时候要考虑功能性的封装,每封装的一段代码只负责一段功能的业务逻辑,例如通过触摸点找到btn的时候,找到的时候直接将按钮返回,而不是在此方法中设置按钮的选中状态,也就是在封装的时候不要将代码写死,如果在此方法中写了按钮的选中状态,有可能以后变成了高亮状态,所以只返回按钮,让外界去设置按钮的状态。
*
*/
//手指开始点击
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//当前的手指所在的点在不在按钮上, 如果在,让按钮成为选中状态
//1.获取当前手指所在的点
//UITouch *touch = [touches anyObject];
//CGPoint curP = [touch locationInView:self];
CGPoint curP = [self getCurrentPoint:touches];
//2.判断curP在不在按钮身上
UIButton *btn = [self btnRectContainsPoint:curP];
if (btn && btn.selected == NO) {//触摸点在按钮身上且按钮的状态是未选中的状态的时候才设置按钮的选中状态并加入到保存的大数组中
btn.selected = YES;
//保存当前选中的按钮
[self.selectBtnArray addObject:btn];
} } //手指移动时调用
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { //当前的手指所在的点在不在按钮上, 如果在,让按钮成为选中状态
//1.获取当前手指所在的点
//UITouch *touch = [touches anyObject];
//CGPoint curP = [touch locationInView:self];
CGPoint curP = [self getCurrentPoint:touches];
//记录当前手指所在的点
self.curP = curP;
//2.判断curP在不在按钮身上
UIButton *btn = [self btnRectContainsPoint:curP];
if (btn && btn.selected == NO) {
btn.selected = YES;
[self.selectBtnArray addObject:btn];
}
//重绘:异步调用drawRect,在移动中绘制连线
[self setNeedsDisplay];
} //手指离开时调用
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSMutableString *str = [NSMutableString string];
//1.取消所有选中的按钮
for (UIButton *btn in self.selectBtnArray) {
NSLog(@"%ld",btn.tag);
btn.selected = NO;
[str appendFormat:@"%ld",btn.tag];
}
//2.清空路径
[self.selectBtnArray removeAllObjects];
[self setNeedsDisplay];
//3.查看当前选中按钮的顺序
NSLog(@"%@",str);
} /**
* 5:1:-(void)drawRect:(CGRect)rect方法:1:作用:当想重绘view的时候可以在在自定义的view中重写,此方法默认系统自动生成了关联view的上下文,不用开启关闭,有系统自动去管理 2:调用:不能手动调用,只能是系统调用系统才会自动生成了关联view的上下文 ,在view的viewWillAppear 执行完后调用,页面出现的时候会调用一次,手动调用[self setNeedsDisplay],系统会异步调用drawRect方法
2:1:画图片:iamge 直接调用drawPoint 或是drawRect,前者是以某个起点开始绘制,绘制出的图形和图片的大小相等,后者是与给出的rect相等 2:画路径:直接绘制路径,设置路径状态,调用
* stroke或是fill方法直接绘制到上下文 3:画继承于UIView的界面如UIImageView或是UIView到上下文,需要将控件的layer绘制到上下文,才可以
*/ -(void)drawRect:(CGRect)rect { if (self.selectBtnArray.count) {
//1.创建路径
UIBezierPath *path = [UIBezierPath bezierPath];
//2.取出所有保存的选中的按钮
for(int i = ; i < self.selectBtnArray.count; i++) {
//取出每一个按钮
UIButton *btn = self.selectBtnArray[i];
//判断当前按钮是不是第一个按钮
if(i == ) {
//如果是,设置成路径的起点
[path moveToPoint:btn.center];
}else {
//添加一根线到按钮的中心:此方法是以上一条线的终点为起点来绘制直线
[path addLineToPoint:btn.center];
}
} //添加一根线到当前手指所在的点
[path addLineToPoint:self.curP]; //设置路径的状态
[path setLineWidth:];//设置线宽
[[UIColor redColor] set];//设置颜色
[path setLineJoinStyle:kCGLineJoinRound];//设置两线交汇处的圆角效果 //3.绘制路径
[path stroke];//stroke描点绘制:他会自动将路径绘制到上下文中,fill,填充,也会自动将路径绘制到上下文中,默认会执行闭环操作
} } /**
* 3:1:在layoutSubviews里设置frame最为严谨,不要忘记调用super ,因为继承UIView,所以可以直接遍历(for in 遍历,外界定义一个index,执行一个index++,可以在for in中获得索引,也可以用for int i 带索引的遍历,根据角标直接从子控件数组中取出)将子控件从子控件数组中取出。2:layoutSubviews配合setNeedslayout ,layoutifneed配合使用,前者,不会立即调用异步调用方法,他是异步调用的(相当于在主线程中增加了一个异步线程,等主线程任务全部执行完,才会执行异步线程,异步调用也就是等所有任务执行完才会调用layoutSubviews),layoutifneed会立即调用layoutSubviews
*/
-(void)layoutSubviews { [super layoutSubviews];
//取出每一个按钮,设置按钮的frame. CGFloat x = ;
CGFloat y = ;
CGFloat btnWH = ; //总共有多少列
int column = ;
CGFloat margin = (self.bounds.size.width - (btnWH * column)) / (column + );
int curC = ;
int curR = ; for (int i =; i < self.subviews.count; i++) { //求当前所在的列
curC = i % column;
//当前所在的行
curR = i / column; x = margin + (btnWH + margin) * curC;
y = margin + (btnWH + margin) * curR;
//取出每一个按钮
UIButton *btn = self.subviews[i];
//设置按钮的frame.
btn.frame = CGRectMake(x, y, btnWH, btnWH);
} } @end

分析界面,当手指在上面移动时,当移动到一个按钮范围内当中, 它会把按钮给成为选中的状态.

并且把第一个选中的按钮当做一个线的起点,当手指移动到某个按钮上时,就会添加一根线到选中的那妞上.

当手指松开时,所有按钮取消选中.所有的线都清空.

实现思路:

先判断点前手指在不在当前的按钮上.如果在按钮上,就把当前按钮成为选中状态.

并且把当前选中的按钮添加到一个数组当中.如果当前按钮已经是选中状态,就不需要再添加到数组中了.

每次移动时,都让它进行重绘.

在绘图当中,遍历出所有的选中的按钮,

判断数组当中的第一个无素,如果是第一个,那么就把它设为路径的起点.其它都在添加一根线到按钮的圆心.

如果当前点不在按钮上.那么就记录住当前手指所在的点.直接从起点添加一根线到当前手指所在的点.

实现步骤:

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. 在绘图方法当中.

创建路径

遍历出有的选中按钮.如果是第一个按钮,把第一个按钮的中心点当做是路径的起点.

其它按钮都直接添加一条线,到该按钮的中心.

遍历完所有的选中按钮后.

最后添加一条线到当前手指所在的点.

iOS开发Quartz2D十二:手势解锁实例的更多相关文章

  1. 从零开始学ios开发(十二):Table Views(中)UITableViewCell定制

    我们继续学习Table View的内容,这次主要是针对UITableViewCell,在前一篇的例子中我们已经使用过UITableViewCell,一个默认的UITableViewCell包含imag ...

  2. 从零开始学ios开发(十二):Table Views(上)

    这次学习的控件非常重要且非常强大,是ios应用中使用率非常高的一个控件,可以说几乎每个app都会使用到它,它就是功能异常强大的Table Views.可以打开你的iphone中的phone.Messa ...

  3. iOS开发:自定义控件实现手势解锁

    自定义控件 1.提供initWithFrame:及initWithCoder:方法来初始化: 2.解锁控件只负责展示.触摸.绘图等,存储轨迹.判断轨迹等操作不是解锁控件要做的.因此要定义一个代理,将轨 ...

  4. iOS开发-定制多样式二维码

    iOS开发-定制多样式二维码   二维码/条形码是按照某种特定的几何图形按一定规律在平台(一维/二维方向上)分布的黑白相间的图形纪录符号信息.使用若干个与二进制对应的几何形体来表示文字数值信息. 最常 ...

  5. IOS开发-文件管理(二)

    IOS开发-文件管理(二) 五.Plist文件 String方式添加               NSString *path = [NSHomeDirectory( )  stringByAppen ...

  6. iOS开发 - Quartz2D画图

    Quartz 2D简单介绍 是一个二维画图引擎,同一时候支持iOS和Mac系统 Quartz 2D能完毕的工作 绘制图形 : 线条\三角形\矩形\圆\弧等 绘制文字 绘制\生成图片(图像) 读取\生成 ...

  7. Solon Web 开发,十二、统一的渲染控制

    Solon Web 开发 一.开始 二.开发知识准备 三.打包与运行 四.请求上下文 五.数据访问.事务与缓存应用 六.过滤器.处理.拦截器 七.视图模板与Mvc注解 八.校验.及定制与扩展 九.跨域 ...

  8. 【Swift】iOS开发历险记(二)

    前言 这个系列主要是一些开发中遇到的坑记录分享,有助于初学者跨过这些坑,攒够 7 条发一篇. 声明  欢迎转载,但请保留文章原始出处:)  博客园:http://www.cnblogs.com 农民伯 ...

  9. [iOS UI进阶 - 5.0] 手势解锁Demo

    A.需求 1.九宫格手势解锁 2.使用了绘图和手势事件   code source: https://github.com/hellovoidworld/GestureUnlockDemo     B ...

随机推荐

  1. Android开源图表库XCL-Charts版本号公布及展示页

    XCL-Charts V2.1 Android开源图表库(XCL-Charts is a free charting library for Android platform.) XCL-Charts ...

  2. WIN8.1的安装和打开"这台电脑"速度很慢的解决办法

    WIN8.1的安装和打开"这台电脑"速度很慢的解决办法 对于非服务器用的电脑,如果电脑的内存在2G或更高,首推的操作系统是 WINDOWS8.1 64位企业版,用了就知道,没有比这流畅懂事的操作系统. ...

  3. php课程 12-42 php中类的关键字有哪些

    php课程 12-42 php中类的关键字有哪些 一.总结 一句话总结:const.final.static 1.类常量-const2.最终版本-final3.静态成员-static 1.php中类常 ...

  4. HTTP协议建立连接、通讯与关闭连接全过程

    为解决服务器TimeWait多的问题,了解了一下TCP/IP协议的连接过程.以访问一静态页面为例,从建立连接到访问拿到数据,然后关闭的整个过程.使用EtherPeek截图如下:   图首为一次交互过程 ...

  5. mysql中load data Infile运用

    速度比insert要快20倍.共享一下java程序操作. package com.mysql.csv; import java.sql.Connection; import java.sql.Driv ...

  6. ORACLE10g R2【单实例 FS→单实例FS】

    ORACLE10g R2[单实例FS→单实例FS] 本演示案例所用环境:   primary standby OS Hostname pry std OS Version RHEL5.8 RHEL5. ...

  7. Django环境搭建(一)

    搭建Django环境之前先搭建python运行环境 需要了解: 解释器(编译器): 计算机不能直接理解任何除机器语言外的其他语言,所以程序员必须要把自己写的语言翻译成机器语言,而将其他语言翻译成机器语 ...

  8. opencv cvPreCornerDetect

    关于OpenCv中cvPreCornerDetect 运行出错解决方法 http://m.blog.csdn.net/blog/wode0239 由于书本上示例的不全,相信大家在做的时候,肯定是无从下 ...

  9. VC++的函数指针和回调函数 及友元函数

    什么是函数指针 函数指针是指向函数的指针变量.也就是说,它是一个指针变量,而且该指针指向一个函数. 对于指针变量来说,它的值是它指向的变量的地址.举个例子:指针变量pi是指向一个整型变量i的指针,则变 ...

  10. MyCat:对MySQL数据库进行分库分表

    本篇前提: mycat配置正确,且能正常启动. 1.schema.xml <table>标签: dataNode -- 分片节点指定(取值:dataNode中的name属性值) rule ...