iOS 头像裁剪、图片裁剪、微信头像裁剪
iOS 头像裁剪、图片裁剪、微信头像裁剪
关于图片裁剪,基本上所有涉及到c端用户带基本信息的App基本都会用到,使用频率相对较高。而系统提供的方法只能裁剪出一个正方形,而且经常会出现裁剪框偏移的bug、在X系屏幕上显示效果也不是很好。本文仿微信头像裁剪并添加了矩形裁剪框,供大家参考。
先看一下效果:
正方形裁剪框
正方形裁剪框矩形裁剪框
矩形裁剪框实现
列举一下具体功能:
- 图片预览
- 捏合手势
- 图片旋转
- 自定义编辑框(中间白色选框)
- 编辑框放大操作
- 图片裁剪
简单说一下实现思路
虽然看起来东西比较少,但是实现的时候还是遇到了挺多坑的,特别是在旋转的时候遇到的问题比较多,改了2次方案最终实现。
-
最早pass的方案,在
UIScrollView
中放UIImageView
,调整UIImageView
在UIScrollView
的frame.origin
到屏幕中间, 每次旋转直接作用在UIImageView
上,旋转完成后重新调整UIImageView
在UIScrollView
中的位置。遇到问题:每次旋转需要改变
UIImageView
的锚地,计算十分困难。!进行缩放手势后通过CGAffineTransform
旋转要在缩放基础上进行,操作较为繁琐。最后放弃。 -
既然旋转
UIImageView
不行,那就直接旋转UIScrollView
。遇到问题:一开始
UIScrollView
是称满整个屏幕的,每次旋转后需要修改UIScrollView
的frame
, 并且每次旋转后需要重置contentSize
和contentOffset
。 -
最后定下来的方案:
UIScrollView
只有裁剪框大小,放置于屏幕中间,每次旋转不必重定锚点,也不用重新修改frame
不用重置contentSize
和contentOffset
,大大减少了计算量.
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