美文网首页ios
iOS 头像裁剪、图片裁剪、微信头像裁剪

iOS 头像裁剪、图片裁剪、微信头像裁剪

作者: Vesincc | 来源:发表于2019-04-18 10:22 被阅读19次

iOS 头像裁剪、图片裁剪、微信头像裁剪

关于图片裁剪,基本上所有涉及到c端用户带基本信息的App基本都会用到,使用频率相对较高。而系统提供的方法只能裁剪出一个正方形,而且经常会出现裁剪框偏移的bug、在X系屏幕上显示效果也不是很好。本文仿微信头像裁剪并添加了矩形裁剪框,供大家参考。

先看一下效果:

正方形裁剪框
正方形裁剪框

矩形裁剪框

矩形裁剪框

实现

列举一下具体功能:

  • 图片预览
  • 捏合手势
  • 图片旋转
  • 自定义编辑框(中间白色选框)
  • 编辑框放大操作
  • 图片裁剪

简单说一下实现思路
虽然看起来东西比较少,但是实现的时候还是遇到了挺多坑的,特别是在旋转的时候遇到的问题比较多,改了2次方案最终实现。

  1. 最早pass的方案,在UIScrollView中放UIImageView,调整UIImageViewUIScrollViewframe.origin到屏幕中间, 每次旋转直接作用在UIImageView上,旋转完成后重新调整UIImageViewUIScrollView中的位置。

    遇到问题:每次旋转需要改变UIImageView的锚地,计算十分困难。!进行缩放手势后通过CGAffineTransform旋转要在缩放基础上进行,操作较为繁琐。最后放弃。

  2. 既然旋转UIImageView不行,那就直接旋转UIScrollView

    遇到问题:一开始UIScrollView是称满整个屏幕的,每次旋转后需要修改UIScrollViewframe, 并且每次旋转后需要重置contentSizecontentOffset

  3. 最后定下来的方案: UIScrollView只有裁剪框大小,放置于屏幕中间,每次旋转不必重定锚点,也不用重新修改frame不用重置contentSizecontentOffset,大大减少了计算量.

View

  • ImageEditViewController: vc
  • ImageActionView: 底部操作栏
  • ImageCaptureView: 负责截图的View
  • ImageEditView: 负责定位线以及定位框拉伸的View

视图结构


矩形裁剪框
矩形裁剪框

缩放

- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
    [self.editView maskViewHideWithDuration:.2f];
}

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return self.imageView;
}

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
    [self.scrollView setZoomScale:scale animated:NO];
    [self.editView maskViewShowWithDuration:.2f];
}

旋转

[UIView animateWithDuration:.3f animations:^{
        self.scrollView.transform = CGAffineTransformRotate(self.scrollView.transform, -M_PI_2);
    } completion:^(BOOL finished) {
        if (self.editViewSize.width != self.editViewSize.height) {
        // 矩形旋转后位置调整
            [self.scrollView setZoomScale:1.f animated:YES];
            [self.scrollView setContentOffset:CGPointMake(0, 0) animated:YES];
            [UIView animateWithDuration:.3f animations:^{
                self.scrollView.frame = CGRectMake(0, 0, self.editViewSize.width, self.editViewSize.height);
                if (times % 2 == 1) {
                    if (self.editViewSize.width * (self.imageViewOriginSize.width/self.imageViewOriginSize.height) >= self.editViewSize.height) {
                        self.imageView.frame = CGRectMake(0, 0, self.editViewSize.width * (self.imageViewOriginSize.width/self.imageViewOriginSize.height), self.editViewSize.width);  //宽拉满
                    } else {
                        self.imageView.frame = CGRectMake(0, 0, self.editViewSize.height, self.editViewSize.height * (self.imageViewOriginSize.height/self.imageViewOriginSize.width)); //高拉满
                    }
                } else {
                    self.imageView.frame = CGRectMake(0, 0, self.imageViewOriginSize.width, self.imageViewOriginSize.height);
                }
            } completion:^(BOOL finished) {
                self.scrollView.contentSize = self.imageView.frame.size;
            }];
        }
    }];

图片裁剪

提供了两个图片裁剪方法, 一个是直接截取View, 一个是按照比例截取对应原图上的裁剪区域

/**
 截取View获取图片

 @return View中的图片
 */
- (UIImage *)captureImage;


/**
 截取框对应原图片

 @return 原图片
 */
- (UIImage *)captureOriginalImage;

裁剪框需要对图片裁剪,这里有两个弱引用是由ViewController传过来的

@property (nonatomic, weak) UIImageView *imageView;
@property (nonatomic, weak) UIView *captureView;

对裁剪框的hitTest进行重写, 保证整块屏幕的手势都全部传到UIScrollView

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *view = [super hitTest:point withEvent:event];
    if (view) {
        return view;
    } else {
        return self.captureView;
    }
}

设置不按Bounds裁剪

- (instancetype)init {
    if (self = [super init]) {
        self.layer.masksToBounds = NO;
    }
    return self;
}

图片编辑框

  • 蒙层 : 除选框外的黑色蒙层
  • 辅助线 : 定位线
  • 拉动块
  • 拉动手势
  • Delegate : 完成拉动后将拉动事件传递出去,让UIScrollView进行放大动画

具体说一下拉动手势的实现
因为偷懒直接放了四张图片展示编辑框四个角,直接重写hiteTest让编辑框处理手势。这里只返回接受事件的View

#pragma mark - private method
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (self.imageWrap.hidden == NO && self.isMoving == NO) {
        for (UIView *view in self.imageWrap.subviews) {
            CGRect rect = [view convertRect:view.bounds toView:self];
            if (CGRectContainsPoint(rect, point)) {
                return view;
            }
        }
    }
    return nil;
}

这里涉及到一个坐标系的转换的两个相关函数, 简单解释一下相关含义

- (CGRect)convertRect:(CGRect)rect toView:(nullable UIView *)view;
//[v convertRect:rect toView:view];
// v上的rect区域 转换到view上的rect区域

- (CGRect)convertRect:(CGRect)rect fromView:(nullable UIView *)view;
// [v convertRect:rect fromView:view];
//  view上的rect区域 转换到v上的rect区域

相应的point转换也是同样的意思

拖动手势的处理, 这里只列举左上角处理,更多可以看源码

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    CGPoint current = [touches.anyObject locationInView:self];
    CGPoint pre = [touches.anyObject previousLocationInView:self];
    
    if (touches.anyObject.view.tag == 0) { // 左上
        CGPoint move = CGPointMake(current.x - pre.x, current.y - pre.y);
        CGFloat moveFit = fabs(move.x) > fabs(move.y) ? move.x : move.y;
        
        CGFloat x = (self.lineWrap.frame.origin.x + moveFit) >= 0 ? (self.lineWrap.frame.origin.x + moveFit) : 0;
        if (x >= self.previewSize.width/3.f) {
            x = self.previewSize.width/3.f;
        }
        CGFloat y = (self.previewSize.height/self.previewSize.width) * x;
        self.lineWrap.frame = CGRectMake(x, y, self.previewSize.width - x, self.previewSize.height - y);
    }
}

拖动手势结束后,编辑框放大,并把事件传递给vc 处理图片放大

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    if (!self.isMoving) {
        return;
    }
    
    CGRect orignalFrame = self.lineWrap.frame;

    self.imageWrap.hidden = YES;
    if (touches.anyObject.view.tag == 0) { // 左上
        self.lineWrap.layer.anchorPoint = CGPointMake(1, 1);
    } else if (touches.anyObject.view.tag == 1) { // 右上
        self.lineWrap.layer.anchorPoint = CGPointMake(0, 1);
    } else if (touches.anyObject.view.tag == 2) { // 左下
        self.lineWrap.layer.anchorPoint = CGPointMake(1, 0);
    } else if (touches.anyObject.view.tag == 3) { // 右下
        self.lineWrap.layer.anchorPoint = CGPointMake(0, 0);
    }
    self.lineWrap.frame = orignalFrame;
    if ([self.delegate respondsToSelector:@selector(editView:anchorPointIndex:rect:)]) {
        [self.delegate editView:self anchorPointIndex:touches.anyObject.view.tag rect:self.lineWrap.frame];
    }
    
    
    [UIView animateWithDuration:.2f animations:^{
        self.lineWrap.transform = CGAffineTransformMakeScale(self.previewSize.width/self.lineWrap.frame.size.width, self.previewSize.height/self.lineWrap.frame.size.height);
    } completion:^(BOOL finished) {
        self.isMoving = NO;
        self.imageWrap.hidden = NO;
        self.lineWrap.layer.anchorPoint = CGPointMake(.5f, .5f);
        self.lineWrap.transform = CGAffineTransformIdentity;
        self.lineWrap.frame = CGRectMake(0, 0, self.previewSize.width, self.previewSize.height);
    }];
}

UIScrollView提供区域放大函数 转换一下坐标系直接调用就可以了

- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated NS_AVAILABLE_IOS(3_0);


#pragma mark - HQEditImageEditViewDelegate
- (void)editView:(HQEditImageEditView *)editView anchorPointIndex:(NSInteger)anchorPointIndex rect:(CGRect)rect {
    CGRect imageEditRect = [self.captureView convertRect:rect toView:self.imageView];
    [self.scrollView zoomToRect:imageEditRect animated:YES];
}

源码

源码放在github 有兴趣的可以下载看下,希望大佬多多点星。
HQImageEditViewController

相关文章

网友评论

    本文标题:iOS 头像裁剪、图片裁剪、微信头像裁剪

    本文链接:https://www.haomeiwen.com/subject/crkzwqtx.html