使用說明github
[[SPClipTool shareClipTool] sp_clipOriginImage:pickerImage complete:^(UIImage * _Nonnull image) { // 獲取到裁剪後的image 後續操做 }];
圖片裁剪,效果以下圖,支持圖片拖拽,縮放,裁剪框自由變換大小。ide
兩個UIImageView,一個作背景,並加上蒙版效果,另一個經過蒙版控制顯示區域,而且保證兩個UIImageView平移和縮放的時候徹底重疊。最後使用一個UIView來作交互,繪製三分網格線(專業術語我不知道叫啥,截圖時一個參照,2/3 ≈0.667 接近黃金比0.618)。工具
手勢的處理和三分網格線繪製的時候,計算線條寬度和長度須要特別注意。ui
爲了加強用戶體驗,在裁剪框邊緣交互設計的時候,注意額外增長用戶的可操控範圍。spa
- (void)setupImageView { // backgroudImageView UIImageView *backgroudImageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; backgroudImageView.contentMode = UIViewContentModeScaleAspectFit; backgroudImageView.image = self.originImage; [self.view addSubview:backgroudImageView]; self.backgroudImageView = backgroudImageView; backgroudImageView.layer.mask = [[CALayer alloc] init]; backgroudImageView.layer.mask.frame = backgroudImageView.bounds; backgroudImageView.layer.mask.backgroundColor = [UIColor colorWithWhite:1 alpha:0.5].CGColor; // clipImageView UIImageView *clipImageView = [[UIImageView alloc] initWithFrame:backgroudImageView.frame]; clipImageView.userInteractionEnabled = YES; clipImageView.image = backgroudImageView.image; clipImageView.contentMode = backgroudImageView.contentMode; [self.view addSubview:clipImageView]; self.clipImageView = clipImageView; clipImageView.layer.mask = [[CALayer alloc] init]; clipImageView.layer.mask.backgroundColor = [UIColor whiteColor].CGColor; clipImageView.layer.mask.borderColor = [UIColor whiteColor].CGColor; clipImageView.layer.mask.borderWidth = 1; [clipImageView.layer.mask removeAllAnimations]; UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(imagePan:)]; [clipImageView addGestureRecognizer:panGesture]; }
- (void)setupClipView { SPClipView *clipView = [[SPClipView alloc] init]; clipView.backgroundColor = [UIColor clearColor]; // 打開下面兩行註釋,能夠查看真實clipView的大小。 // clipView.layer.borderColor = [UIColor whiteColor].CGColor; // clipView.layer.borderWidth = 1; [self.view addSubview:clipView]; self.clipView = clipView; // 獲取真實frame dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ clipView.frame = CGRectMake(0, 0, self.view.width / 1.5, self.view.height / 1.5); clipView.center = self.view.center; self.backgroudImageView.frame = self.view.bounds; self.clipImageView.frame = self.backgroudImageView.frame; [self dealMask]; }); UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(clipPan:)]; [clipView addGestureRecognizer:panGesture]; UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchGestureAction:)]; [self.view addGestureRecognizer:pinchGesture]; }
#pragma mark- UIPanGestureRecognizer - (void)clipPan:(UIPanGestureRecognizer *)panGesture { CGPoint point = [panGesture translationInView:self.clipView]; self.clipView.origin = [self.clipView convertPoint:point toView:self.view]; [self expandClipView:panGesture]; [self dealGuideLine:panGesture]; [self dealMask]; [panGesture setTranslation:CGPointMake(0, 0) inView:self.view]; } - (void)imagePan:(UIPanGestureRecognizer *)panGesture { CGPoint point = [panGesture translationInView:self.clipImageView]; self.clipImageView.origin = [self.clipImageView convertPoint:point toView:self.view]; self.backgroudImageView.center = self.clipImageView.center; [self dealGuideLine:panGesture]; [self dealMask]; [panGesture setTranslation:CGPointMake(0, 0) inView:self.view]; } #pragma mark- UIPinchGestureRecognizer - (void)pinchGestureAction:(UIPinchGestureRecognizer *)pinchGesture { switch (pinchGesture.state) { case UIGestureRecognizerStateBegan: { if (lastScale <= minScale) { lastScale = minScale; }else if (lastScale >= maxScale) { lastScale = maxScale; } self.clipImageViewCenter = self.clipImageView.center; self.clipView.showGuideLine = YES; } case UIGestureRecognizerStateChanged: { CGFloat currentScale = lastScale + pinchGesture.scale - 1; if (currentScale > minScale && currentScale < maxScale) { [self dealViewScale:currentScale]; } } break; case UIGestureRecognizerStateEnded: lastScale += (pinchGesture.scale - 1); self.clipView.showGuideLine = NO; [self.clipView setNeedsDisplay]; default: break; } } #pragma mark- Action - (void)dealViewScale:(CGFloat)currentScale { self.clipImageView.width = currentScale * self.view.width; self.clipImageView.height = currentScale * self.view.height; self.clipImageView.center = self.clipImageViewCenter; self.backgroudImageView.frame = self.clipImageView.frame; self.backgroudImageView.layer.mask.frame = self.backgroudImageView.bounds; [self.backgroudImageView.layer.mask removeAllAnimations]; [self dealMask]; } - (void)expandClipView:(UIPanGestureRecognizer *)panGesture { CGPoint point = [panGesture translationInView:self.clipImageView]; CGFloat margin = 60; CGFloat minValue = margin; if (panGesture.numberOfTouches) { CGPoint location = [panGesture locationOfTouch:0 inView:panGesture.view]; if (location.x < margin) { self.clipView.width = MAX(self.clipView.width -= point.x, minValue); } if ((self.clipView.width - location.x) < margin) { self.clipView.frame = CGRectMake(self.clipView.x - point.x, self.clipView.y, self.clipView.width + point.x, self.clipView.height); } if (location.y < margin) { self.clipView.height = MAX(self.clipView.height -= point.y, minValue); } if ((self.clipView.height - location.y) < margin) { self.clipView.frame = CGRectMake(self.clipView.x , self.clipView.y - point.y, self.clipView.width, self.clipView.height + point.y); } } } - (void)dealGuideLine:(UIPanGestureRecognizer *)panGesture { switch (panGesture.state) { case UIGestureRecognizerStateBegan: self.clipView.showGuideLine = YES; break; case UIGestureRecognizerStateEnded: self.clipView.showGuideLine = NO; break; default: break; } } - (void)dealMask { // 額外增長拖拉區域 加強邊緣手勢體驗 CGFloat margin = 30; CGRect rect = [self.view convertRect:self.clipView.frame toView:self.clipImageView]; self.clipImageView.layer.mask.frame = CGRectMake(rect.origin.x + margin, rect.origin.y + margin, rect.size.width - 2 * margin, rect.size.height - 2 * margin); [self.clipView setNeedsDisplay]; [self.clipImageView.layer.mask removeAllAnimations]; }
- (void)clipImage { CGSize size = self.view.bounds.size; UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale); [self.view drawViewHierarchyInRect:self.view.bounds afterScreenUpdates:NO]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); CGImageRef cgImage = [image CGImage]; CGRect rect = [self.clipImageView convertRect:self.clipImageView.layer.mask.frame toView:self.view]; // 邊框線條寬度值 CGFloat borderW = 1; CGImageRef cgClipImage = CGImageCreateWithImageInRect(cgImage, CGRectMake((rect.origin.x + borderW / 2) * image.scale, (rect.origin.y + borderW / 2) * image.scale, (rect.size.width - borderW) * image.scale, (rect.size.height - borderW) * image.scale)); UIGraphicsEndImageContext(); if (self.complete) { self.complete([UIImage imageWithCGImage:cgClipImage]); } [self dismissViewControllerAnimated:YES completion:nil]; }
裁剪區域繪製設計
在這裏,裁剪區域的矩形框我並無直接採用clipView的fram大小,而是在其內部繪製了一個矩形框,爲了讓用戶在調節邊緣的時候更靈活,否則只有當手指在邊框內部邊緣才能觸發調節邊框大小的事件。以下圖,能夠看到clipView真實的大小(外框)。3d
@implementation SPClipView - (void)drawRect:(CGRect)rect { // Drawing code CGContextRef currentContext = UIGraphicsGetCurrentContext(); CGContextSetStrokeColorWithColor(currentContext, [UIColor whiteColor].CGColor); CGContextSetLineWidth(currentContext, 1); // 額外增長拖拉區域 加強邊緣手勢體驗,該值應該和上文- (void)dealMask;方法中的margin一致 CGFloat margin = 30; // 繪製矩形框 CGContextAddRect(currentContext, CGRectMake(margin, margin, self.width - 2 * margin, self.height - 2 * margin)); CGContextStrokePath(currentContext); // 繪製三分線 CGFloat maskW = self.width - 2 * margin; CGFloat maskH = self.height - 2 * margin; CGContextSetLineWidth(currentContext, 0.5); if (self.showGuideLine) { CGContextSetStrokeColorWithColor(currentContext, [UIColor whiteColor].CGColor); }else { CGContextSetStrokeColorWithColor(currentContext, [UIColor clearColor].CGColor); } CGContextMoveToPoint(currentContext, margin, maskH / 3 + margin); CGContextAddLineToPoint(currentContext, self.width - margin, maskH / 3 + margin); CGContextMoveToPoint(currentContext, margin, 2 / 3.0 * maskH + margin); CGContextAddLineToPoint(currentContext, self.width - margin, 2 / 3.0 * maskH + margin); CGContextMoveToPoint(currentContext, maskW / 3 + margin, margin); CGContextAddLineToPoint(currentContext, maskW / 3+ margin, self.height - margin); CGContextMoveToPoint(currentContext, 2 / 3.0 * maskW + margin, margin); CGContextAddLineToPoint(currentContext, 2 / 3.0 * maskW + margin, self.height - margin); CGContextStrokePath(currentContext); // 繪製四角 CGFloat cornerL = 15; CGFloat cornerLW = 2; // 實際的長度 CGFloat cornerRL = cornerL + cornerLW; CGPoint originH = CGPointMake(margin - cornerLW, margin - cornerLW / 2); CGPoint originV = CGPointMake(margin - cornerLW / 2, margin - cornerLW); CGContextSetStrokeColorWithColor(currentContext, [UIColor whiteColor].CGColor); CGContextSetLineWidth(currentContext, cornerLW); // 左上 CGContextMoveToPoint(currentContext, originH.x, originH.y); CGContextAddLineToPoint(currentContext, originH.x + cornerRL, originH.y); CGContextMoveToPoint(currentContext, originV.x, originV.y); CGContextAddLineToPoint(currentContext, originV.x, originV.y + cornerRL); // 左下 CGContextMoveToPoint(currentContext, originH.x, originH.y + maskH + cornerLW); CGContextAddLineToPoint(currentContext, originH.x + cornerRL, originH.y + maskH + cornerLW); CGContextMoveToPoint(currentContext, originV.x, originV.y + maskH + 2 * cornerLW); CGContextAddLineToPoint(currentContext, originV.x, originV.y + maskH + 2 * cornerLW - cornerRL); // 右上 CGContextMoveToPoint(currentContext, originH.x + maskW + 2 * cornerLW, originH.y); CGContextAddLineToPoint(currentContext, originH.x + maskW + 2 * cornerLW - cornerRL, originH.y); CGContextMoveToPoint(currentContext, originV.x + maskW + cornerLW, originV.y); CGContextAddLineToPoint(currentContext, originV.x + maskW + cornerLW, originV.y + cornerRL); // 右下 CGContextMoveToPoint(currentContext, originH.x + maskW + 2 * cornerLW, originH.y + maskH + cornerLW); CGContextAddLineToPoint(currentContext, originH.x + maskW + 2 * cornerLW - cornerRL, originH.y + maskH + cornerLW); CGContextMoveToPoint(currentContext, originV.x + maskW + cornerLW, originV.y + maskH + 2 * cornerLW); CGContextAddLineToPoint(currentContext, originV.x + maskW + cornerLW, originV.y + maskH + 2 * cornerLW - cornerRL); CGContextStrokePath(currentContext); }
這裏必定要注意線條的寬度,線條是有寬度的,繪製路徑位於線條的中心位置。code