本文源代码请参考: https://github.com/coderZYGui/drawingBoard
利用Quartz2D 实现画板功能,效果如下图
功能实现步骤:
1.界面搭建(使用autolayout自动布局),顶部使用Toorbar来管理多个item
中间画板是一个View,底部一个View上放一个UISlider和三个Button,并做布局操作.
2. 实现画线: 每条线都是新的path,所以把每个path都保存在数组中,画线的时候从数组中取出.
3. 设置属性功能: 设置清屏,撤销,UISlider等相关业务逻辑.因为清屏,撤销,橡皮擦,线宽,线色属于画板的功能
因此写在画板类中.
4. 绘制图片到画板: 从系统相册中选择图片后,对图片做拖动,旋转,平移等形变操作. 长按图片时,将图片绘制到画板中.
注意: 这里采用的方式是: 对一个UIView进行截屏操作,在UIView中放置一个UIImageView,将相册的image保存到UIImageView的image中.然后利用UIView的代理属性将图片传给画板的image.
代码如下:
ViewController文件
//
// ViewController.m
// DrawingBoard
//
// Created by 朝阳 on 2017/10/14.
// Copyright © 2017年 sunny. All rights reserved.
//
#import "ViewController.h"
#import "ZYDrawView.h"
#import "ZYHandleImageView.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet ZYDrawView *drawView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
// 操作属于谁,写在谁的类中
/**
清屏
*/
- (IBAction)clear:(UIBarButtonItem *)sender {
[self.drawView clear];
}
/**
撤销
*/
- (IBAction)undo:(UIBarButtonItem *)sender {
[self.drawView undo];
}
/**
橡皮擦
*/
- (IBAction)eraser:(UIBarButtonItem *)sender {
[self.drawView eraser];
}
/**
设置线宽
*/
- (IBAction)setLineWidth:(UISlider *)slider {
[self.drawView setLineWidth:slider.value];
}
/**
设置线颜色
*/
- (IBAction)setLineColor:(UIButton *)button {
[self.drawView setLineColor:button.backgroundColor];
}
/**
照片
*/
- (IBAction)photo:(UIBarButtonItem *)sender {
//从系统相册中选择一张图片
//1. 弹出系统相册
UIImagePickerController *pickerVC = [[UIImagePickerController alloc] init];
//2. 弹出相册的类型
pickerVC.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
//3. 设置pickerVC代理
pickerVC.delegate = self;
//3. modal出系统相册
[self presentViewController:pickerVC animated:YES completion:nil];
}
#pragma -mark UIImagePickerControllerDelegate
/**
当选中系统相册中的图片时会调用
@param picker 系统相册控制器
@param info 存放图片的字典
*/
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
NSLog(@"%@",info);
// 系统相册是一个字典,根据key值来取出图片
UIImage *image = info[UIImagePickerControllerOriginalImage];
ZYHandleImageView *handleV = [[ZYHandleImageView alloc] initWithFrame:self.drawView.frame];
handleV.backgroundColor = [UIColor clearColor];
handleV.image = image;
// 设置代理属性
handleV.delegate = self;
[self.view addSubview:handleV];
// 系统相册modal消失
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma -mark ZYHandleImageViewDelegate
- (void)handleImageView:(ZYHandleImageView *)handleImageView newImage:(UIImage *)newImage
{
self.drawView.image = newImage;
}
/**
保存
*/
- (IBAction)save:(UIBarButtonItem *)sender {
//1. 开启一个位图上下文
UIGraphicsBeginImageContextWithOptions(self.drawView.bounds.size, NO, 0);
//2. 把drawView上的内容渲染到上下文中
CGContextRef ctx = UIGraphicsGetCurrentContext();
[self.drawView.layer renderInContext:ctx];
//3. 从上下文中生成新的图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
//把图片保存到桌面
// NSData *data = UIImagePNGRepresentation(newImage);
// [data writeToFile:@"/Users/sunny/Desktop/photo.png" atomically:YES];
//4. 把图片保存到系统相册中
// 注意: 弹出系统相册必须实现 image:didFinishSavingWithError:contextInfo:方法
UIImageWriteToSavedPhotosAlbum(newImage, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
}
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
NSLog(@"saveSuccess");
}
/**
隐藏导航
*/
- (BOOL)prefersStatusBarHidden
{
return YES;
}
@end
ZYDrawView文件(画板)
//
// ZYDrawView.h
// DrawingBoard
//
// Created by 朝阳 on 2017/10/14.
// Copyright © 2017年 sunny. All rights reserved.
//
#import
@interface ZYDrawView : UIView
@property (nonatomic, strong) UIImage *image;
// 清屏
- (void)clear;
// 撤销
- (void)undo;
// 擦除
- (void)eraser;
// 设置线宽
- (void)setLineWidth:(CGFloat)width;
// 设置线的颜色
- (void)setLineColor:(UIColor *)color;
@end
#import "ZYDrawView.h"
#import "ZYBezierPath.h"
/** 定义类扩展 */
@interface ZYDrawView ()
@property (nonatomic, strong) UIBezierPath *path;
/** 用来保存所有的path */
@property (nonatomic, strong) NSMutableArray *allPaths;
/** 线宽 */
@property (nonatomic, assign) CGFloat width;
/** 线色 */
@property (nonatomic, strong) UIColor *color;
@end
@implementation ZYDrawView
- (NSMutableArray *)allPaths
{
if (!_allPaths) {
self.allPaths = [NSMutableArray array];
}
return _allPaths;
}
- (void)awakeFromNib
{
[super awakeFromNib];
// 添加手势
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
[self addGestureRecognizer:pan];
// 设置一个默认线宽和线色
self.width = 1;
self.color = [UIColor blackColor];
}
// 清屏
- (void)clear
{
[self.allPaths removeAllObjects];
// 重绘
[self setNeedsDisplay];
}
// 撤销
- (void)undo
{
[self.allPaths removeLastObject];
[self setNeedsDisplay];
}
// 擦除
- (void)eraser
{
[self setLineColor:[UIColor whiteColor]];
}
// 设置线宽
- (void)setLineWidth:(CGFloat)width
{
self.width = width;
}
// 设置线的颜色
- (void)setLineColor:(UIColor *)color
{
self.color = color;
}
/**
拖动手势方法
*/
- (void)pan:(UIPanGestureRecognizer *)pan
{
// 获取当前手指所在的点
CGPoint curP = [pan locationInView:self];
// 判断手势状态
if (pan.state == UIGestureRecognizerStateBegan) {
// 创建路径
ZYBezierPath *path = [[ZYBezierPath alloc] init];
self.path = path;
// 设置线宽
[path setLineWidth:self.width];
path.color = self.color;
// 设置起点
[path moveToPoint:curP];
// 把路径保存到数组中
[self.allPaths addObject:self.path];
}else if(pan.state == UIGestureRecognizerStateChanged){
[self.path addLineToPoint:curP];
// 重绘,调用drawRect方法
[self setNeedsDisplay];
}
}
- (void)setImage:(UIImage *)image
{
_image = image;
// 把图片添加到数组中
[self.allPaths addObject:image];
// 重绘
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
for (ZYBezierPath *path in self.allPaths) {
// 判断path的真实类型
if ([path isKindOfClass:[UIImage class]]) {
UIImage *image = (UIImage *)path;
// 把图片绘制到画板中(填充整个区域)
[image drawInRect:rect];
}else{
[path.color set];
[path stroke];
}
}
}
@end
ZYBezierPath文件(继承自UIBezierPath,系统UIBezierPath功能不够用)
//
// ZYBezierPath.h
// DrawingBoard
//
// Created by 朝阳 on 2017/10/14.
// Copyright © 2017年 sunny. All rights reserved.
//
#import
@interface ZYBezierPath : UIBezierPath
@property (nonatomic,strong) UIColor *color;
@end
#import "ZYBezierPath.h"
@implementation ZYBezierPath
@end
ZYHandleImageView文件(分析中的UIView文件,继承UIView)
//
// ZYHandleImageView.h
// DrawingBoard
//
// Created by 朝阳 on 2017/10/14.
// Copyright © 2017年 sunny. All rights reserved.
//
#import
@class ZYHandleImageView;
@protocol ZYHandleImageViewDelegate
- (void)handleImageView:(ZYHandleImageView *)handleImageView newImage:(UIImage *)newImage;
@end
@interface ZYHandleImageView : UIView
@property (nonatomic, strong) UIImage *image;
/** 代理属性 */
@property (nonatomic, weak) id delegate;
@end
#import "ZYHandleImageView.h"
/** 定义类扩展 */
@interface ZYHandleImageView ()
@property (nonatomic, strong) UIImageView *imageV;
@end
@implementation ZYHandleImageView
- (UIImageView *)imageV
{
if (!_imageV) {
UIImageView *imageV = [[UIImageView alloc] initWithFrame:self.bounds];
imageV.userInteractionEnabled = YES;
[self addSubview:imageV];
_imageV = imageV;
// 添加手势
[self addGestures];
}
return _imageV;
}
- (void)setImage:(UIImage *)image
{
_image = image;
self.imageV.image = image;
}
- (void)addGestures
{
// 拖拽手势
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:@selector(pan:)];
[self.imageV addGestureRecognizer:pan];
// 捏合
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinch:)];
pinch.delegate = self;
[self.imageV addGestureRecognizer:pinch];
// 添加旋转
UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotation:)];
rotation.delegate = self;
[self.imageV addGestureRecognizer:rotation];
// 长按
UILongPressGestureRecognizer *longP = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
[self.imageV addGestureRecognizer:longP];
}
// 拖动的时候调用
- (void)pan:(UIPanGestureRecognizer *)pan
{
// 手指移动后,相对于坐标中的偏移量
// 此时的pan.view 就相当于 UIImageView
CGPoint transP = [pan translationInView:pan.view];
pan.view.transform = CGAffineTransformTranslate(pan.view.transform, transP.x, transP.y);
// 复位
[pan setTranslation:CGPointZero inView:pan.view];
}
// 捏合的时候调用
- (void)pinch:(UIPinchGestureRecognizer *)pinch
{
pinch.view.transform = CGAffineTransformScale(pinch.view.transform, pinch.scale, pinch.scale);
//复位
[pinch setScale:1];
}
// 旋转的时候调用
- (void)rotation:(UIRotationGestureRecognizer *)rotation
{
rotation.view.transform = CGAffineTransformRotate(rotation.view.transform, rotation.rotation);
//复位
[rotation setRotation:0];
}
// 长按的时候调用
-(void)longPress:(UILongPressGestureRecognizer *)longPress
{
// 长按时,图片闪烁一下
if (longPress.state == UIGestureRecognizerStateBegan) {
[UIView animateWithDuration:0.3 animations:^{
self.imageV.alpha = 0;
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
self.imageV.alpha = 1;
}completion:^(BOOL finished) {
// 把相册中的图片绘制到DrawView上
//1. 开启一个位图上下文
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);
//2. 把imageV上的内容绘制到上下文中
CGContextRef ctx = UIGraphicsGetCurrentContext();
[self.layer renderInContext:ctx];
//3. 从上下文中生成新的图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
//4. 关闭上下文
UIGraphicsEndImageContext();
// 若要把newImage绘制到DrawView上,需要使用代理进行传值
if ([self.delegate respondsToSelector:@selector(handleImageView:newImage:)]) {
[self.delegate handleImageView:self newImage:newImage];
}
//从父控件当中移除
[self removeFromSuperview];
}];
}];
}
}
//能够同时支持多个手势
-(BOOL)gestureRecognizer:(nonnull UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(nonnull UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
@end