STAR -- 简洁的图片裁切


尝试用STAR法则写一篇小白Demo,关于自定义图片切割功能

固定裁切框大小,图片可拖动缩放,精准裁切.

源码在最后


STAR法则是情境(situation)、任务(task)、行动(action)、结果(result)

情境(situation)

需要一个裁切固定尺寸图片的功能,类似这样

任务(task)

封装一个View,需要以下功能

  • 可以接受选择的图片显示

  • 图片编辑完成后给出图片的裁切范围

  • 图片可以放大,缩小,拖动,且活动范围在给定的裁切框范围内

行动(action)

从任务预期来看
首先需要给出一个参数 image 用来接收外界传过来的参数

在image做完交互后传出对应到原始image 的Frame,切割用

因为图片需要有放大缩小拖动的交互,所以自然想到可以把图片放到 UIScrollView 容器内,为了方便说明,整个裁切的结构层级如下

  • 1 是用UIScrollView做容器
  • 2 是添加一个UIImageView用来展示图片
  • 3 是一个UIView,作为一个遮罩
  • 4 是一个自定义的UIView,裁切框,这个Frame很关键


  • 比较关键的一些点
1
2
3
4
5
-(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.backgroundContainerView;
//这里 backgroundContainerView 是imageView的容器,实现这个代理保证图片的放大拖动交互
}
1
2
3
4
5
6
7
self.scrollView.contentInset = (UIEdgeInsets){CGRectGetMinY(contentRect),
CGRectGetMinX(contentRect),
CGRectGetMaxY(self.bounds) - CGRectGetMaxY(contentRect),
CGRectGetMaxX(self.bounds) - CGRectGetMaxX(contentRect)};
contentRect 是裁切框Frame
这里的偏移量 scrollView.contentInset 用来保证图片不滑出 裁切框外
1
2
3
4
5
6
self.scrollView.minimumZoomScale = scale;
self.scrollView.maximumZoomScale = 15.0f;
self.scrollView.zoomScale = self.scrollView.minimumZoomScale;
这里设置 scrollView 最大和最小缩放范围,这里的scale获取以屏幕宽为主
比如. 一个原始大小为 750*1330 的图片,scale 为 375/750
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-(CAShapeLayer *)shaperLayer
{
if (!_shaperLayer) {
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
[path appendPath:[[UIBezierPath bezierPathWithRoundedRect:self.cropBoxFrame cornerRadius:1] bezierPathByReversingPath]];
_shaperLayer = [CAShapeLayer layer];
_shaperLayer.path = path.CGPath;
}
return _shaperLayer;
}
cropBoxFrame 是裁切框的Frame,这里返回的是遮罩的镂空层,添加到遮罩层上
  • 获取到交互后的图片 对应到 原始图片坐标点和大小
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (CGRect)croppedImageFrame
{
CGSize imageSize = self.imageSize;
CGSize contentSize = self.scrollView.contentSize;
CGRect cropBoxFrame = self.cropBoxFrame;
CGPoint contentOffset = self.scrollView.contentOffset;
UIEdgeInsets edgeInsets = self.scrollView.contentInset;
CGRect frame = CGRectZero;
frame.origin.x = floorf((contentOffset.x + edgeInsets.left) * (imageSize.width / contentSize.width));
frame.origin.x = MAX(0, frame.origin.x);
frame.origin.y = floorf((contentOffset.y + edgeInsets.top) * (imageSize.height / contentSize.height));
frame.origin.y = MAX(0, frame.origin.y);
frame.size.width = ceilf(cropBoxFrame.size.width * (imageSize.width / contentSize.width));
frame.size.width = MIN(imageSize.width, frame.size.width);
frame.size.height = ceilf(cropBoxFrame.size.height * (imageSize.height / contentSize.height));
frame.size.height = MIN(imageSize.height, frame.size.height);
return frame;
}
要知道图片的原始尺寸 与 scrollView.contentSize 的比例,
然后通过 contentOffset 算出 在对应比例下图片的移动位置,得到要切割的起始坐标

结果(result)

  • 使用
1
2
3
4
5
6
7
8
PPMainCropVC *vc = [[PPMainCropVC alloc]initWithImage:[UIImage imageNamed:@"1.png"]];
vc.cropBlock = ^(UIImage *image) {
[self.mainImageView setImage:image];
};
[self.navigationController pushViewController:vc animated:YES];
  • 源码在这,具体效果请运行Demo

  • 在裁切页面隐藏了导航栏和状态栏,如果图片拖动与裁切框有偏移,看看Info.plist 中 , 设置 View controller-based status bar appearance 为NO,该参数决定我们项目状态栏的显隐藏是否以各控制器的设置为准。