博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
(五十四)涂鸦的实现和截图的保存
阅读量:5277 次
发布时间:2019-06-14

本文共 6151 字,大约阅读时间需要 20 分钟。

利用touchesMoved来获取各个触摸点,并存入一个数组。

在drawRect方法内,循环生成这些点,当i=0时,使用CGContextMoveToPoint方法移动到起点,其余点都通过CGContextAddLineToPoint方法连线。

这样的问题是起点只有一个,画完一条线如果再开始画,会把上次的终点连接到这次的起点;另一个问题是无法回退到上一笔,因为没有记录每次的起点,只是记录了每一个移动微元。

因此应该使用数组存储一条线(从开始触摸到停止触摸),用一个大数组存储这些小数组,为了方便描述,设大数组为S,小数组为C。

在touchesBegan方法中,新建一个小数组C1,将起点装入C1,并将C1装入S,最后还要调用setNeedsDisplay来更新屏幕上的点。

在touchesMoved方法中,使用S的lastObject方法取出现在所在的C,这时取出的为C1,将新点加入C1,注意因为都是指针指向数组,只要改了C1,就改了C中的C1。不要忘记最后调用setNeedsDisplay来更新屏幕上的点。

在touchesEnded方法中,可以发现和touchesMoved方法过程一致,因此只要调用一次touchesMoved即可,传入自己的参数。

这样不仅记录了所有的点,还记录了每一次开始到终止的位置。

最初我的一个思路是每次drawRect中只重新画最后一个数组,但是发现这样以前的线会消失,经过思考发现是drawRect每次调用前会自动清屏,后来想想这是非常科学的,想想以前用单片机做UI还得手动清屏,这个的确方便。

这样的话,清屏只需要调用S数组的removeAllObjects方法,而回退只需要调用S数组的removeLastObject方法。

具体过程为:

首先新建S数组,并且重写get方法初始化:

@property (nonatomic, strong) NSMutableArray *totalPathPoints;
- (NSMutableArray *)totalPathPoints{    if (_totalPathPoints == nil) {        _totalPathPoints = [NSMutableArray array];    }    return _totalPathPoints;}
起点的操作为新建字数组C,将当前点加入,最后装入S:不要忘记更新屏幕!

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{    UITouch *touch = [touches anyObject];    CGPoint startPos = [touch locationInView:touch.view];        // 每一次开始触摸, 就新建一个数组来存放这次触摸过程的所有点(这次触摸过程的路径)    NSMutableArray *pathPoints = [NSMutableArray array];    [pathPoints addObject:[NSValue valueWithCGPoint:startPos]];        // 添加这次路径的所有点到大数组中    [self.totalPathPoints addObject:pathPoints];        [self setNeedsDisplay];}
移动操作为将当前点加入S数组的最后一个元素:由于是使用指针,因此改变后不必赋值回去。

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{    UITouch *touch = [touches anyObject];    CGPoint pos = [touch locationInView:touch.view];        // 取出这次路径对应的数组    NSMutableArray *pathPoints = [self.totalPathPoints lastObject];    [pathPoints addObject:[NSValue valueWithCGPoint:pos]];        [self setNeedsDisplay];}
终点的操作与移动操作完全一致,因此调用一次移动操作:

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{    [self touchesMoved:touches withEvent:event];}
最后是drawRect方法,因为每次调用drawRect方法都会清屏,因此要重绘所有的点和线。

使用双层循环,外层从S中取得C,内层遍历C中的点,同样地,将第一个点作为起点(MoveToPoint),后面通过连线到达(AddLineToPoint)。

这里顺带复习了使用Quartz2D绘图的方法,先获取上下文,然后调用绘图函数、设置状态等,最后通过Stroke或者Fill方法将上下文中的图像呈现在View上。

- (void)drawRect:(CGRect)rect{    CGContextRef ctx = UIGraphicsGetCurrentContext();    // 注意每次drawRect都会清屏,因此需要重绘所有的线。    for (NSMutableArray *pathPoints in self.totalPathPoints) {        for (int i = 0; i
回退和清屏非常简单,只需要移除S中的最后一个或者全部元素即可:

- (void)clear{    [self.totalPathPoints removeAllObjects];    [self setNeedsDisplay];}- (void)back{    [self.totalPathPoints removeLastObject];    [self setNeedsDisplay];}
要实现截图,首先要获取绘图板View的内容,可以通过给UIImage增加分类来实现捕捉视图:注意上下文有开启就应当有结束。

+ (instancetype)captureWithView:(UIView *)view{    // 1.开启上下文,第二个参数是是否不透明(opaque)NO为透明,这样可以防止占据额外空间(例如圆形图会出现方框),第三个为伸缩比例,0.0为不伸缩。    UIGraphicsBeginImageContextWithOptions(view.frame.size, NO, 0.0);        // 2.将控制器view的layer渲染到上下文    [view.layer renderInContext:UIGraphicsGetCurrentContext()];        // 3.取出图片    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();        // 4.结束上下文    UIGraphicsEndImageContext();        return newImage;}
这样就可以得到View上的视图。

要保存图片,使用UIImageWriteToSavedPhotosAlbum函数,注意这是一个C语言函数:

/**     *  保存图片到用户相册     *     *  @param image              要保存的图片     *  @param completionTarget   完成后调用的方法所在的对象     *  @param completionSelector 完成后调用的方法     *  @param contextInfo        额外上下文消息,一般为空     */    void UIImageWriteToSavedPhotosAlbum(UIImage *image, id completionTarget, SEL completionSelector, void *contextInfo);
需要注意的是,完成后调用的函数是需要传入参数的,官方在上面的函数上给出了建议:

// Adds a photo to the saved photos album.  The optional completionSelector should have the form://  - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo;
因此应当严格按照这个要求写完成后的回调函数,但是注意的是方法名是image:didFinishSavingWithError:contextInfo:,换句话说就是方法名只包括描述部分和冒号,不包括参数类型和参数名。

应该这样调用:

UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
并且实现这个方法:注意这里用到一个技巧,如果成功error会是nil,这样就会进入else,否则会进入error的条件分支。

这里使用了一个第三方类库MBProgressHUD来作为指示器,并且使用了李明杰老师进一步封装的版本,在这里感谢李明杰老师。

- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{    if (error) { // 保存失败        [MBProgressHUD showError:@"保存失败"];    } else { // 保存成功        [MBProgressHUD showSuccess:@"保存成功"];    }}
需要注意的是第一次保存时系统会询问用户是否允许App访问相册,如果不允许则以后不再弹框,要通过修改设置中的隐私->相册中主动开启,如果保存失败,可以通过截图引导用户来操作。

要保存多个路径,还可以使用CGMultablePathRef来创建路径,然后分别加入上下文绘制,需要注意的是它不是OC对象,因此不能直接放入数组,所以这个方法比较麻烦。

还可以使用UIBezierPath对象来实现这个方法,称为贝塞尔曲线对象,每一个UIBezierPath对应一条完整的曲线。可以简单的理解C语言中的CGMutablePathRef对应OC中的UIBezierPath。使用它的好处是全是OC的调用,比较亲切和简洁。

使用UIBezierPath绘图非常简洁,例如绘制一条(0,0)到(100,100)的直线:与之前过程基本一致,好处是面向对象。

UIBezierPath *path = [UIBezierPath bezierPath];[path moveToPoint:CGPointZero];[path addLineToPoint:CGPointMake(100,100)];[path stroke];
这样实现起来就很简单了,创建UIBezierPath对象的数组即可,每一次完整的触摸过程都对应一个UIBezierPath。

起点时创建一个新的UIBezierPath,并且使用moveToPoint记录起点,最后将UIBezierPath加入对象数组,并更新屏幕。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{    // 1.获得当前的触摸点    UITouch *touch = [touches anyObject];    CGPoint startPos = [touch locationInView:touch.view];        // 2.创建一个新的路径    UIBezierPath *currenPath = [UIBezierPath bezierPath];    currenPath.lineCapStyle = kCGLineCapRound;    currenPath.lineJoinStyle = kCGLineJoinRound;        // 设置起点    [currenPath moveToPoint:startPos];        // 3.添加路径到数组中    [self.paths addObject:currenPath];        [self setNeedsDisplay];}
移动和结束时都是获取当前点,取出最后一个UIBezierPath对象并且使用addLineToPoint方法加入这个点:

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{    UITouch *touch = [touches anyObject];    CGPoint pos = [touch locationInView:touch.view];        UIBezierPath *currentPath = [self.paths lastObject];    [currentPath addLineToPoint:pos];        [self setNeedsDisplay];}- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{    [self touchesMoved:touches withEvent:event];}
在drawRect方法中,取出每个path,调用stroke方法绘制:在这里可以设置全局的状态,例如线宽。

for (UIBezierPath *path in self.paths) {        path.lineWidth = 10;        [path stroke];}

转载于:https://www.cnblogs.com/aiwz/p/6154197.html

你可能感兴趣的文章
内置函数
查看>>
ucore lab1实验笔记
查看>>
java内部类概念
查看>>
(60)zabbix网络发现介绍Network Discovery
查看>>
annotation本质
查看>>
shell之文本过滤(awk)
查看>>
学习进度条--第五周
查看>>
获取spring中所有的bean名称
查看>>
linux常用命令
查看>>
java DecimalFormat
查看>>
简单两步快速学会使用Mybatis-Generator自动生成entity实体、dao接口和简单mapper映射(用mysql和oracle举例)...
查看>>
Spring读书笔记-----Spring核心机制:依赖注入
查看>>
如何挂载阿里云的数据盘
查看>>
block extends include三者的差别跟用法
查看>>
服务器安全
查看>>
系统学习qsort1 尤其partition
查看>>
yield生成器对象返回Fiabs元素 分类: python 小练习 ...
查看>>
HDU 1001 Sum Problem
查看>>
BZOJ 1196 [HNOI2006]公路修建问题(二分答案+并查集)
查看>>
Android学习笔记1:初识框架
查看>>