亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > iOS > 正文

iOS中實現動態區域裁剪圖片功能實例

2019-10-21 18:43:33
字體:
來源:轉載
供稿:網友

前言

相信大家應該都有所體會,裁剪圖片功能在很多上傳圖片的場景里都需要用到,一方面應用服務器可能對圖片的尺寸大小有限制,因而希望上傳的圖片都是符合規定的,另一方面,用戶可能希望只上傳圖片中的部分內容,突出圖片中關鍵的信息。而為了滿足用戶多種多樣的裁剪需求,就需要裁剪圖片時能支持由用戶動態地改變裁剪范圍、裁剪尺寸等。

動態裁剪圖片的基本過程大致可以分為以下幾步

  • 顯示圖片與裁剪區域
  • 支持移動和縮放圖片
  • 支持手勢改變裁剪區域
  • 進行圖片裁剪并獲得裁剪后的圖片

顯示圖片與裁剪區域

顯示圖片

在裁剪圖片之前,首先我們要在頁面上顯示待裁剪的圖片,如下圖所示

ios,裁剪屏幕指定區域,裁剪圖片指定區域,圖片裁剪

這一步比較簡單,配置一個 UIImageView 來放置圖片即可。但是要注意一點,UIImageView 有多種 contentMode,最常見有三種

  • UIViewContentModeScaleToFill
  • UIViewContentModeScaleAspectFit
  • UIViewContentModeScaleAspectFill

三者區別可以看下面的比較

UIViewContentModeScaleToFill

ios,裁剪屏幕指定區域,裁剪圖片指定區域,圖片裁剪

UIViewContentModeScaleAspectFit

ios,裁剪屏幕指定區域,裁剪圖片指定區域,圖片裁剪

UIViewContentModeScaleAspectFill

ios,裁剪屏幕指定區域,裁剪圖片指定區域,圖片裁剪

可以看出,ScaleToFill 會改變圖片的長寬比例來鋪滿整個 UIImageView,ScaleAspectFill 則會保持圖片比例來鋪滿,從而會有部分圖片內容超出 UIImageView 區域的情況,而 ScaleAspectFit 則會保證圖片比例不變,同時圖片內容都顯示在 UIImageView 中,即使無法鋪滿 UIImageView。

因此不同顯示模式會影響到我們最終顯示到屏幕上的圖片的樣子,而在裁剪過程中最理想的放置圖片的模式則是,圖片的短邊剛好鋪滿裁剪區域的短邊,而長邊至少不會小于裁剪區域的長邊,這就要求我們要考慮裁剪區域的長寬來放置我們的圖片。

裁剪區域

接下來我們要放置我們的裁剪區域,它的樣子如下所示

ios,裁剪屏幕指定區域,裁剪圖片指定區域,圖片裁剪

裁剪區域本身就是在 UIImageView 上放上一層 UIView,再在 UIView 上繪制出一個白邊框的方格 Layer。

首先自定義一個 CAShapeLayer

#import <QuartzCore/QuartzCore.h>@interface YasicClipAreaLayer : CAShapeLayer@property(assign, nonatomic) NSInteger cropAreaLeft;@property(assign, nonatomic) NSInteger cropAreaTop;@property(assign, nonatomic) NSInteger cropAreaRight;@property(assign, nonatomic) NSInteger cropAreaBottom;- (void)setCropAreaLeft:(NSInteger)cropAreaLeft CropAreaTop:(NSInteger)cropAreaTop CropAreaRight:(NSInteger)cropAreaRight CropAreaBottom:(NSInteger)cropAreaBottom;@end@implementation YasicClipAreaLayer- (instancetype)init{ self = [super init]; if (self) { _cropAreaLeft = 50; _cropAreaTop = 50; _cropAreaRight = SCREEN_WIDTH - self.cropAreaLeft; _cropAreaBottom = 400; } return self;}- (void)drawInContext:(CGContextRef)ctx{ UIGraphicsPushContext(ctx);  CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor); CGContextSetLineWidth(ctx, lineWidth); CGContextMoveToPoint(ctx, self.cropAreaLeft, self.cropAreaTop); CGContextAddLineToPoint(ctx, self.cropAreaLeft, self.cropAreaBottom); CGContextSetShadow(ctx, CGSizeMake(2, 0), 2.0); CGContextStrokePath(ctx);  CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor); CGContextSetLineWidth(ctx, lineWidth); CGContextMoveToPoint(ctx, self.cropAreaLeft, self.cropAreaTop); CGContextAddLineToPoint(ctx, self.cropAreaRight, self.cropAreaTop); CGContextSetShadow(ctx, CGSizeMake(0, 2), 2.0); CGContextStrokePath(ctx);  CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor); CGContextSetLineWidth(ctx, lineWidth); CGContextMoveToPoint(ctx, self.cropAreaRight, self.cropAreaTop); CGContextAddLineToPoint(ctx, self.cropAreaRight, self.cropAreaBottom); CGContextSetShadow(ctx, CGSizeMake(-2, 0), 2.0); CGContextStrokePath(ctx);  CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor); CGContextSetLineWidth(ctx, lineWidth); CGContextMoveToPoint(ctx, self.cropAreaLeft, self.cropAreaBottom); CGContextAddLineToPoint(ctx, self.cropAreaRight, self.cropAreaBottom); CGContextSetShadow(ctx, CGSizeMake(0, -2), 2.0); CGContextStrokePath(ctx);  UIGraphicsPopContext();}- (void)setCropAreaLeft:(NSInteger)cropAreaLeft{ _cropAreaLeft = cropAreaLeft; [self setNeedsDisplay];}- (void)setCropAreaTop:(NSInteger)cropAreaTop{ _cropAreaTop = cropAreaTop; [self setNeedsDisplay];}- (void)setCropAreaRight:(NSInteger)cropAreaRight{ _cropAreaRight = cropAreaRight; [self setNeedsDisplay];}- (void)setCropAreaBottom:(NSInteger)cropAreaBottom{ _cropAreaBottom = cropAreaBottom; [self setNeedsDisplay];}- (void)setCropAreaLeft:(NSInteger)cropAreaLeft CropAreaTop:(NSInteger)cropAreaTop CropAreaRight:(NSInteger)cropAreaRight CropAreaBottom:(NSInteger)cropAreaBottom{ _cropAreaLeft = cropAreaLeft; _cropAreaRight = cropAreaRight; _cropAreaTop = cropAreaTop; _cropAreaBottom = cropAreaBottom;  [self setNeedsDisplay];}@end

這里 layer 有幾個屬性 cropAreaLeft、cropAreaRight、cropAreaTop、cropAreaBottom,從命名上可以看出這幾個屬性定義了這個 layer 上繪制的白邊框裁剪區域的坐標信息。還暴露了一個方法用于配置這四個屬性。

然后在 CAShapeLayer 內部,重點在于復寫 drawInContext 方法,這個方法負責直接在圖層上繪圖,復寫的方法主要做的事情是根據上面四個屬性 cropAreaLeft、cropAreaRight、cropAreaTop、cropAreaBottom 繪制出封閉的四條線,這樣就能表示裁剪區域的邊界了。

要注意的是 drawInContext 方法不能手動顯示調用,必須通過調用 setNeedsDisplay 或者 setNeedsDisplayInRect 讓系統自動調該方法。

在裁剪頁面里,我們放置了一個 cropView,然后將自定義的 CAShaplayer 加入到這個 view 上

 self.cropView.layer.sublayers = nil; YasicClipAreaLayer * layer = [[YasicClipAreaLayer alloc] init];  CGRect cropframe = CGRectMake(self.cropAreaX, self.cropAreaY, self.cropAreaWidth, self.cropAreaHeight); UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:self.cropView.frame cornerRadius:0]; UIBezierPath * cropPath = [UIBezierPath bezierPathWithRect:cropframe]; [path appendPath:cropPath]; layer.path = path.CGPath;  layer.fillRule = kCAFillRuleEvenOdd; layer.fillColor = [[UIColor blackColor] CGColor]; layer.opacity = 0.5;  layer.frame = self.cropView.bounds; [layer setCropAreaLeft:self.cropAreaX CropAreaTop:self.cropAreaY CropAreaRight:self.cropAreaX + self.cropAreaWidth CropAreaBottom:self.cropAreaY + self.cropAreaHeight]; [self.cropView.layer addSublayer:layer]; [self.view bringSubviewToFront:self.cropView];

這里主要是為了用自定義的 CAShapelayer 產生出空心遮罩的效果,從而出現中心的裁剪區域高亮而四周非裁剪區域有蒙層的效果,示意圖如下

ios,裁剪屏幕指定區域,裁剪圖片指定區域,圖片裁剪

所以首先確定了 cashapelayer 的大小為 cropview 的大小,生成一個對應的 UIBezierPath,然后根據裁剪區域的大?。ㄓ?self.cropAreaX, self.cropAreaY, self.cropAreaWidth, self.cropAreaHeight 確定)生成空心遮罩的內圈 UIBezierPath,

CGRect cropframe = CGRectMake(self.cropAreaX, self.cropAreaY, self.cropAreaWidth, self.cropAreaHeight); UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:self.cropView.frame cornerRadius:0]; UIBezierPath * cropPath = [UIBezierPath bezierPathWithRect:cropframe]; [path appendPath:cropPath]; layer.path = path.CGPath;

然后將這個 path 配置給 CAShapeLayer,并將 CAShapeLayer 的 fillRule 配置為 kCAFillRuleEvenOdd

 layer.fillRule = kCAFillRuleEvenOdd; layer.fillColor = [[UIColor blackColor] CGColor]; layer.opacity = 0.5; layer.frame = self.cropView.bounds;

其中 fillRule 屬性表示使用哪一種算法去判斷畫布上的某區域是否屬于該圖形“內部”,內部區域將被填充顏色,主要有兩種方式

kCAFillRuleNonZero,這種算法判斷規則是,如果從某一點射出任意方向射線,與對應 Layer 交點為 0 則不在 Layer 內,大于 0 則在 畫布內

ios,裁剪屏幕指定區域,裁剪圖片指定區域,圖片裁剪

kCAFillRuleEvenOdd 如果從某一點射出任意射線,與對應 Layer 交點為偶數則在畫布內,否則不在畫布內

ios,裁剪屏幕指定區域,裁剪圖片指定區域,圖片裁剪

再給 CAShapeLayer 設置蒙層顏色為透明度 0.5 的黑色,就可以實現空心蒙層效果了。

最后就是設置 layer 的四個屬性并繪制內邊框的白色邊線。

 [layer setCropAreaLeft:self.cropAreaX CropAreaTop:self.cropAreaY CropAreaRight:self.cropAreaX + self.cropAreaWidth CropAreaBottom:self.cropAreaY + self.cropAreaHeight]; [self.cropView.layer addSublayer:layer]; [self.view bringSubviewToFront:self.cropView];

合理放置圖片

到這一步我們正確顯示了圖片,也正確顯示出了裁剪區域,但是我們沒有將二者的約束關系建立起來,因此可能會出現下面這樣的情況

ios,裁剪屏幕指定區域,裁剪圖片指定區域,圖片裁剪

可以看到這里由于這張圖片的 width 遠大于 height,因此會在裁剪區域內出現黑色區域,這對用戶來說是一種不好的體驗,同時也會影響到我們后面的裁剪步驟,究其原因是因為我們沒有針對裁剪區域的寬高來放置 UIImageView,我們希望最理想的效果是,能在裁剪區域內實現類似 UIViewContentModeScaleAspectFill 的效果,也就是圖片保持比例地鋪滿裁剪區域,并允許部分內容超出裁剪區域,這就要求

  • 當圖片寬與裁剪區域寬之比大于圖片高與裁剪區域高之比時,將圖片高鋪滿裁剪區域高,圖片寬成比例放大
  • 當圖片高與裁剪區域高之比大于圖片寬與裁剪區域寬之比時,將圖片寬鋪滿裁剪區域寬,圖片高成比例方法

這里我們用到 Masonry 來做這些布局操作

 CGFloat tempWidth = 0.0; CGFloat tempHeight = 0.0;  if (self.targetImage.size.width/self.cropAreaWidth <= self.targetImage.size.height/self.cropAreaHeight) { tempWidth = self.cropAreaWidth; tempHeight = (tempWidth/self.targetImage.size.width) * self.targetImage.size.height; } else if (self.targetImage.size.width/self.cropAreaWidth > self.targetImage.size.height/self.cropAreaHeight) { tempHeight = self.cropAreaHeight; tempWidth = (tempHeight/self.targetImage.size.height) * self.targetImage.size.width; }  [self.bigImageView mas_updateConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.cropAreaX - (tempWidth - self.cropAreaWidth)/2); make.top.mas_equalTo(self.cropAreaY - (tempHeight - self.cropAreaHeight)/2); make.width.mas_equalTo(tempWidth); make.height.mas_equalTo(tempHeight); }];

可以看到,我們進行了兩步判斷,從而獲得合適的寬高值,然后將圖片進行布局,在自動布局時將圖片中心與裁剪區域中心重合,最后我們會得到下面的效果圖

ios,裁剪屏幕指定區域,裁剪圖片指定區域,圖片裁剪

支持移動和縮放圖片

正如上面所講,由于圖片在裁剪區域內是以類似 UIViewContentModeScaleAspectFill 的方式放置的,很可能出現部分內容溢出裁剪區域,因此我們要讓圖片能支持動態移動和縮放,從而使用戶能靈活地裁剪圖片的內容。

具體實現上,我們其實是在 cropview 上加上手勢,間接操作圖片的尺寸和位置,這樣有助于后面我們實現動態改變裁剪區域的實現。

縮放功能

這里實現縮放的原理實際是對放置圖片的 UIImageView 的 frame 進行修改,首先我們要記錄下最初的 UIImageView 的 frame

self.originalFrame = CGRectMake(self.cropAreaX - (tempWidth - self.cropAreaWidth)/2, self.cropAreaY - (tempHeight - self.cropAreaHeight)/2, tempWidth, tempHeight);

然后為 cropView 添加手勢

 // 捏合手勢 UIPinchGestureRecognizer *pinGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handleCenterPinGesture:)]; [self.view addGestureRecognizer:pinGesture];

然后是手勢處理函數

-(void)handleCenterPinGesture:(UIPinchGestureRecognizer *)pinGesture{ CGFloat scaleRation = 3; UIView * view = self.bigImageView;  // 縮放開始與縮放中 if (pinGesture.state == UIGestureRecognizerStateBegan || pinGesture.state == UIGestureRecognizerStateChanged) { // 移動縮放中心到手指中心 CGPoint pinchCenter = [pinGesture locationInView:view.superview]; CGFloat distanceX = view.frame.origin.x - pinchCenter.x; CGFloat distanceY = view.frame.origin.y - pinchCenter.y; CGFloat scaledDistanceX = distanceX * pinGesture.scale; CGFloat scaledDistanceY = distanceY * pinGesture.scale; CGRect newFrame = CGRectMake(view.frame.origin.x + scaledDistanceX - distanceX, view.frame.origin.y + scaledDistanceY - distanceY, view.frame.size.width * pinGesture.scale, view.frame.size.height * pinGesture.scale); view.frame = newFrame; pinGesture.scale = 1; }  // 縮放結束 if (pinGesture.state == UIGestureRecognizerStateEnded) { CGFloat ration = view.frame.size.width / self.originalFrame.size.width;  // 縮放過大 if (ration > 5) { CGRect newFrame = CGRectMake(0, 0, self.originalFrame.size.width * scaleRation, self.originalFrame.size.height * scaleRation); view.frame = newFrame; }  // 縮放過小 if (ration < 0.25) { view.frame = self.originalFrame; } // 對圖片進行位置修正 CGRect resetPosition = CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, view.frame.size.height);  if (resetPosition.origin.x >= self.cropAreaX) { resetPosition.origin.x = self.cropAreaX; } if (resetPosition.origin.y >= self.cropAreaY) { resetPosition.origin.y = self.cropAreaY; } if (resetPosition.size.width + resetPosition.origin.x < self.cropAreaX + self.cropAreaWidth) { CGFloat movedLeftX = fabs(resetPosition.size.width + resetPosition.origin.x - (self.cropAreaX + self.cropAreaWidth)); resetPosition.origin.x += movedLeftX; } if (resetPosition.size.height + resetPosition.origin.y < self.cropAreaY + self.cropAreaHeight) { CGFloat moveUpY = fabs(resetPosition.size.height + resetPosition.origin.y - (self.cropAreaY + self.cropAreaHeight)); resetPosition.origin.y += moveUpY; } view.frame = resetPosition;  // 對圖片縮放進行比例修正,防止過小 if (self.cropAreaX < self.bigImageView.frame.origin.x || ((self.cropAreaX + self.cropAreaWidth) > self.bigImageView.frame.origin.x + self.bigImageView.frame.size.width) || self.cropAreaY < self.bigImageView.frame.origin.y || ((self.cropAreaY + self.cropAreaHeight) > self.bigImageView.frame.origin.y + self.bigImageView.frame.size.height)) { view.frame = self.originalFrame; } }}

在手勢處理時,要注意,為了能跟隨用戶捏合手勢的中心進行縮放,我們要在手勢過程中移動縮放中心到手指中心,這里我們判斷了 pinGesture 的 state 來確定手勢開始、進行中和結束階段。

 if (pinGesture.state == UIGestureRecognizerStateBegan || pinGesture.state == UIGestureRecognizerStateChanged) { // 移動縮放中心到手指中心 CGPoint pinchCenter = [pinGesture locationInView:view.superview]; CGFloat distanceX = view.frame.origin.x - pinchCenter.x; CGFloat distanceY = view.frame.origin.y - pinchCenter.y; CGFloat scaledDistanceX = distanceX * pinGesture.scale; CGFloat scaledDistanceY = distanceY * pinGesture.scale; CGRect newFrame = CGRectMake(view.frame.origin.x + scaledDistanceX - distanceX, view.frame.origin.y + scaledDistanceY - distanceY, view.frame.size.width * pinGesture.scale, view.frame.size.height * pinGesture.scale); view.frame = newFrame; pinGesture.scale = 1; }

pinchCenter 就是捏合手勢的中心,我們獲取到當前圖片 view 的 frame,然后計算當前 view 與手勢中心的 x、y 坐標差,再根據手勢縮放值 scale,創建出新的 frame

 CGRect newFrame = CGRectMake(view.frame.origin.x + scaledDistanceX - distanceX, view.frame.origin.y + scaledDistanceY - distanceY, view.frame.size.width * pinGesture.scale, view.frame.size.height * pinGesture.scale);

這個 frame 的中心坐標就在縮放手勢的中心,將新的 frame 賦值給圖片 view,從而實現依據手勢中心進行縮放的效果。

而在手勢結束階段,我們要對圖片縮放進行邊界保護,既不能放大過大,也不能縮小過小。

CGFloat ration = view.frame.size.width / self.originalFrame.size.width;  // 縮放過大 if (ration > 5) { CGRect newFrame = CGRectMake(0, 0, self.originalFrame.size.width * scaleRation, self.originalFrame.size.height * scaleRation); view.frame = newFrame; }  // 縮放過小 if (ration < 0.25) { view.frame = self.originalFrame; }

同時縮放后如果圖片與裁剪區域出現了空白區域,還要對圖片的位置進行修正以保證圖片始終是覆蓋全裁剪區域的。

// 對圖片進行位置修正 CGRect resetPosition = CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, view.frame.size.height);  if (resetPosition.origin.x >= self.cropAreaX) {  resetPosition.origin.x = self.cropAreaX; } if (resetPosition.origin.y >= self.cropAreaY) {  resetPosition.origin.y = self.cropAreaY; } if (resetPosition.size.width + resetPosition.origin.x < self.cropAreaX + self.cropAreaWidth) {  CGFloat movedLeftX = fabs(resetPosition.size.width + resetPosition.origin.x - (self.cropAreaX + self.cropAreaWidth));  resetPosition.origin.x += movedLeftX; } if (resetPosition.size.height + resetPosition.origin.y < self.cropAreaY + self.cropAreaHeight) {  CGFloat moveUpY = fabs(resetPosition.size.height + resetPosition.origin.y - (self.cropAreaY + self.cropAreaHeight));  resetPosition.origin.y += moveUpY; } view.frame = resetPosition;

這里我們通過生成當前圖片的 CGRect,與裁剪區域的邊界進行如下比較

  • 圖片左邊線大于裁剪區域左邊線時圖片移動到裁剪區域 x 值
  • 圖片上邊線大于裁剪區域上邊線時圖片移動到裁剪區域 y 值
  • 圖片右邊線小于裁剪區域右邊線時圖片右貼裁剪區域右邊線
  • 圖片下邊線小于裁剪區域右邊線時圖片下貼裁剪區域下邊線

進行這番操作后,可能會出現由于圖片過小無法鋪滿裁剪區域的情況,如下圖所示

ios,裁剪屏幕指定區域,裁剪圖片指定區域,圖片裁剪

因此還需要再次對圖片尺寸進行修正

 // 對圖片縮放進行比例修正,防止過小 if (self.cropAreaX < self.bigImageView.frame.origin.x  || ((self.cropAreaX + self.cropAreaWidth) > self.bigImageView.frame.origin.x + self.bigImageView.frame.size.width)  || self.cropAreaY < self.bigImageView.frame.origin.y  || ((self.cropAreaY + self.cropAreaHeight) > self.bigImageView.frame.origin.y + self.bigImageView.frame.size.height)) {  view.frame = self.originalFrame; }

這樣就實現了縮放功能。

移動功能

相比于縮放,移動功能實現就簡單了,只需要在 cropview 上添加 UIPanGestureRecognizer,然后在回調方法里拿到需要移動的距離,修改 UIImageView 的 center 就可以了。

 CGPoint translation = [panGesture translationInView:view.superview]; [view setCenter:CGPointMake(view.center.x + translation.x, view.center.y + translation.y)];  [panGesture setTranslation:CGPointZero inView:view.superview];

但是同樣為了保證移動后的圖片不會與裁剪區域出現空白甚至是超出裁剪區域,這里更新了圖片位置后,在手勢結束時還要對圖片進行位置修正

  CGRect currentFrame = view.frame;    if (currentFrame.origin.x >= self.cropAreaX) {   currentFrame.origin.x = self.cropAreaX;     }  if (currentFrame.origin.y >= self.cropAreaY) {   currentFrame.origin.y = self.cropAreaY;  }  if (currentFrame.size.width + currentFrame.origin.x < self.cropAreaX + self.cropAreaWidth) {   CGFloat movedLeftX = fabs(currentFrame.size.width + currentFrame.origin.x - (self.cropAreaX + self.cropAreaWidth));   currentFrame.origin.x += movedLeftX;  }  if (currentFrame.size.height + currentFrame.origin.y < self.cropAreaY + self.cropAreaHeight) {   CGFloat moveUpY = fabs(currentFrame.size.height + currentFrame.origin.y - (self.cropAreaY + self.cropAreaHeight));   currentFrame.origin.y += moveUpY;  }  [UIView animateWithDuration:0.3 animations:^{      [view setFrame:currentFrame];  }];

可以看到,這里做的位置檢查與縮放時做的檢查是一樣的,只是由于不會改變圖片尺寸所以這里不需要進行尺寸修正。

支持手勢改變裁剪區域

接下來就是動態裁剪圖片的核心內容了,其實原理也很簡單,只要在上面的移動手勢處理函數中,進行一些判斷,決定是移動圖片位置還是改變裁剪區域,也就是自定義的 CAShapeLayer 的繪制方框的尺寸就可以了。

首先我們定義一個枚舉,用來表示當前應當操作的是圖片還是裁剪區域的邊線

typedef NS_ENUM(NSInteger, ACTIVEGESTUREVIEW) { CROPVIEWLEFT, CROPVIEWRIGHT, CROPVIEWTOP, CROPVIEWBOTTOM, BIGIMAGEVIEW};

它們分別表示觸發對象為裁剪區域左邊線、右邊線、上邊線、下邊線以及 UIImageView

然后我們定義一個枚舉屬性

@property(assign, nonatomic) ACTIVEGESTUREVIEW activeGestureView;

判斷操作對象的標準是當前的手勢所觸發的位置是在邊線上還是在非邊線上,因此我們需要知道手勢觸發時的坐標,要知道這一點就需要我們繼承一個 UIPanGestureRecognizer 并覆寫一些方法

@interface YasicPanGestureRecognizer : UIPanGestureRecognizer@property(assign, nonatomic) CGPoint beginPoint;@property(assign, nonatomic) CGPoint movePoint;-(instancetype)initWithTarget:(id)target action:(SEL)action inview:(UIView*)view;@end@interface YasicPanGestureRecognizer()@property(strong, nonatomic) UIView *targetView;@end@implementation YasicPanGestureRecognizer-(instancetype)initWithTarget:(id)target action:(SEL)action inview:(UIView*)view{  self = [super initWithTarget:target action:action]; if(self) { self.targetView = view; } return self;}- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event{  [super touchesBegan:touches withEvent:event]; UITouch *touch = [touches anyObject]; self.beginPoint = [touch locationInView:self.targetView];}- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [super touchesMoved:touches withEvent:event]; UITouch *touch = [touches anyObject]; self.movePoint = [touch locationInView:self.targetView];}@end

可以看到,我們首先傳入了一個 view,用于將手勢觸發的位置轉換為 view 中的坐標值。在 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event{ 方法中我們得到了手勢開始時的觸發點 beginPoint,在 - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 方法中我們獲得了手勢進行時的觸發點 movePoint。

自定義完 UIPanGestureRecognizer 后我們將其加到 cropview 上并把 cropview 作為參數傳給 UIPanGestureRecognizer

 // 拖動手勢 YasicPanGestureRecognizer *panGesture = [[YasicPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleDynamicPanGesture:) inview:self.cropView]; [self.cropView addGestureRecognizer:panGesture];

接下來就是處理手勢的函數,這里我們可以將整個過程分為三個步驟,開始時 -> 進行時 -> 結束時。

手勢開始時

在這里我們要根據手勢的 beginPoint 判斷觸發對象是邊線還是 UIImageView

// 開始滑動時判斷滑動對象是 ImageView 還是 Layer 上的 Line if (panGesture.state == UIGestureRecognizerStateBegan) { if (beginPoint.x >= self.cropAreaX - judgeWidth && beginPoint.x <= self.cropAreaX + judgeWidth && beginPoint.y >= self.cropAreaY && beginPoint.y <= self.cropAreaY + self.cropAreaHeight && self.cropAreaWidth >= 50) {  self.activeGestureView = CROPVIEWLEFT; } else if (beginPoint.x >= self.cropAreaX + self.cropAreaWidth - judgeWidth && beginPoint.x <= self.cropAreaX + self.cropAreaWidth + judgeWidth && beginPoint.y >= self.cropAreaY && beginPoint.y <= self.cropAreaY + self.cropAreaHeight && self.cropAreaWidth >= 50) {  self.activeGestureView = CROPVIEWRIGHT; } else if (beginPoint.y >= self.cropAreaY - judgeWidth && beginPoint.y <= self.cropAreaY + judgeWidth && beginPoint.x >= self.cropAreaX && beginPoint.x <= self.cropAreaX + self.cropAreaWidth && self.cropAreaHeight >= 50) {  self.activeGestureView = CROPVIEWTOP; } else if (beginPoint.y >= self.cropAreaY + self.cropAreaHeight - judgeWidth && beginPoint.y <= self.cropAreaY + self.cropAreaHeight + judgeWidth && beginPoint.x >= self.cropAreaX && beginPoint.x <= self.cropAreaX + self.cropAreaWidth && self.cropAreaHeight >= 50) {  self.activeGestureView = CROPVIEWBOTTOM; } else {  self.activeGestureView = BIGIMAGEVIEW;  [view setCenter:CGPointMake(view.center.x + translation.x, view.center.y + translation.y)];  [panGesture setTranslation:CGPointZero inView:view.superview]; } }

手勢進行時

在這里,如果觸發對象是邊線,則計算邊線需要移動的距離和方向,以及對于邊界條件的限制以防止邊線之間交叉錯位的情況,具體來說就是獲得坐標差值,更新 cropAreaX、cropAreaWidth 等值,然后更新 CAShapeLayer 上的空心蒙層。

如果觸發對象是 UIImageView 則只需要將其位置進行改變即可。

// 滑動過程中進行位置改變 if (panGesture.state == UIGestureRecognizerStateChanged) { CGFloat diff = 0; switch (self.activeGestureView) {  case CROPVIEWLEFT: {  diff = movePoint.x - self.cropAreaX;  if (diff >= 0 && self.cropAreaWidth > 50) {   self.cropAreaWidth -= diff;   self.cropAreaX += diff;  } else if (diff < 0 && self.cropAreaX > self.bigImageView.frame.origin.x && self.cropAreaX >= 15) {   self.cropAreaWidth -= diff;   self.cropAreaX += diff;  }  [self setUpCropLayer];  break;  }  case CROPVIEWRIGHT: {  diff = movePoint.x - self.cropAreaX - self.cropAreaWidth;  if (diff >= 0 && (self.cropAreaX + self.cropAreaWidth) < MIN(self.bigImageView.frame.origin.x + self.bigImageView.frame.size.width, self.cropView.frame.origin.x + self.cropView.frame.size.width - 15)){   self.cropAreaWidth += diff;  } else if (diff < 0 && self.cropAreaWidth >= 50) {   self.cropAreaWidth += diff;  }  [self setUpCropLayer];  break;  }  case CROPVIEWTOP: {  diff = movePoint.y - self.cropAreaY;  if (diff >= 0 && self.cropAreaHeight > 50) {   self.cropAreaHeight -= diff;   self.cropAreaY += diff;  } else if (diff < 0 && self.cropAreaY > self.bigImageView.frame.origin.y && self.cropAreaY >= 15) {   self.cropAreaHeight -= diff;   self.cropAreaY += diff;  }  [self setUpCropLayer];  break;  }  case CROPVIEWBOTTOM: {  diff = movePoint.y - self.cropAreaY - self.cropAreaHeight;  if (diff >= 0 && (self.cropAreaY + self.cropAreaHeight) < MIN(self.bigImageView.frame.origin.y + self.bigImageView.frame.size.height, self.cropView.frame.origin.y + self.cropView.frame.size.height - 15)){   self.cropAreaHeight += diff;  } else if (diff < 0 && self.cropAreaHeight >= 50) {   self.cropAreaHeight += diff;  }  [self setUpCropLayer];  break;  }  case BIGIMAGEVIEW: {  [view setCenter:CGPointMake(view.center.x + translation.x, view.center.y + translation.y)];  [panGesture setTranslation:CGPointZero inView:view.superview];  break;  }  default:  break; } }

手勢結束時

手勢結束時,我們需要對位置進行修正。如果是裁剪區域邊線,則要判斷左右、上下邊線之間的距離是否過短,邊線是否超出 UIImageView 的范圍等。如果左右邊線距離過短則設置最小裁剪寬度,如果上線邊線距離過短則設置最小裁剪高度,如果左邊線超出了 UIImageView 左邊線則需要緊貼 UIImageView 的左邊線,并更新裁剪區域寬度,以此類推。然后更新 CAShapeLayer 上的空心蒙層即可。

如果是 UIImageView 則跟上一節一樣要保證圖片不會與裁剪區域出現空白。

 // 滑動結束后進行位置修正 if (panGesture.state == UIGestureRecognizerStateEnded) { switch (self.activeGestureView) {  case CROPVIEWLEFT: {  if (self.cropAreaWidth < 50) {   self.cropAreaX -= 50 - self.cropAreaWidth;   self.cropAreaWidth = 50;  }  if (self.cropAreaX < MAX(self.bigImageView.frame.origin.x, 15)) {   CGFloat temp = self.cropAreaX + self.cropAreaWidth;   self.cropAreaX = MAX(self.bigImageView.frame.origin.x, 15);   self.cropAreaWidth = temp - self.cropAreaX;  }  [self setUpCropLayer];  break;  }  case CROPVIEWRIGHT: {  if (self.cropAreaWidth < 50) {   self.cropAreaWidth = 50;  }  if (self.cropAreaX + self.cropAreaWidth > MIN(self.bigImageView.frame.origin.x + self.bigImageView.frame.size.width, self.cropView.frame.origin.x + self.cropView.frame.size.width - 15)) {   self.cropAreaWidth = MIN(self.bigImageView.frame.origin.x + self.bigImageView.frame.size.width, self.cropView.frame.origin.x + self.cropView.frame.size.width - 15) - self.cropAreaX;  }  [self setUpCropLayer];  break;  }  case CROPVIEWTOP: {  if (self.cropAreaHeight < 50) {   self.cropAreaY -= 50 - self.cropAreaHeight;   self.cropAreaHeight = 50;  }  if (self.cropAreaY < MAX(self.bigImageView.frame.origin.y, 15)) {   CGFloat temp = self.cropAreaY + self.cropAreaHeight;   self.cropAreaY = MAX(self.bigImageView.frame.origin.y, 15);   self.cropAreaHeight = temp - self.cropAreaY;  }  [self setUpCropLayer];  break;  }  case CROPVIEWBOTTOM: {  if (self.cropAreaHeight < 50) {   self.cropAreaHeight = 50;  }  if (self.cropAreaY + self.cropAreaHeight > MIN(self.bigImageView.frame.origin.y + self.bigImageView.frame.size.height, self.cropView.frame.origin.y + self.cropView.frame.size.height - 15)) {   self.cropAreaHeight = MIN(self.bigImageView.frame.origin.y + self.bigImageView.frame.size.height, self.cropView.frame.origin.y + self.cropView.frame.size.height - 15) - self.cropAreaY;  }  [self setUpCropLayer];  break;  }  case BIGIMAGEVIEW: {  CGRect currentFrame = view.frame;    if (currentFrame.origin.x >= self.cropAreaX) {   currentFrame.origin.x = self.cropAreaX;     }  if (currentFrame.origin.y >= self.cropAreaY) {   currentFrame.origin.y = self.cropAreaY;  }  if (currentFrame.size.width + currentFrame.origin.x < self.cropAreaX + self.cropAreaWidth) {   CGFloat movedLeftX = fabs(currentFrame.size.width + currentFrame.origin.x - (self.cropAreaX + self.cropAreaWidth));   currentFrame.origin.x += movedLeftX;  }  if (currentFrame.size.height + currentFrame.origin.y < self.cropAreaY + self.cropAreaHeight) {   CGFloat moveUpY = fabs(currentFrame.size.height + currentFrame.origin.y - (self.cropAreaY + self.cropAreaHeight));   currentFrame.origin.y += moveUpY;  }  [UIView animateWithDuration:0.3 animations:^{      [view setFrame:currentFrame];  }];  break;  }  default:  break; } }

進行圖片裁剪并獲得裁剪后的圖片

最后一步就是對圖片進行裁剪了。首先確定對圖片的縮放尺寸 imageScale

 CGFloat imageScale = MIN(self.bigImageView.frame.size.width/self.targetImage.size.width, self.bigImageView.frame.size.height/self.targetImage.size.height);

然后將 cropView 的裁剪區域對應到 UIImageView 上,再除以縮放值,即可得到對應 UIImage 上需要裁剪的區域

 CGFloat cropX = (self.cropAreaX - self.bigImageView.frame.origin.x)/imageScale; CGFloat cropY = (self.cropAreaY - self.bigImageView.frame.origin.y)/imageScale; CGFloat cropWidth = self.cropAreaWidth/imageScale; CGFloat cropHeight = self.cropAreaHeight/imageScale; CGRect cropRect = CGRectMake(cropX, cropY, cropWidth, cropHeight);

最后調用 CoreGraphic 的方法,將圖片對應區域的數據取出來生成新的圖片,就是我們需要的裁剪后的圖片了。

 CGImageRef sourceImageRef = [self.targetImage CGImage]; CGImageRef newImageRef = CGImageCreateWithImageInRect(sourceImageRef, cropRect); UIImage *newImage = [UIImage imageWithCGImage:newImageRef];

源碼下載:

github下載地址:點擊這里

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對VEVB武林網的支持。


注:相關教程知識閱讀請移步到IOS開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美极品少妇xxxxⅹ喷水| 日本不卡高字幕在线2019| 国产精品精品久久久久久| 久久久国产精品免费| 精品高清美女精品国产区| 久久久精品电影| 欧美精品做受xxx性少妇| 欧美日韩国产成人高清视频| 在线播放亚洲激情| 欧美床上激情在线观看| 亚洲最大在线视频| 中文字幕在线看视频国产欧美在线看完整| 日韩欧美中文第一页| 亚洲va男人天堂| 欧美精品免费在线观看| 国产噜噜噜噜久久久久久久久| 亚洲欧美一区二区三区情侣bbw| 国产精品视频内| xxxx欧美18另类的高清| 国产色婷婷国产综合在线理论片a| 成人免费高清完整版在线观看| 91亚洲人电影| 亚洲aⅴ日韩av电影在线观看| 亚洲国产精品热久久| 欧美一区二区三区艳史| 亚洲欧美日韩直播| 国产视频精品在线| 日本91av在线播放| 久久精品久久久久久国产 免费| 97久久精品视频| 久久免费成人精品视频| 日韩一区在线视频| 日韩亚洲精品电影| 日韩精品极品毛片系列视频| 欧美限制级电影在线观看| 国产精品一区二区三区在线播放| 午夜精品一区二区三区在线播放| 综合久久五月天| 国产一区二区三区在线免费观看| 日韩精品久久久久久久玫瑰园| 欧美日韩国产精品一区| 亚洲男人天堂久| 国产精品免费看久久久香蕉| 国产精品夜色7777狼人| 成人免费视频网| 成人动漫网站在线观看| 久久香蕉国产线看观看av| 亚洲一级片在线看| 性金发美女69hd大尺寸| 欧美激情亚洲综合一区| 91精品久久久久| 国产日韩在线一区| 红桃视频成人在线观看| 亚洲免费视频一区二区| 中文字幕一区电影| 亚洲电影免费观看高清完整版| 亚洲国产精品热久久| 国产日韩综合一区二区性色av| 久久久国产精品亚洲一区| 国产成人精品久久久| 日韩精品在线观看一区| 国产精品你懂得| 国产成人福利夜色影视| 日本欧美国产在线| 亚洲第一精品夜夜躁人人躁| 精品中文字幕在线| 91精品国产高清| 欧美裸体xxxxx| 18一19gay欧美视频网站| 亚洲国产精品yw在线观看| 亚洲一区二区三区sesese| 日韩在线一区二区三区免费视频| 一本大道亚洲视频| 日韩视频在线观看免费| 亚洲精品久久久久久久久久久| 日韩一级裸体免费视频| 欧美成人激情图片网| 欧美日韩国产综合新一区| 成人妇女淫片aaaa视频| 国产精品视频精品视频| 亚洲免费视频在线观看| 26uuu亚洲伊人春色| 欧美日韩一区二区三区| 亚洲色图综合网| 美女撒尿一区二区三区| 日韩电视剧在线观看免费网站| 色噜噜狠狠狠综合曰曰曰88av| 国产日本欧美一区二区三区在线| 色狠狠av一区二区三区香蕉蜜桃| 高清一区二区三区四区五区| 亚洲精品一区二区三区不| 国产精品视频一区二区三区四| 久久国产精品网站| 亚洲欧美国产精品| 亚洲性视频网站| 国产精品扒开腿做爽爽爽视频| 亚洲精选中文字幕| 亚洲色图色老头| 国产精品成久久久久三级| 国产精品免费观看在线| 不卡av电影院| 国产日韩欧美在线播放| 亚洲自拍偷拍在线| 2018中文字幕一区二区三区| 欧美色另类天堂2015| 91精品国产高清久久久久久91| 亚洲国产女人aaa毛片在线| 精品美女国产在线| 91超碰中文字幕久久精品| 国内偷自视频区视频综合| 97色伦亚洲国产| 成人午夜一级二级三级| 欧美日韩中国免费专区在线看| 91极品女神在线| 亚洲精品按摩视频| 久久精品99国产精品酒店日本| 日本成人在线视频网址| 欧美成人sm免费视频| 大荫蒂欧美视频另类xxxx| 成人精品一区二区三区电影黑人| 国产日韩欧美在线播放| 高清日韩电视剧大全免费播放在线观看| 国产成人精品一区二区| 久久久久久av| 91精品国产91| 精品久久久久久久久久久久久久| 午夜精品一区二区三区av| 亚洲激情久久久| 一区二区三区四区在线观看视频| 九九热r在线视频精品| 97人洗澡人人免费公开视频碰碰碰| 国产中文日韩欧美| 欧美伊久线香蕉线新在线| 亚洲激情视频在线| 日韩精品视频在线播放| 最近更新的2019中文字幕| 国产精品日韩欧美大师| 国产一区二区三区视频在线观看| 91国偷自产一区二区三区的观看方式| 国产综合色香蕉精品| 丝袜亚洲欧美日韩综合| 成人乱色短篇合集| 国产午夜精品一区二区三区| 亚洲欧美中文在线视频| 国产精品免费久久久久久| 一区二区三区动漫| 中文字幕一精品亚洲无线一区| 欧美日韩国产成人高清视频| 国产精品扒开腿做爽爽爽视频| 亚洲一区二区三区四区视频| 中文字幕日韩视频| 热99久久精品| 亚洲永久在线观看| 懂色aⅴ精品一区二区三区蜜月| 欧美性视频精品| 欧美午夜丰满在线18影院| 久久资源免费视频| 亚洲天堂成人在线| 久久久久久久久亚洲| 国产裸体写真av一区二区| 亚洲午夜国产成人av电影男同| 91亚洲精品一区| 亚洲最大的成人网| 中文字幕少妇一区二区三区|