iOS開發之簡單畫板實現

這幾天在學習Quartz2D,學習了一個簡單畫板的實現,如今把實現過程記錄一下。數組

主要用到的點就是畫線,截屏,繪製圖片,選擇圖片,以及保存全部繪製的線。ide

首先在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初始化方法,初始化時要作的事情就是給畫板添加拖動手勢,也能夠將畫筆路徑的線寬在這裏設置atom

//自定義初始化方法
- (void)setUp {
    
    //添加手勢
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
    [self addGestureRecognizer:pan];
    
    //初始化時設置路徑線寬
    _lineWidth = 2;
    
}

手指在畫板上移動時開始繪製線條,這裏由於原生的UIBezierPath類沒有辦法設置路徑顏色,因此這裏只能自定義Path類了    spa

#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方法,繪製線條或者圖片時,是把數組中的路徑所有畫出來code

- (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];
        }
    }
    
}

當把圖片添加到畫板時orm

- (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];
    
    //建立一個處理圖片的view
    ImageHandleView *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();
                
                //調用block
                if(_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;


最後效果圖是這樣的:

畫線:  帶圖片的:  保存生成的圖片:

相關文章
相關標籤/搜索