iOS簡單畫板開發(fā)案例分享
最近在學(xué)習(xí)Quartz2D,學(xué)習(xí)了一個簡單畫板的實(shí)現(xiàn),現(xiàn)在把實(shí)現(xiàn)過程記錄一下。
主要用到的點(diǎn)就是畫線,截屏,繪制圖片,選擇圖片,以及保存所有繪制的線。
首先在storyboard上布局好控件,設(shè)置約束等等,最后的效果是這樣:
自定義畫板DrawView,使用時(shí)可能是從xib中加載,也可能是手動創(chuàng)建,所以創(chuàng)建對象的方法需要實(shí)現(xiàn)兩個:
#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;
- (void)awakeFromNib { [self setUp]; } - (instancetype)initWithFrame:(CGRect)frame { if (self == [super initWithFrame:frame]) { [self setUp]; } return self; }
setUp初始化方法,初始化時(shí)要做的事情就是給畫板添加拖動手勢,也可以將畫筆路徑的線寬在這里設(shè)置
//自定義初始化方法 - (void)setUp { //添加手勢 UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)]; [self addGestureRecognizer:pan]; //初始化時(shí)設(shè)置路徑線寬 _lineWidth = 2; }
手指在畫板上移動時(shí)開始繪制線條,這里因?yàn)樵腢IBezierPath類沒有辦法設(shè)置路徑顏色,所以這里只能自定義Path類了
#import <UIKit/UIKit.h> @interface DrawPath : UIBezierPath @property (nonatomic, strong) UIColor *pathColor; @end
手指移動時(shí),繪制線條,路徑是自定義的Path類
@interface DrawView () @property(nonatomic, strong)DrawPath *path; /** 保存所有路徑的數(shù)組 */ @property(nonatomic, strong) NSMutableArray *pathArr; @end //懶加載 - (NSMutableArray *)pathArr { if (_pathArr == nil) { _pathArr = [NSMutableArray array]; } return _pathArr; }
- (void)pan:(UIPanGestureRecognizer *)pan { //獲取開始的觸摸點(diǎn) CGPoint startP = [pan locationInView:self]; if (pan.state == UIGestureRecognizerStateBegan) { //創(chuàng)建貝塞爾路徑 _path = [[DrawPath alloc]init]; _path.lineWidth = _lineWidth; _path.pathColor = _pathColor; //不能在手指抬起時(shí)將路徑添加到數(shù)組,因?yàn)樵诒闅v數(shù)組畫線時(shí)路徑還沒有被添加到數(shù)組里面 [_pathArr addObject:_path]; //設(shè)置起點(diǎn) [_path moveToPoint:startP]; } //連線 [_path addLineToPoint:startP]; //重繪,調(diào)用drawRect方法 [self setNeedsDisplay]; }
畫線實(shí)現(xiàn)drawRect方法,繪制線條或者圖片時(shí),是把數(shù)組中的路徑全部畫出來
- (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]; } } }
當(dāng)把圖片添加到畫板時(shí)
- (void)setImage:(UIImage *)image { _image = image; [self.pathArr addObject:image]; //重繪調(diào)用drawRect才能在畫板上顯示圖片 [self setNeedsDisplay]; }
還可以把直接更新路徑數(shù)組的操作封裝在畫板中
- (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
實(shí)現(xiàn)幾個按鈕對畫板的操作:
- (IBAction)clear:(id)sender { //清屏 [_drawView clear]; } - (IBAction)undo:(id)sender { //撤銷 [_drawView undo]; } - (IBAction)eraser:(id)sender { //擦除 就是把路徑的顏色設(shè)置為畫板的背景色,假象 _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 { //選擇照片 //彈出系統(tǒng)相冊 UIImagePickerController *picker = [[UIImagePickerController alloc]init]; //設(shè)置選擇控制器的來源 UIImagePickerControllerSourceTypeSavedPhotosAlbum:照片庫 picker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum; //設(shè)置代理 picker.delegate = self; //modal出控制器 [self presentViewController:picker animated:YES completion:nil]; } - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info { //獲取選擇的圖片 UIImage *image = info[UIImagePickerControllerOriginalImage]; //創(chuàng)建一個處理圖片的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:^{ //保存當(dāng)前畫板上的內(nèi)容 //開啟上下文 UIGraphicsBeginImageContextWithOptions(_drawView.bounds.size, NO, 0); //獲取位圖上下文 CGContextRef ctx = UIGraphicsGetCurrentContext(); //把控件上的圖層渲染到上下文 [_drawView.layer renderInContext:ctx]; //獲取上下文中的圖片 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); //關(guān)閉上下文 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,這時(shí)候需要對圖片進(jìn)行移動縮放旋轉(zhuǎn)這些操作的話,但是UIImage是不能拉伸旋轉(zhuǎn)這些操作的,UIImageView才可以,所以解決思路就是自定義一個view來專門處理對圖片的操作,在自定義view上放一個UIImageView,從相冊選擇圖片后獲取的image設(shè)置給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; //設(shè)置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]; //旋轉(zhuǎn)手勢 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]; //設(shè)置imageview的形變 self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, tranp.x, tranp.y); //復(fù)位 [pan setTranslation:CGPointZero inView:self.imageView]; } #pragma mark - 處理旋轉(zhuǎn)手勢 - (void)rotation:(UIRotationGestureRecognizer *)rotation { //設(shè)置imageview的形變 self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, rotation.rotation); //復(fù)位 rotation.rotation = 0; } #pragma mark - 處理縮放手勢 - (void)pinch:(UIPinchGestureRecognizer *)pinch { //設(shè)置imageview的形變 self.imageView.transform = CGAffineTransformScale(self.imageView.transform, pinch.scale, pinch.scale); //復(fù)位 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) { //高亮?xí)r生成一張新的圖片 //開啟位圖上下文 UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0); //獲取位圖上下文 CGContextRef ctx = UIGraphicsGetCurrentContext(); //把控件的圖層渲染到上下文 [self.layer renderInContext:ctx]; //從上下文中獲取新的圖片 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); //關(guān)閉上下文 UIGraphicsEndImageContext(); //調(diào)用block if(_handleCompletionBlock) { _handleCompletionBlock(image); } //移除父控件 [self removeFromSuperview]; }]; }]; } } #pragma mark - 手勢代理方法 <UIGestureRecognizerDelegate> - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { //yes表示同時(shí)支持多個手勢 return YES; } - (void)setImage:(UIImage *)image { _image = image; //把圖片展示到UIImageView上 self.imageView.image = image; } @end
需要注意的是,當(dāng)長按將操作過的圖片繪制都畫板上生成一張新的圖片后,這時(shí)候需要把這個image設(shè)置給畫板drawView,但是這時(shí)候就必須要在專門處理圖片的view中去import畫板view,這樣耦合性太強(qiáng)。所以為了解耦,可以使用代理或者Block。我用了Block將剛剛生成的image先保存起來,在控制器中初始化imageHandleView之后再賦值給drawView。
最后保存畫板上的內(nèi)容就是將畫板上的內(nèi)容生成圖片保存到相冊即可。注意,保存完之后執(zhí)行的方法必須是這個:
最后效果圖是這樣的:
以上就是本文的全部內(nèi)容,希望對大家學(xué)習(xí)iOS程序設(shè)計(jì)有所幫助。
相關(guān)文章
IOS 七種手勢操作(拖動、捏合、旋轉(zhuǎn)、點(diǎn)按、長按、輕掃、自定義)詳解及實(shí)例代碼
這篇文章主要介紹了IOS 七種手勢操作(拖動、捏合、旋轉(zhuǎn)、點(diǎn)按、長按、輕掃、自定義)詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-12-12iOS應(yīng)用中UISearchDisplayController搜索效果的用法
這篇文章主要介紹了iOS應(yīng)用中UISearchDisplayController搜索效果的用法,包括點(diǎn)擊搜索出現(xiàn)黑條問題的解決方法,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2016-02-02UITableViewCell在編輯狀態(tài)下背景顏色的修改方法
這篇文章主要給大家介紹了關(guān)于UITableViewCell在編輯狀態(tài)下背景顏色的修改方法,文中通過示例代碼介紹的非常詳細(xì),對大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編一起來學(xué)習(xí)學(xué)習(xí)吧。2017-07-07iOS App設(shè)計(jì)模式開發(fā)中對建造者模式的運(yùn)用實(shí)例
這篇文章主要介紹了iOS App設(shè)計(jì)模式開發(fā)中對建造者模式的運(yùn)用實(shí)例,示例代碼為傳統(tǒng)的Objective-C,需要的朋友可以參考下2016-04-04iOS App開發(fā)中UISearchBar搜索欄組件的基本用法整理
iOS開發(fā)組件中自帶的UISearchBar提供了很多基礎(chǔ)和好用的搜索欄UI功能,下面就來總結(jié)一下iOS App開發(fā)中UISearchBar搜索欄組件的基本用法整理,需要的朋友可以參考下2016-05-05iOS應(yīng)用開發(fā)中的文字選中操作控件UITextView用法講解
這篇文章主要介紹了iOS應(yīng)用開發(fā)中的文字選中操作控件UITextView用法講解,代碼基于傳統(tǒng)的Objective-C語言,需要的朋友可以參考下2016-02-02IOS開發(fā)之tableView點(diǎn)擊行跳轉(zhuǎn)并帶有“顯示”更多功能
這篇文章給大家介紹通過點(diǎn)擊城市中的tableView跳轉(zhuǎn)到旅游景點(diǎn)的tableView,下面會有“顯示”更多的功能,代碼簡單易懂,對ios點(diǎn)擊tableview跳轉(zhuǎn)相關(guān)知識感興趣的朋友一起學(xué)習(xí)吧2016-03-03IOS開發(fā)中使用writeToFile時(shí)的注意事項(xiàng)
本篇文章主要介紹了開發(fā)中使用writeToFile時(shí)的注意事項(xiàng),具有很好的參考價(jià)值。下面跟著小編一起來看下吧2017-03-03