iOS簡(jiǎn)單畫板開(kāi)發(fā)案例分享
最近在學(xué)習(xí)Quartz2D,學(xué)習(xí)了一個(gè)簡(jiǎn)單畫板的實(shí)現(xiàn),現(xiàn)在把實(shí)現(xiàn)過(guò)程記錄一下。
主要用到的點(diǎn)就是畫線,截屏,繪制圖片,選擇圖片,以及保存所有繪制的線。
首先在storyboard上布局好控件,設(shè)置約束等等,最后的效果是這樣:
自定義畫板DrawView,使用時(shí)可能是從xib中加載,也可能是手動(dòng)創(chuàng)建,所以創(chuàng)建對(duì)象的方法需要實(shí)現(xiàn)兩個(gè):
#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í)要做的事情就是給畫板添加拖動(dòng)手勢(shì),也可以將畫筆路徑的線寬在這里設(shè)置
//自定義初始化方法 - (void)setUp { //添加手勢(shì) UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)]; [self addGestureRecognizer:pan]; //初始化時(shí)設(shè)置路徑線寬 _lineWidth = 2; }
手指在畫板上移動(dòng)時(shí)開(kāi)始繪制線條,這里因?yàn)樵腢IBezierPath類沒(méi)有辦法設(shè)置路徑顏色,所以這里只能自定義Path類了
#import <UIKit/UIKit.h> @interface DrawPath : UIBezierPath @property (nonatomic, strong) UIColor *pathColor; @end
手指移動(dòng)時(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 { //獲取開(kāi)始的觸摸點(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í)路徑還沒(méi)有被添加到數(shù)組里面 [_pathArr addObject:_path]; //設(shè)置起點(diǎn) [_path moveToPoint:startP]; } //連線 [_path addLineToPoint:startP]; //重繪,調(diào)用drawRect方法 [self setNeedsDisplay]; }
畫線實(shí)現(xiàn)drawRect方法,繪制線條或者圖片時(shí),是把數(shù)組中的路徑全部畫出來(lái)
- (void)drawRect:(CGRect)rect { //把所有路徑畫出來(lái) 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)幾個(gè)按鈕對(duì)畫板的操作:
- (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)相冊(cè) UIImagePickerController *picker = [[UIImagePickerController alloc]init]; //設(shè)置選擇控制器的來(lái)源 UIImagePickerControllerSourceTypeSavedPhotosAlbum:照片庫(kù) 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)建一個(gè)處理圖片的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)容 //開(kāi)啟上下文 UIGraphicsBeginImageContextWithOptions(_drawView.bounds.size, NO, 0); //獲取位圖上下文 CGContextRef ctx = UIGraphicsGetCurrentContext(); //把控件上的圖層渲染到上下文 [_drawView.layer renderInContext:ctx]; //獲取上下文中的圖片 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); //關(guān)閉上下文 UIGraphicsEndImageContext(); //保存圖片到相冊(cè) UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil); self.drawView.alpha = 0; } completion:^(BOOL finished) { [UIView animateWithDuration:0.15 animations:^{ self.drawView.alpha = 1; }]; }]; } //保存成功后的方法必須是這個(gè),不能隨便寫 - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { NSLog(@"保存成功"); }
從相冊(cè)選擇完圖片后把圖片顯示在畫板上了但是還沒(méi)有渲染到layer,這時(shí)候需要對(duì)圖片進(jìn)行移動(dòng)縮放旋轉(zhuǎn)這些操作的話,但是UIImage是不能拉伸旋轉(zhuǎn)這些操作的,UIImageView才可以,所以解決思路就是自定義一個(gè)view來(lái)專門處理對(duì)圖片的操作,在自定義view上放一個(gè)UIImageView,從相冊(cè)選擇圖片后獲取的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; //添加手勢(shì) [self setUpGestureRecognizer]; //把這個(gè)imageview添加到圖片處理的view上 [self addSubview:imageV]; } return _imageView; } #pragma mark - 添加手勢(shì) - (void)setUpGestureRecognizer { //平移手勢(shì) UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)]; [_imageView addGestureRecognizer:pan]; //旋轉(zhuǎn)手勢(shì) UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotation:)]; rotation.delegate = self; [_imageView addGestureRecognizer:rotation]; //縮放手勢(shì) UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinch:)]; pinch.delegate = self; [_imageView addGestureRecognizer:pinch]; //長(zhǎng)按手勢(shì) UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPress:)]; [_imageView addGestureRecognizer:longPress]; } #pragma mark - 處理平移手勢(shì) - (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)手勢(shì) - (void)rotation:(UIRotationGestureRecognizer *)rotation { //設(shè)置imageview的形變 self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, rotation.rotation); //復(fù)位 rotation.rotation = 0; } #pragma mark - 處理縮放手勢(shì) - (void)pinch:(UIPinchGestureRecognizer *)pinch { //設(shè)置imageview的形變 self.imageView.transform = CGAffineTransformScale(self.imageView.transform, pinch.scale, pinch.scale); //復(fù)位 pinch.scale = 1; } #pragma mark - 處理長(zhǎng)按手勢(shì) - (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生成一張新的圖片 //開(kāi)啟位圖上下文 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 - 手勢(shì)代理方法 <UIGestureRecognizerDelegate> - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { //yes表示同時(shí)支持多個(gè)手勢(shì) return YES; } - (void)setImage:(UIImage *)image { _image = image; //把圖片展示到UIImageView上 self.imageView.image = image; } @end
需要注意的是,當(dāng)長(zhǎng)按將操作過(guò)的圖片繪制都畫板上生成一張新的圖片后,這時(shí)候需要把這個(gè)image設(shè)置給畫板drawView,但是這時(shí)候就必須要在專門處理圖片的view中去import畫板view,這樣耦合性太強(qiáng)。所以為了解耦,可以使用代理或者Block。我用了Block將剛剛生成的image先保存起來(lái),在控制器中初始化imageHandleView之后再賦值給drawView。
最后保存畫板上的內(nèi)容就是將畫板上的內(nèi)容生成圖片保存到相冊(cè)即可。注意,保存完之后執(zhí)行的方法必須是這個(gè):
最后效果圖是這樣的:
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家學(xué)習(xí)iOS程序設(shè)計(jì)有所幫助。
相關(guān)文章
IOS 七種手勢(shì)操作(拖動(dòng)、捏合、旋轉(zhuǎn)、點(diǎn)按、長(zhǎng)按、輕掃、自定義)詳解及實(shí)例代碼
這篇文章主要介紹了IOS 七種手勢(shì)操作(拖動(dòng)、捏合、旋轉(zhuǎn)、點(diǎn)按、長(zhǎng)按、輕掃、自定義)詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-12-12iOS應(yīng)用中UISearchDisplayController搜索效果的用法
這篇文章主要介紹了iOS應(yīng)用中UISearchDisplayController搜索效果的用法,包括點(diǎn)擊搜索出現(xiàn)黑條問(wèn)題的解決方法,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2016-02-02UITableViewCell在編輯狀態(tài)下背景顏色的修改方法
這篇文章主要給大家介紹了關(guān)于UITableViewCell在編輯狀態(tài)下背景顏色的修改方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編一起來(lái)學(xué)習(xí)學(xué)習(xí)吧。2017-07-07IOS開(kāi)發(fā)第三方語(yǔ)音-微信語(yǔ)音
微信語(yǔ)音開(kāi)放平臺(tái)致力于為開(kāi)發(fā)者提供免費(fèi)的語(yǔ)音技術(shù),目前已經(jīng)開(kāi)放的語(yǔ)音技術(shù)包括在線語(yǔ)音識(shí)別、在線語(yǔ)音合成等,下面通過(guò)本篇文章給大家介紹IOS開(kāi)發(fā)第三方語(yǔ)言-微信語(yǔ)言,需要的朋友可以一起來(lái)學(xué)習(xí)下2015-08-08iOS App設(shè)計(jì)模式開(kāi)發(fā)中對(duì)建造者模式的運(yùn)用實(shí)例
這篇文章主要介紹了iOS App設(shè)計(jì)模式開(kāi)發(fā)中對(duì)建造者模式的運(yùn)用實(shí)例,示例代碼為傳統(tǒng)的Objective-C,需要的朋友可以參考下2016-04-04iOS開(kāi)發(fā)中簡(jiǎn)單實(shí)用的幾個(gè)小技巧
大家可能都知道,在開(kāi)發(fā)過(guò)程中我們總會(huì)遇到各種各樣的小問(wèn)題,有些小問(wèn)題并不是十分容易解決。在此我就總結(jié)一下,我在開(kāi)發(fā)中遇到的各種小問(wèn)題,以及我的解決方法,也算是些小技巧吧,分享給大家,方便大家在iOS開(kāi)發(fā)的時(shí)候能夠參考借鑒,下面有需要的朋友一起來(lái)看看吧。2016-11-11iOS App開(kāi)發(fā)中UISearchBar搜索欄組件的基本用法整理
iOS開(kāi)發(fā)組件中自帶的UISearchBar提供了很多基礎(chǔ)和好用的搜索欄UI功能,下面就來(lái)總結(jié)一下iOS App開(kāi)發(fā)中UISearchBar搜索欄組件的基本用法整理,需要的朋友可以參考下2016-05-05iOS應(yīng)用開(kāi)發(fā)中的文字選中操作控件UITextView用法講解
這篇文章主要介紹了iOS應(yīng)用開(kāi)發(fā)中的文字選中操作控件UITextView用法講解,代碼基于傳統(tǒng)的Objective-C語(yǔ)言,需要的朋友可以參考下2016-02-02IOS開(kāi)發(fā)之tableView點(diǎn)擊行跳轉(zhuǎn)并帶有“顯示”更多功能
這篇文章給大家介紹通過(guò)點(diǎn)擊城市中的tableView跳轉(zhuǎn)到旅游景點(diǎn)的tableView,下面會(huì)有“顯示”更多的功能,代碼簡(jiǎn)單易懂,對(duì)ios點(diǎn)擊tableview跳轉(zhuǎn)相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)吧2016-03-03IOS開(kāi)發(fā)中使用writeToFile時(shí)的注意事項(xiàng)
本篇文章主要介紹了開(kāi)發(fā)中使用writeToFile時(shí)的注意事項(xiàng),具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-03-03