最近在學習Quartz2D,學習了一個簡單畫板的實現,現在把實現過程記錄一下。
主要用到的點就是畫線,截屏,繪製圖片,選擇圖片,以及儲存所有繪製的線。
首先在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;
- (void)awakeFromNib { [self setUp]; } - (instancetype)initWithFrame:(CGRect)frame { if (self == [super initWithFrame:frame]) { [self setUp]; } return self;}
setUp初始化方法,初始化時要做的事情就是給畫板添加拖動手勢,也可以將畫筆路徑的線寬在這裡設定
//自訂初始化方法- (void)setUp { //添加手勢 UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)]; [self addGestureRecognizer:pan]; //初始化時設定路徑線寬 _lineWidth = 2; }
手指在畫板上移動時開始繪製線條,這裡因為原生的UIBezierPath類沒有辦法設定路徑顏色,所以這裡只能自訂Path類了
#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方法,繪製線條或者圖片時,是把數組中的路徑全部畫出來
- (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]; } } }
當把圖片添加到畫板時
- (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;
最後效果圖是這樣的:
以上就是本文的全部內容,希望對大家學習iOS程式設計有所協助。