这次介绍一个比较复杂一些的小demo,一个简洁的画板,主要知识点是利用贝塞尔曲线–UIBezierPath
这个类去实现手动画线的操作,这个操作在很多app中电子签名这个功能中都曾用到。 先看看示例图,这个小画板有哪些工能
1、基本的画线,可以写字,你可以滑动滑块控制画线的粗细,点击最下方颜色按钮改变画线的颜色
2、点击“撤销”,可以清除你上一步的操作,不断点击,可以不断撤销。如果点击“清屏”,就会清除所有
3、点击“橡皮擦”,可以抹掉屏幕上已经画出的线,滑动滑块可以改变橡皮擦的宽度
4、点击“照片”,可以进入系统相册选择图片
5、可以对图片进行拖动、旋转、放大缩小,然后长按图片,图片会定住,这时候可以在画板上画线
6、点击“保存”,会将你绘制的内容以图片的形式保存到系统相册中
一 、UI控件的布局,如下图,我在storyboard中直接拖入的,绿色区域包含一个toolBar、5个item和1个弹簧,红色区域是1个view,蓝色区域是一个view里包含3个button和1个slider。我直接将这个item和button的action方法拖入到ViewController
中
二、 我的项目中新增了两个view,“imageHandleView”和“drawView”; 另外自定义了一个UIBezierPath
类,“DrawPath”
三、 首先要写的是DrawView
,在这个类里把相应的属性和方法写好,等ViewController
调用
- DrawView.h
#import <UIKit/UIKit.h>
@interface DrawView : UIView
/**线宽*/
@property(nonatomic,assign)NSInteger lineWidth;
/**颜色*/
@property(nonatomic,strong)UIColor * pathColor;
/**照片*/
@property(nonatomic,strong)UIImage * imageDraw;
//清屏
-(void)clear;
//撤销
-(void)undo;
@end
- DrawView.m
#import "DrawView.h"
#import "DrawPath.h"
@interface DrawView()
/**
自定义的路径
*/
@property(nonatomic,strong)DrawPath * path;
/**路径数组*/
@property(nonatomic,strong)NSMutableArray * pathArr;
@end
@implementation DrawView
-(NSMutableArray *)pathArr
{
if (_pathArr == nil) {
_pathArr = [NSMutableArray array];
}
return _pathArr;
}
//仅仅加载xib的时候调用
-(void)awakeFromNib
{
[super awakeFromNib];
[self setUp];
}
-(instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self setUp];
}
return self;
}
/**
初始化
*/
-(void)setUp
{
UIPanGestureRecognizer * pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
[self addGestureRecognizer:pan];
_lineWidth = 1;
_pathColor = [UIColor blackColor];
}
//当手指拖动的时候调用
-(void)pan:(UIPanGestureRecognizer*)pan
{
// NSLog(@"%s",__func__);
//获取手指当前触摸点
CGPoint curP = [pan locationInView:self];
if (pan.state == UIGestureRecognizerStateBegan) {
//创建贝瑟尔路径
_path = [[DrawPath alloc]init];
//设置线宽
_path.lineWidth = _lineWidth;
//给路径设置颜色
_path.pathSystemColor = _pathColor;
//设置路径的起点
[_path moveToPoint:curP];
//保存描述好的路径
[self.pathArr addObject:_path];
}
//添加线到当前点
[_path addLineToPoint:curP];
//重绘
[self setNeedsDisplay];
}
//绘制图形
//只要调用drawRact方法就会把之前的内容全部清空
- (void)drawRect:(CGRect)rect {
// NSLog(@"%s",__func__);
for (DrawPath * path in self.pathArr) {
if ([path isKindOfClass:[UIImage class]]) {
//绘制图片
UIImage * image = (UIImage *)path;
[image drawInRect:rect];
}
else{
//画线
[path.pathSystemColor set];
[path stroke];
}
}
}
#pragma mark - 功能
//清屏
-(void)clear;
{
[self.pathArr removeAllObjects];
[self setNeedsDisplay];
}
//撤销
-(void)undo
{
[self.pathArr removeLastObject];
[self setNeedsDisplay];
}
//图片
-(void)setImageDraw:(UIImage *)imageDraw
{ //重新赋值
_imageDraw = imageDraw;
//添加到路径中
[self.pathArr addObject:_imageDraw];
//重绘
[self setNeedsDisplay];
}
@end
四、自定义UIBezierPath,为了给它添加颜色的属性
- DrawPath.h
#import <UIKit/UIKit.h>
@interface DrawPath : UIBezierPath
//因为 UIBezierPath 这个类没有颜色的属性,需要自己添加此属性
@property(nonatomic,strong)UIColor * pathSystemColor;
@end
五、然后写ImageHandleView
,这个view是对image进行操作用的,里面主要写入拖动、旋转、缩放、长按这个几个手势,在用户操作完成后它就会被移除
- ImageHandleView.h
#import <UIKit/UIKit.h>
@interface ImageHandleView : UIView
/**ViewController传过来的图片传给这个图片*/
@property(nonatomic,strong)UIImage * imageH;
/**图片处理完成后的block*/
@property(nonatomic,strong)void(^handleCompletionBlock)(UIImage * image);
@end
- ImageHandleView.m
#import "ImageHandleView.h"
@interface ImageHandleView()<UIGestureRecognizerDelegate>
@property(nonatomic,weak)UIImageView * imageV;
@end
@implementation ImageHandleView
//重写set方法,展示UIImageView的图片
-(void)setImageH:(UIImage *)imageH
{
_imageH = imageH;
self.imageV.image = imageH;
}
-(UIImageView *)imageV
{
if (_imageV == nil) {
UIImageView * imageV = [[UIImageView alloc]initWithFrame:self.bounds];
imageV.userInteractionEnabled = YES;
_imageV = imageV;
//添加手势
[self setUpGestureRecognizer];
[self addSubview:imageV];
}
return _imageV;
}
#warning 开启这个方法就可以阻止DrawView的pan方法,这样在拖动图片时就不会画出线
-(void)panHandle
{
NSLog(@"%s",__func__);
}
#pragma mark - 添加手势
-(void)setUpGestureRecognizer
{
//添加拖动手势给ImageHandleView
UIPanGestureRecognizer * panHandle = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panHandle)];
[self addGestureRecognizer:panHandle];
//平移
UIPanGestureRecognizer * pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
[_imageV addGestureRecognizer:pan];
//旋转
UIRotationGestureRecognizer * rotation = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotation:)];
[_imageV addGestureRecognizer:rotation];
//缩放
UIPinchGestureRecognizer * pinch = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinch:)];
[_imageV addGestureRecognizer:pinch];
//长按
UILongPressGestureRecognizer * longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPress:)];
[_imageV addGestureRecognizer:longPress];
}
-(void)pan:(UIPanGestureRecognizer*)pan
{
//获取手指的偏移量
CGPoint transP = [pan translationInView:self.imageV];
//设置UIImageView的形变
self.imageV.transform = CGAffineTransformTranslate(self.imageV.transform, transP.x, transP.y);
//复位:只要想要相对于上一次就必须复位
[pan setTranslation:CGPointZero inView:self.imageV];
}
-(void)rotation:(UIRotationGestureRecognizer*)rotation
{
self.imageV.transform = CGAffineTransformRotate(self.imageV.transform, rotation.rotation);
//必须遵循代理才能支持多个手势
rotation.delegate = self;
rotation.rotation = 0;
}
-(void)pinch:(UIPinchGestureRecognizer*)pinch
{
self.imageV.transform = CGAffineTransformScale(self.imageV.transform, pinch.scale, pinch.scale);
pinch.delegate = self;
pinch.scale = 1;
}
-(void)longPress:(UILongPressGestureRecognizer*)longPress
{
if (longPress.state == UIGestureRecognizerStateBegan) {
//高亮效果
[UIView animateWithDuration:0.25 animations:^{
self.imageV.alpha = 0;
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.25 animations:^{
self.imageV.alpha = 1;
} completion:^(BOOL finished) {
//高亮完成之后
//把处理的图片生成一张新的图片
//开启位图上下文
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);
//获取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
//把控件的图层渲染到上下文
[self.layer renderInContext:ctx];
//获取图片
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
//关闭上下文
UIGraphicsEndImageContext();
//调用block
if (_handleCompletionBlock) {
_handleCompletionBlock(image);
}
//把自己从父控件移除
[self removeFromSuperview];
}];
}];
}
}
#pragma mark - UIGestureRecognizerDelegate
// 是否同时支持多个手势
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
@end
六、 最后用ViewController
控制以上两个view
- ViewCnontroller.m
#import "ViewController.h"
#import "DrawView.h"
#import "ImageHandleView.h"
@interface ViewController ()<UINavigationControllerDelegate,UIImagePickerControllerDelegate>
@property (weak, nonatomic) IBOutlet DrawView *drawView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
#pragma mark - 清屏
- (IBAction)clear:(id)sender {
[_drawView clear];
}
#pragma mark - 撤销
- (IBAction)undo:(id)sender {
[_drawView undo];
}
#pragma mark - 橡皮擦
- (IBAction)eraser:(id)sender {
_drawView.pathColor = _drawView.backgroundColor;
}
#pragma mark - 选择照片
- (IBAction)pickPhoto:(id)sender {
//弹出系统相册
//选择控制器
UIImagePickerController * pickerVc = [[UIImagePickerController alloc]init];
//设置选择控制器来源
pickerVc.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
pickerVc.delegate = self;
[self presentViewController:pickerVc animated:YES completion:nil];
}
#pragma mark - UIImagePickerControllerDelegate
//当用户选择一张图片的时候调用
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
//获取原始大小的图片
UIImage * image = info[UIImagePickerControllerOriginalImage];
//初始化imageHandleV
ImageHandleView * imageHandleV = [[ImageHandleView alloc]initWithFrame:self.drawView.bounds];
//当用户长按图片后,会执行这个block,把图片赋值给drawView
imageHandleV.handleCompletionBlock = ^(UIImage *image) {
_drawView.imageDraw = image;
};
[self.drawView addSubview:imageHandleV];
//把图片传过去
imageHandleV.imageH = image;
//把选中的照片画到画板上(这句是没有加图片操作之前的代码,可以删掉)
// _drawView.imageDraw = image;
//退出相册
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - 保存
- (IBAction)save:(id)sender {
//开启上下文
UIGraphicsBeginImageContextWithOptions(_drawView.bounds.size, NO, 0);
//获取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
//渲染图层
[_drawView.layer renderInContext:ctx];
//获取上下文中的图片
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
//关闭上下文
UIGraphicsEndImageContext();
// 保存画板的内容放入相册
// image:写入的图片
// completionTarget图片保存监听者
// 注意:以后写入相册方法中,想要监听图片有没有保存完成,保存完成的方法不能随意乱写
UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
}
//监听保存完成,必须实现这个方法
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
NSLog(@"保存图片成功");
UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"保存成功!" message:nil preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction * action = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil];
[alert addAction:action];
[self presentViewController:alert animated:YES completion:nil];
}
#pragma mark - 线宽控制
- (IBAction)valueChange:(UISlider *)sender {
_drawView.lineWidth = sender.value;
}
#pragma mark - 颜色控制
- (IBAction)colorChange:(UIButton *)sender {
_drawView.pathColor = sender.backgroundColor;
}
@end