2019独角兽企业重金招聘Python工程师标准>>>

这几天在学习Quartz2D,学习了一个简单画板的实现,现在把实现过程记录一下。

主要用到的点就是画线,截屏,绘制图片,选择图片,以及保存所有绘制的线。

首先在storyboard上布局好控件,设置约束等等,最后的效果是这样:

自定义画板DrawView,使用时可能是从xib中加载,也可能是手动创建,所以创建对象的方法需要实现两个:

#import <UIKit/UIKit.h>@interface DrawView : UIView
/** 线宽 */
@property (nonatomic, assign) NSInteger lineWidth;/** 颜色 */
@property(nonatomic, strong) UIColor *pathColor;/** 图片 */
@property(nonatomic, strong) UIImage *image;- (void)clear;- (void)undo;@end
- (void)awakeFromNib {[self setUp];}- (instancetype)initWithFrame:(CGRect)frame {if (self == [super initWithFrame:frame]) {[self setUp];}return self;
}

setUp初始化方法,初始化时要做的事情就是给画板添加拖动手势,也可以将画笔路径的线宽在这里设置

//自定义初始化方法
- (void)setUp {//添加手势UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];[self addGestureRecognizer:pan];//初始化时设置路径线宽_lineWidth = 2;}

手指在画板上移动时开始绘制线条,这里因为原生的UIBezierPath类没有办法设置路径颜色,所以这里只能自定义Path类了

#import <UIKit/UIKit.h>@interface DrawPath : UIBezierPath@property (nonatomic, strong) UIColor *pathColor;@end

手指移动时,绘制线条,路径是自定义的Path类

@interface DrawView ()@property(nonatomic, strong)DrawPath *path;
/** 保存所有路径的数组 */
@property(nonatomic, strong) NSMutableArray *pathArr;@end//懒加载
- (NSMutableArray *)pathArr {if (_pathArr == nil) {_pathArr = [NSMutableArray array];}return _pathArr;
}
- (void)pan:(UIPanGestureRecognizer *)pan {//获取开始的触摸点CGPoint startP = [pan locationInView:self];if (pan.state == UIGestureRecognizerStateBegan) {//创建贝塞尔路径_path = [[DrawPath alloc]init];_path.lineWidth = _lineWidth;_path.pathColor = _pathColor;//不能在手指抬起时将路径添加到数组,因为在遍历数组画线时路径还没有被添加到数组里面[_pathArr addObject:_path];//设置起点[_path moveToPoint:startP];}//连线[_path addLineToPoint:startP];//重绘,调用drawRect方法[self setNeedsDisplay];}

画线实现drawRect方法,绘制线条或者图片时,是把数组中的路径全部画出来

- (void)drawRect:(CGRect)rect {//把所有路径画出来for (DrawPath *path in self.pathArr) {if ([path isKindOfClass:[UIImage class]]) {//画图UIImage *image = (UIImage *)path;[image drawInRect:rect];}else {//画线[path.pathColor set];[path stroke];}}}

当把图片添加到画板时

- (void)setImage:(UIImage *)image {_image = image;[self.pathArr addObject:image];//重绘调用drawRect才能在画板上显示图片[self setNeedsDisplay];
}

还可以把直接更新路径数组的操作封装在画板中

- (void)clear {//清除[self.pathArr removeAllObjects];[self setNeedsDisplay];}- (void)undo {//撤销[self.pathArr removeLastObject];[self setNeedsDisplay];
}

控制器中:

@interface ViewController () <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@property (weak, nonatomic) IBOutlet DrawView *drawView;
@end

实现几个按钮对画板的操作:

- (IBAction)clear:(id)sender {//清屏[_drawView clear];}- (IBAction)undo:(id)sender {//撤销[_drawView undo];}- (IBAction)eraser:(id)sender {//擦除 就是把路径的颜色设置为画板的背景色,假象_drawView.pathColor = _drawView.backgroundColor;_drawView.lineWidth = 20;}- (IBAction)changeLineWidth:(UISlider *)sender {//改变路径线宽_drawView.lineWidth = sender.value;}- (IBAction)changeColor:(UIButton *)sender {//改变路径颜色_drawView.pathColor = sender.backgroundColor;}- (IBAction)pickPhoto:(id)sender {//选择照片//弹出系统相册UIImagePickerController *picker = [[UIImagePickerController alloc]init];//设置选择控制器的来源 UIImagePickerControllerSourceTypeSavedPhotosAlbum:照片库picker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;//设置代理picker.delegate = self;//modal出控制器[self presentViewController:picker animated:YES completion:nil];}- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {//获取选择的图片UIImage *image = info[UIImagePickerControllerOriginalImage];//创建一个处理图片的viewImageHandleView *handleView = [[ImageHandleView alloc]initWithFrame:self.drawView.bounds];handleView.handleCompletionBlock = ^(UIImage *image){_drawView.image = image;};[self.drawView addSubview:handleView];//将图片画在画板上handleView.image = image;//_drawView.image = image;//dismiss[self dismissViewControllerAnimated:YES completion:nil];//NSLog(@"%@", info);}- (IBAction)save:(id)sender {[UIView animateWithDuration:0.15 animations:^{//保存当前画板上的内容//开启上下文UIGraphicsBeginImageContextWithOptions(_drawView.bounds.size, NO, 0);//获取位图上下文CGContextRef ctx = UIGraphicsGetCurrentContext();//把控件上的图层渲染到上下文[_drawView.layer renderInContext:ctx];//获取上下文中的图片UIImage *image = UIGraphicsGetImageFromCurrentImageContext();//关闭上下文UIGraphicsEndImageContext();//保存图片到相册UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);self.drawView.alpha = 0;} completion:^(BOOL finished) {[UIView animateWithDuration:0.15 animations:^{self.drawView.alpha = 1;}];}];}//保存成功后的方法必须是这个,不能随便写
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {NSLog(@"保存成功");}

从相册选择完图片后把图片显示在画板上了但是还没有渲染到layer,这时候需要对图片进行移动缩放旋转这些操作的话,但是UIImage是不能拉伸旋转这些操作的,UIImageView才可以,所以解决思路就是自定义一个view来专门处理对图片的操作,在自定义view上放一个UIImageView,从相册选择图片后获取的image设置给UIImageView,这样的自定义view上操作UIIamgeView。

#import <UIKit/UIKit.h>@interface ImageHandleView : UIView
/** 图片 */
@property(nonatomic, strong) UIImage *image;/** block */
@property(nonatomic, strong) void(^handleCompletionBlock)(UIImage *image);
@end
#import "ImageHandleView.h"@interface ImageHandleView () <UIGestureRecognizerDelegate>/** image */
@property(nonatomic, weak) UIImageView *imageView;@end@implementation ImageHandleView//防止图片上的触摸事件传递到画板
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {return _imageView;
}- (UIImageView *)imageView {if (_imageView == nil) {UIImageView *imageV = [[UIImageView alloc]initWithFrame:self.bounds];_imageView = imageV;//设置imgaeview允许与用户交互_imageView.userInteractionEnabled = YES;//添加手势[self setUpGestureRecognizer];//把这个imageview添加到图片处理的view上[self addSubview:imageV];}return _imageView;
}#pragma mark - 添加手势
- (void)setUpGestureRecognizer {//平移手势UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];[_imageView addGestureRecognizer:pan];//旋转手势UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotation:)];rotation.delegate = self;[_imageView addGestureRecognizer:rotation];//缩放手势UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinch:)];pinch.delegate = self;[_imageView addGestureRecognizer:pinch];//长按手势UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPress:)];[_imageView addGestureRecognizer:longPress];}#pragma mark - 处理平移手势
- (void)pan:(UIPanGestureRecognizer *)pan {//获取手指的偏移量CGPoint tranp = [pan translationInView:self.imageView];//设置imageview的形变self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, tranp.x, tranp.y);//复位[pan setTranslation:CGPointZero inView:self.imageView];}#pragma mark - 处理旋转手势
- (void)rotation:(UIRotationGestureRecognizer *)rotation {//设置imageview的形变self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, rotation.rotation);//复位rotation.rotation = 0;}#pragma mark - 处理缩放手势
- (void)pinch:(UIPinchGestureRecognizer *)pinch {//设置imageview的形变self.imageView.transform = CGAffineTransformScale(self.imageView.transform, pinch.scale, pinch.scale);//复位pinch.scale = 1;}#pragma mark - 处理长按手势
- (void)longPress:(UILongPressGestureRecognizer *)longPress {//图片处理完成if (longPress.state == UIGestureRecognizerStateBegan) {//高亮效果[UIView animateWithDuration:0.25 animations:^{self.imageView.alpha = 0;} completion:^(BOOL finished) {[UIView animateWithDuration:0.25 animations:^{self.imageView.alpha = 1;} completion:^(BOOL finished) {//高亮时生成一张新的图片//开启位图上下文UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);//获取位图上下文CGContextRef ctx = UIGraphicsGetCurrentContext();//把控件的图层渲染到上下文[self.layer renderInContext:ctx];//从上下文中获取新的图片UIImage *image = UIGraphicsGetImageFromCurrentImageContext();//关闭上下文UIGraphicsEndImageContext();//调用blockif(_handleCompletionBlock) {_handleCompletionBlock(image);}//移除父控件[self removeFromSuperview];}];}];}}#pragma mark - 手势代理方法 <UIGestureRecognizerDelegate>
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {//yes表示同时支持多个手势return YES;}- (void)setImage:(UIImage *)image {_image = image;//把图片展示到UIImageView上self.imageView.image = image;}@end

需要注意的是,当长按将操作过的图片绘制都画板上生成一张新的图片后,这时候需要把这个image设置给画板drawView,但是这时候就必须要在专门处理图片的view中去import画板view,这样耦合性太强。所以为了解耦,可以使用代理或者Block。我用了Block将刚刚生成的image先保存起来,在控制器中初始化imageHandleView之后再赋值给drawView。

最后保存画板上的内容就是将画板上的内容生成图片保存到相册即可。注意,保存完之后执行的方法必须是这个:

- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo;

最后效果图是这样的:

画线:  带图片的:  保存生成的图片:

转载于:https://my.oschina.net/shenhuniurou/blog/653929

iOS开发之简单画板实现相关推荐

  1. IOS开发基础之画板案例软件的开发

    IOS开发基础之画板案例软件的开发 值此元宵佳节,我依然在学习IOS,几天没有更新博客了.今天更新了一下. 源码在我的主页里面. info.plist里面加入这样的代码,防止截图的时候有问题 < ...

  2. iOS开发:简单的Toast提示框实现

    今天小年,再分享一篇2018年度最后一篇博客,博主是以iOS开发出身,那就最后一篇博文就分享一下关于iOS的内容吧.iOS开发过程中,有些时候操作App的时候,需要给用户对应的响应提示操作,使用系统自 ...

  3. iOS开发-------自定义简单的表情键盘(UICollectionView 集合视图)

    最近制作自制表情键盘的时候,突然了解到还有一个叫做UICollectionView (集合视图)的类,就研究了一下,确实在做表情键盘上要比用 UIScrollView(滚动视图) 要简单的多,用法与 ...

  4. IOS开发工具介绍之Xcode开发工具使用

    IT在线教育专家--麦子学院特约授课老师柯博文<iOS开发入门实战>向我们详细的讲述了ios开发实战过程.Xcode开发工具是ios开发的流程使用范围最广的工具. 在所有课程开始之前,柯博 ...

  5. iOS开发UI篇—多控制器和导航控制器简单介绍

    iOS开发UI篇-多控制器和导航控制器简单介绍 一.多控制器 一个iOS的app很少只由一个控制器组成,除非这个app极其简单.当app中有多个控制器的时候,我们就需要对这些控制器进行管理 有多个vi ...

  6. iOS开发UI篇—UIWindow简单介绍

    iOS开发UI篇-UIWindow简单介绍 一.简单介绍 UIWindow是一种特殊的UIView,通常在一个app中只会有一个UIWindow iOS程序启动完毕后,创建的第一个视图控件就是UIWi ...

  7. iOS开发UI篇—简单介绍静态单元格的使用

    iOS开发UI篇-简单介绍静态单元格的使用 一.实现效果与说明 说明:观察上面的展示效果,可以发现整个界面是由一个tableview来展示的,上面的数据都是固定的,且几乎不会改变. 要完成上面的效果, ...

  8. iOS开发UI篇—UITabBarController简单介绍

    iOS开发UI篇-UITabBarController简单介绍 一.简单介绍 UITabBarController和UINavigationController类似,UITabBarControlle ...

  9. iOS开发拓展篇—CoreLocation简单介绍

    iOS开发拓展篇-CoreLocation简单介绍 一.简介 1.在移动互联网时代,移动app能解决用户的很多生活琐事,比如 (1)导航:去任意陌生的地方 (2)周边:找餐馆.找酒店.找银行.找电影院 ...

最新文章

  1. TODO:macOS编译PHP7.1
  2. yolov3深度解析
  3. 13.PHP_ThinkPHP
  4. 【Windows 逆向】使用 CE 工具挖掘关键数据内存真实地址 ( CE 找出子弹数据内存地址是临时地址 | 挖掘真实的子弹数据内存地址 )
  5. Linux日常命令使用记录
  6. MSSQL 2008 企业管理器打开命令
  7. C++基础学习(02)--(数据类型,变量类型,变量作用域,常量,修饰符类型)
  8. 取周一时间_周一到周五不好化妆?别着急,5款夏日通勤裸妆的教程来了!
  9. Vim升华之树形目录插件NERDTree安装图解
  10. Linux操作系统下三种配置环境变量的方法
  11. Babylon-AST初探-代码生成(Create)
  12. linux vmware 服务,学习笔记:在Linux虚拟机上搭建node服务
  13. 7-12 方阵循环右移 (20 分)
  14. JDBC 常用的类和接口--一学就会(欢迎转载)
  15. qnx 设备驱动开发_一种QNX系统的USB驱动加载方法与流程
  16. 易支付源码第四方支付接口
  17. 安全策略手记 (安全沙箱全攻略)
  18. 详细Ubuntu系统下搭建Hadoop完全分布式
  19. 数据分析之《我不是药神》
  20. IOS 安卓 按键精灵 触动精灵脚本逆向解密

热门文章

  1. C++_标准模板库STL概念介绍5-其他库与总结
  2. C#:委托和自定义事件
  3. 【BZOJ】4259: 残缺的字符串 FFT
  4. 电商前端设计-详情页
  5. 如何修改dedecms专题目录默认名称special
  6. [禅悟人生]拿得起放得下, 才是真幸福
  7. VMware LUN发现、日志、操作
  8. 最近在研究Spring Data JPA,大家都来说说感受~~ - Spring,JPA - Java - ITeye论坛
  9. 去邵程程博客,得到很有喜感图片一张
  10. 配置交换机etherchannel