demo:https://download.csdn.net/download/u012881779/10744408
之前做电商平台的图片搜索时使用自定义相机和图片剪切写的一个demo,类似于京东和淘宝的图片搜索功能。
示意图:
自定义相机:
#import <UIKit/UIKit.h>
#import "ResultViewController.h"
typedef void(^customSelectImageViewBlock)(UIImage *selectImage);
@interface GACustomCameraViewController : UIViewController
@property (nonatomic, strong) customSelectImageViewBlock selectImage;
@end
#import "GACustomCameraViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <AssetsLibrary/AssetsLibrary.h>
@interface GACustomCameraViewController () <UINavigationControllerDelegate, UIImagePickerControllerDelegate>
@property (weak, nonatomic) IBOutlet UIButton *theFlashBut;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *statusHighConstraint;
@property (strong, nonatomic) AVCaptureSession *session;
@property (strong, nonatomic) AVCaptureVideoPreviewLayer *previewLayer;
@property (strong, nonatomic) AVCaptureDevice *device;
@property (strong, nonatomic) AVCaptureDeviceInput *deviceInput;
@property (strong, nonatomic) AVCaptureStillImageOutput *imageOutput;
@property (strong, nonatomic) AVCaptureConnection *connection;
@end
@implementation GACustomCameraViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 判断相机 是否可以使用
if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
NSLog(@"sorry, no camera or camera is unavailable.");
return;
}
// 设置相机
[self initCameraSettings];
// 当前闪光灯状态
if (self.device.flashMode == AVCaptureFlashModeOff) {
_theFlashBut.selected = NO;
} else {
_theFlashBut.selected = YES;
}
// 状态栏高度
_statusHighConstraint.constant = [[UIApplication sharedApplication] statusBarFrame].size.height;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:YES];
if (self.session) {
[self.session startRunning];
}
[self shatusViewHidden:NO];
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:YES];
if (self.session) {
[self.session stopRunning];
}
// [self shatusViewHidden:NO];
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDefault;
}
// 状态栏显示/隐藏
- (void)shatusViewHidden:(BOOL)result {
UIView *statusView = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];
statusView.hidden = result;
}
// 初始化相机
- (void)initCameraSettings {
NSError *error;
// 创建会话层
self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
// 初始化session
self.session = [[AVCaptureSession alloc] init];
if ([self.session canSetSessionPreset:AVCaptureSessionPresetPhoto]) {
self.session.sessionPreset = AVCaptureSessionPresetPhoto;
}
// 初始化输入设备
self.deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:&error];
// 初始化照片输出对象
self.imageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:AVVideoCodecJPEG,AVVideoCodecKey, nil];
[self.imageOutput setOutputSettings:outputSettings];
// 判断输入输出设备是否可用
if ([self.session canAddInput:self.deviceInput]) {
[self.session addInput:self.deviceInput];
}
if ([self.session canAddOutput:self.imageOutput]) {
[self.session addOutput:self.imageOutput];
}
// 初始化预览图层
self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
/** 设置图层的填充样式
AVLayerVideoGravityResize, // 非均匀模式。两个维度完全填充至整个视图区域
AVLayerVideoGravityResizeAspect, // 等比例填充,直到一个维度到达区域边界
AVLayerVideoGravityResizeAspectFill, // 等比例填充,直到填充满整个视图区域,其中一个维度的部分区域会被裁剪
*/
self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
self.previewLayer.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
[self.view.layer insertSublayer:self.previewLayer atIndex:0];
}
// 返回
- (IBAction)returnAction:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)returnWithImage:(UIImage *)image {
/*
// 在首页跳转
[self returnAction:nil];
if (self.selectImage) {
self.selectImage(image);
}*/
// 在自定义相机跳转
[self entryRsultVCWithImage:image];
}
- (void)entryRsultVCWithImage:(UIImage *)image {
ResultViewController *result = [[ResultViewController alloc] initWithNibName:@"ResultViewController" bundle:nil];
result.originalImage = image;
[self presentViewController:result animated:YES completion:nil];
}
// 闪光灯
- (IBAction)theFlashAction:(id)sender {
AVCaptureFlashMode mode;
UIButton *tempBut = (UIButton *)sender;
if (tempBut.isSelected) {
tempBut.selected = NO;
mode = AVCaptureFlashModeOff;
} else {
tempBut.selected = YES;
mode = AVCaptureFlashModeOn;
}
if ([self.device isFlashModeSupported:mode]) {
[self.session beginConfiguration];
[self.device lockForConfiguration:nil];
[self.device setFlashMode:mode]; // 这行代码就要放在这个顺序的位置否则会崩溃
[self.device unlockForConfiguration];
[self.session commitConfiguration];
[self.session startRunning];
}
}
// 拍照
- (IBAction)takePhotoAction:(id)sender {
self.connection = [self.imageOutput connectionWithMediaType:AVMediaTypeVideo];
[self.connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
[self.imageOutput captureStillImageAsynchronouslyFromConnection:self.connection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
NSData *jpegData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
UIImage *image = [UIImage imageWithData:jpegData];
[self returnWithImage:image];
}];
}
// 前置/后置摄像头
- (IBAction)frontOrRearCameraAction:(id)sender {
[UIView beginAnimations:@"animation" context:nil];
[UIView setAnimationDuration:.5f];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:YES];
[UIView commitAnimations];
NSArray *inputs = self.session.inputs;
for ( AVCaptureDeviceInput *input in inputs ) {
AVCaptureDevice *device = input.device;
if ( [device hasMediaType:AVMediaTypeVideo] ) {
AVCaptureDevicePosition position = device.position;
AVCaptureDevice *newCamera = nil;
AVCaptureDeviceInput *newInput = nil;
if (position == AVCaptureDevicePositionFront) {
newCamera = [self cameraWithPosition:AVCaptureDevicePositionBack];
} else {
newCamera = [self cameraWithPosition:AVCaptureDevicePositionFront];
}
newInput = [AVCaptureDeviceInput deviceInputWithDevice:newCamera error:nil];
[self.session beginConfiguration];
[self.session removeInput:input];
[self.session addInput:newInput];
[self.session commitConfiguration];
break;
}
}
}
// 相机状态
- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position {
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for ( AVCaptureDevice *device in devices ) {
if ( device.position == position ) {
return device;
}
}
return nil;
}
// 打开相册
- (IBAction)openAlbumAction:(id)sender {
UIImagePickerController *pickerController = [[UIImagePickerController alloc] init];
pickerController.delegate = self;
pickerController.navigationBar.translucent = NO;
pickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
pickerController.edgesForExtendedLayout = UIRectEdgeNone;
[self presentViewController:pickerController animated:YES completion:nil];
}
#pragma mark - 从相册选择图片后操作
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
// 获取拍照的图像
// UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
// 获取图片裁剪的图
// UIImage *edit = [info objectForKey:UIImagePickerControllerEditedImage];
// 获取照片的原图
UIImage *original = [info objectForKey:UIImagePickerControllerOriginalImage];
[picker dismissViewControllerAnimated:NO completion:^{
[self returnWithImage:original];
}];
}
#pragma mark - 取消操作时调用
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
[picker dismissViewControllerAnimated:YES completion:nil];
}
@end
结果展示页:
#import <UIKit/UIKit.h>
@interface ResultViewController : UIViewController
@property (strong, nonatomic) UIImage *originalImage;
@end
#import "ResultViewController.h"
#import "YBPictureCutterView.h"
static float const LIMIT_SV_BOTTOM = 120.0; // scrollView底部最大移动范围
static float const LIMIT_DRAG_DISTANCE = 44.0; // 拖动超过响应范围后自动滚动到约束位置
static float const LIMIT_CO_TOP = 100.0; // 约束内容视图顶部
static float const LIMIT_CO_BOTTOM = 200.0; // 约束图片视图底部
@interface ResultViewController () <YBPictureCutterViewDataSource, YBPictureCutterViewDelegate, UIGestureRecognizerDelegate>
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
@property (weak, nonatomic) IBOutlet UIView *contentTitleBGView;
@property (weak, nonatomic) IBOutlet UIImageView *tempShowIV;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *ivBottomConstraint; // 图片区域的bottom约束
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *contentTopConstraint; // 内容区域的top约束
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *scrollViewBottomConstraint; // 图片区域sv的bottom约束
@property (strong, nonatomic) YBPictureCutterView *cutterView;
@property (strong, nonatomic) UIImageView *showImageView;
@property (assign, nonatomic) CGPoint startPoint; // 开始拖动时的起始点
@property (assign, nonatomic) CGFloat startTopY; // 开始拖动时的内容区域top约束
@property (assign, nonatomic) CGFloat startSCBottomY; // 开始拖动时图片区域Bottom约束
@property (assign, nonatomic) CGFloat startSCOffSetY; // 开始拖动时图片区域offset.y
@end
@implementation ResultViewController
- (void)viewDidLoad {
[super viewDidLoad];
_contentTopConstraint.constant = LIMIT_CO_TOP;
_ivBottomConstraint.constant = LIMIT_CO_BOTTOM;
_scrollViewBottomConstraint.constant = LIMIT_SV_BOTTOM;
// UIView部分倒圆角
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:_contentTitleBGView.bounds byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight cornerRadii:CGSizeMake(10, 10)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = _contentTitleBGView.bounds;
maskLayer.path = maskPath.CGPath;
_contentTitleBGView.layer.mask = maskLayer;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self updateScrollViewWithDev:0];
[_scrollView addSubview:self.showImageView];
self.showImageView.image = _originalImage;
[self.showImageView addSubview:self.cutterView];
self.showImageView.userInteractionEnabled = YES;
[self adaptationSafeAreaWith:_scrollView useArea:1];
}
- (void)updateScrollViewWithDev:(float)dev {
float hbw = _originalImage.size.height/(float)_originalImage.size.width;
float newWidth = [UIScreen mainScreen].bounds.size.width;
float newHeight = newWidth*hbw;
float devY = 0;
BOOL result = NO;
if (_scrollView.frame.size.height > newHeight) {
devY = (_scrollView.frame.size.height - newHeight)/2.0;
} else {
result = YES;
}
[_scrollView setContentSize:CGSizeMake(newWidth, newHeight)];
self.showImageView.frame = CGRectMake(0, devY, newWidth, newHeight);
if (result) {
CGPoint oldOffset = _scrollView.contentOffset;
oldOffset.y = _startSCOffSetY - dev;
CGFloat jgHeight = _scrollView.contentSize.height-_scrollView.frame.size.height;
if (oldOffset.y < 0) {
oldOffset.y = 0;
} else if (oldOffset.y > jgHeight) {
oldOffset.y = jgHeight;
}
[_scrollView setContentOffset:oldOffset];
}
}
- (UIImageView *)showImageView {
if (!_showImageView) {
_showImageView = [UIImageView new];
[_showImageView setContentMode:UIViewContentModeScaleToFill];
}
return _showImageView;
}
- (YBPictureCutterView *)cutterView {
if (!_cutterView) {
_cutterView = [[YBPictureCutterView alloc] init];
_cutterView.dataSource = self;
_cutterView.delegate = self;
}
_cutterView.frame = _showImageView.bounds;
return _cutterView;
}
#pragma mark - pictureCutterView dataSource & delegate
- (UIImage *)imageForPictureCutterView:(YBPictureCutterView *)cutterView {
return _showImageView.image;
}
- (void)pictureCutterView:(YBPictureCutterView *)cutterView didClippedImage:(UIImage *)image {
NSLog(@"%@", NSStringFromCGSize(image.size));
_tempShowIV.image = image;
}
- (IBAction)returnAction:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
/**
* 适配iPhone X的安全区域
* isUse = 1 表示使用安全区域
* isUse = 0 表示不使用安全区域
*/
- (void)adaptationSafeAreaWith:(UIScrollView *)sv useArea:(NSInteger)isUse {
#ifdef __IPHONE_11_0
if ([sv respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
if (isUse) {
if (@available(iOS 11.0, *)) {
sv.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
if ([[sv class] isSubclassOfClass:[UITableView class]]) {
UITableView *tv = (UITableView *)sv;
[tv setInsetsContentViewsToSafeArea:NO];
}
} else {
// Fallback on earlier versions
}
} else {
if (@available(iOS 11.0, *)) {
sv.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentAlways;
} else {
// Fallback on earlier versions
}
}
}
#endif
}
#pragma mark - PanGestureRecognizer手势
- (IBAction)contentViewPanGesture:(UIPanGestureRecognizer *)gestureRecognizer {
UIView *tempView = gestureRecognizer.view;
if (tempView.tag == 567) {
CGPoint tempPoint = [gestureRecognizer locationInView:self.view];
// 图片ScrollView底部约束移动范围
CGFloat scMaxMoveRange = LIMIT_SV_BOTTOM;
// 图片ScrollView移动范围/内容View移动范围
CGFloat bl = scMaxMoveRange/(float)([UIScreen mainScreen].bounds.size.height - LIMIT_CO_TOP - LIMIT_CO_BOTTOM);
CGFloat scrHeight = [UIScreen mainScreen].bounds.size.height;
CGFloat devY = tempPoint.y-_startPoint.y;
CGFloat newY = _startTopY + devY;
if (newY >= LIMIT_CO_TOP) {
if (newY <= (scrHeight - LIMIT_CO_BOTTOM)) {
_contentTopConstraint.constant = newY;
_scrollViewBottomConstraint.constant = _startSCBottomY - bl*devY;
[self updateScrollViewWithDev:bl*devY];
} else {
[self toBottomLocationWithConstant:0 Dev:0];
return;
}
} else {
[self toTopLocationWithConstant:scMaxMoveRange Dev:0];
return;
}
// 判断是否拖动结束
if (gestureRecognizer.state == UIGestureRecognizerStateEnded || gestureRecognizer.state == UIGestureRecognizerStateCancelled) {
CGFloat responseRange = LIMIT_DRAG_DISTANCE;
BOOL result = NO;
if (devY > 0) {
if (devY >= responseRange) {
[self toBottomLocationWithConstant:0 Dev:bl*devY];
} else {
result = YES;
}
} else if (devY < 0) {
if (devY <= (0 - responseRange)) {
[self toTopLocationWithConstant:scMaxMoveRange Dev:bl*devY];
} else {
result = YES;
}
}
if (result) {
[self toOldLocationWithConstant:self.startSCBottomY Dev:bl*devY];
}
}
}
}
// 滚动到底部位置
- (void)toBottomLocationWithConstant:(float)constant Dev:(float)dev {
self.scrollViewBottomConstraint.constant = constant;
[self.view layoutIfNeeded];
[UIView animateWithDuration:0.3 animations:^{
self.contentTopConstraint.constant = [UIScreen mainScreen].bounds.size.height - LIMIT_CO_BOTTOM;
[self updateScrollViewWithDev:dev];
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
self.collectionView.scrollEnabled = NO;
}];
}
// 滚动到顶部位置
- (void)toTopLocationWithConstant:(float)constant Dev:(float)dev {
self.scrollViewBottomConstraint.constant = constant;
[self.view layoutIfNeeded];
[UIView animateWithDuration:0.3 animations:^{
self.contentTopConstraint.constant = LIMIT_CO_TOP;
[self updateScrollViewWithDev:dev];
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
self.collectionView.scrollEnabled = YES;
}];
}
// 滚动回到原位置
- (void)toOldLocationWithConstant:(float)constant Dev:(float)dev {
self.scrollViewBottomConstraint.constant = constant;
[self.view layoutIfNeeded];
[UIView animateWithDuration:0.3 animations:^{
self.contentTopConstraint.constant = self.startTopY;
[self updateScrollViewWithDev:dev];
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
if (self.startTopY == LIMIT_CO_TOP) {
self.collectionView.scrollEnabled = YES;
}
}];
}
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
UIView *tempView = gestureRecognizer.view;
if (tempView.tag == 567) {
_startPoint = [gestureRecognizer locationInView:self.view];
_startSCBottomY = _scrollViewBottomConstraint.constant;
_startSCOffSetY = _scrollView.contentOffset.y;
_startTopY = _contentTopConstraint.constant;
CGFloat tempLimitBottom = [UIScreen mainScreen].bounds.size.height - LIMIT_CO_BOTTOM;
if (_startTopY != LIMIT_CO_TOP && _startTopY != tempLimitBottom) {
if (_startTopY-LIMIT_CO_TOP < tempLimitBottom-_startTopY) {
_startTopY = LIMIT_CO_TOP;
} else {
_startTopY = tempLimitBottom;
}
}
}
return YES;
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.tag == 2000) {
CGPoint offset = scrollView.contentOffset;
if (offset.y <= 0) {
offset.y = 0;
scrollView.contentOffset = offset;
self.collectionView.scrollEnabled = NO;
} else {
}
}
}
@end
图片剪切:
YBPictureCutterView
#import <UIKit/UIKit.h>
@class YBPictureCutterView;
@protocol YBPictureCutterViewDataSource <NSObject>
@required
// 提供被处理的图片
- (UIImage *)imageForPictureCutterView:(YBPictureCutterView *)cutterView;
@end
@protocol YBPictureCutterViewDelegate <NSObject>
@optional
// 返回被裁剪过的图片
- (void)pictureCutterView:(YBPictureCutterView *)cutterView didClippedImage:(UIImage *)image;
@end
@interface YBPictureCutterView : UIView
@property (nonatomic, weak) id<YBPictureCutterViewDataSource> dataSource;
@property (nonatomic, weak) id<YBPictureCutterViewDelegate> delegate;
- (void)dismiss;
@end
#import "YBPictureCutterView.h"
#import "YBPhotoCutView.h"
#import "UIImage+FixOrientation.h"
@interface YBPictureCutterView () <YBPhotoCutViewDelegate>
@property (nonatomic, strong) YBPhotoCutView *cutView;
@property (nonatomic, strong) UIImageView *imgView;
@property (nonatomic, assign) CGRect cutFrame;
@property (nonatomic, strong) UIImage *image;
@end
@implementation YBPictureCutterView
#pragma mark - life cycle
- (instancetype)init {
if (self = [super init]) {
self.backgroundColor = [UIColor blackColor];
}
return self;
}
- (void)didMoveToWindow {
[super didMoveToWindow];
[self addSubview:self.imgView];
[self addSubview:self.cutView];
}
#pragma mark - public method
- (void)dismiss {
[self removeFromSuperview];
}
#pragma mark - YBPhotoCutView delegate
- (void)photoCutView:(YBPhotoCutView *)customView shotFrame:(CGRect)frame {
self.cutFrame = frame;
[self delegateInvocation];
}
- (void)delegateInvocation {
if ([self.delegate respondsToSelector:@selector(pictureCutterView:didClippedImage:)]) {
UIImage *image = [self cutImage];
if (image) {
[self.delegate pictureCutterView:self didClippedImage:image];
}
}
}
#pragma mark - 图片剪裁
- (UIImage *)cutImage {
if (self.cutFrame.size.height == 0) {
return nil;
}
double scale = self.imgView.image.size.width / self.imgView.frame.size.width;
CGFloat x = self.cutFrame.origin.x *scale;
CGFloat y = self.cutFrame.origin.y *scale;
CGFloat w = self.cutFrame.size.width *scale;
CGFloat h = self.cutFrame.size.height *scale;
CGRect cropRect = CGRectMake(x, y, w, h);
// 直接使用图片会发现image的方向不对
// CGImageRef imageRef = CGImageCreateWithImageInRect([self.imgView.image CGImage], cropRect);
// 需要处理以下图片的方向问题
CGImageRef imageRef = CGImageCreateWithImageInRect([[self.imgView.image fixOrientation] CGImage], cropRect);
UIImage *img = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
self.cutFrame = CGRectZero;
return img;
}
#pragma mark - lazy load
- (YBPhotoCutView *)cutView {
self.cutFrame = CGRectMake(self.bounds.size.width/4, self.bounds.size.height/4, self.bounds.size.width/2, self.bounds.size.height/2);
if (!_cutView) {
_cutView = [[YBPhotoCutView alloc] initWithFrame:self.bounds pictureFrame:self.cutFrame];
_cutView.delegate = self;
[self delegateInvocation];
}
return _cutView;
}
- (UIImageView *)imgView {
if (!_imgView) {
_imgView = [[UIImageView alloc] init];
_imgView.frame = self.bounds;
}
_imgView.image = self.image;
return _imgView;
}
- (UIImage *)image {
return [self.dataSource imageForPictureCutterView:self];
}
@end
YBPhotoCutView
#import <UIKit/UIKit.h>
@class YBPhotoCutView;
@protocol YBPhotoCutViewDelegate <NSObject>
@optional
// 返回裁剪框frame
- (void)photoCutView:(YBPhotoCutView *)customView shotFrame:(CGRect)frame;
@end
@interface YBPhotoCutView : UIControl
@property (nonatomic, assign) id<YBPhotoCutViewDelegate> delegate;
// 剪切框的frame
@property (nonatomic, assign, readonly) CGRect pictureFrame;
@property (nonatomic, assign) CGFloat minWidth;
@property (nonatomic, assign) CGFloat minHeight;
// frame是总视图大小,picFrame是剪切框大小
- (instancetype)initWithFrame:(CGRect)frame pictureFrame:(CGRect)picFrame;
@end
@interface YBMath : NSObject
// 极坐标
typedef struct {
double radius; // 半径
double angle; // 角度
} YBPolarCoordinate;
// 矩形的四个角坐标
typedef struct {
CGPoint topLeftPoint;
CGPoint topRightPoint;
CGPoint bottomLeftPoint;
CGPoint bottomRightPoint;
} CornerPoint;
// 矩形的四个边中心点
typedef struct {
CGPoint top;
CGPoint bottom;
CGPoint left;
CGPoint right;
} DirectionPoint;
// 四个边中心点的矩形
typedef struct {
CGRect topRect;
CGRect bottomRect;
CGRect leftRect;
CGRect rightRect;
} DirectionRect;
// 直角坐标转极坐标
YBPolarCoordinate decartToPolar(CGPoint center, CGPoint point);
// 根据frame计算矩形四个角的坐标
CornerPoint frameToCornerPoint(CGRect frame);
// 根据矩形四个角计算四条边中心点
DirectionPoint cornerPointToDirection(CornerPoint corner);
// 根据矩形四条边中心点计算中心点的矩形坐标
DirectionRect directionPointToDirectionRect(DirectionPoint drt_point, CGFloat width, CGFloat height);
@end
#import "YBPhotoCutView.h"
// 四个角的触摸范围半径
static CGFloat const touchRadius = 20.0;
// 四个角
typedef NS_ENUM(NSInteger,CornerIndex) {
TopLeft = 0,
TopRight,
BottomLeft,
BottomRight
};
// 四条边
typedef NS_ENUM(NSUInteger, DirectionIndex) {
Top = 0,
Bottom,
Left,
Right
};
@implementation YBPhotoCutView {
// 记录上次的触摸点以计算偏移量
CGPoint _touchedPoint;
// YES缩放模式(NO拖动模式,即不改变大小拖动剪切框)
BOOL _zooming;
// 拖动缩放的点 0上左:(0,0) 1上右:(1,0) 2下左:(0,1) 3下右:(1,1)
CornerIndex _zoomingIndex;
// 拖动缩放的边
DirectionIndex _zoomingDirection;
// 截图框四个角的坐标
CornerPoint _cornerPoint;
// 四条边中心点
DirectionPoint _directionPoint;
}
- (instancetype)initWithFrame:(CGRect)frame pictureFrame:(CGRect)picFrame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
self.pictureFrame = picFrame;
_minHeight = 60.0;
_minWidth = 60.0;
_zooming = NO;
_zoomingDirection = -1;
}
return self;
}
- (void)setPictureFrame:(CGRect)pictureFrame {
_pictureFrame = pictureFrame;
_cornerPoint = frameToCornerPoint(self.pictureFrame);
_directionPoint = cornerPointToDirection(_cornerPoint);
}
// 开始触摸跟踪
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint touchPoint = [touch locationInView:self];
_touchedPoint = touchPoint;
// 判断点击的是否是四条边
DirectionRect rect = directionPointToDirectionRect(_directionPoint, 30, 25);
if (CGRectContainsPoint(rect.topRect, touchPoint)) {
_zoomingDirection = Top;
_zooming = YES;
return YES;
}
if (CGRectContainsPoint(rect.bottomRect, touchPoint)) {
_zoomingDirection = Bottom;
_zooming = YES;
return YES;
}
if (CGRectContainsPoint(rect.leftRect, touchPoint)) {
_zoomingDirection = Left;
_zooming = YES;
return YES;
}
if (CGRectContainsPoint(rect.rightRect, touchPoint)) {
_zoomingDirection = Right;
_zooming = YES;
return YES;
}
// 遍历四个角的坐标,判断是否点击的是四个角
for (NSInteger i = 0; i < 4; i ++) {
// 0上左:(0,0) 1上右:(1,0) 2下左:(0,1) 3下右:(1,1)
CGFloat roundX = i%2 * self.pictureFrame.size.width + self.pictureFrame.origin.x;
CGFloat roundY = i/2 * self.pictureFrame.size.height + self.pictureFrame.origin.y;
// 如果在四个角为圆心的圆周内
if ([self touchInCircleWithPoint:touchPoint circleCenter:CGPointMake(roundX, roundY)]) {
// 缩放模式
_zooming = YES;
// 记录所点的角
_zoomingIndex = i;
return YES;
}
}
if (CGRectContainsPoint(self.pictureFrame, touchPoint)) {
// 拖动模式
return YES;
}
return NO;
}
// 继续触摸跟踪
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint touchPoint = [touch locationInView:self];
if (_zooming) {
// 让剪切框不超过self.bounds的范围
if (touchPoint.x < 0) {
touchPoint.x = 0;
}
if (touchPoint.y < 0) {
touchPoint.y = 0;
}
if (touchPoint.x > CGRectGetMaxX(self.bounds)) {
touchPoint.x = CGRectGetMaxX(self.bounds);
}
if (touchPoint.y > CGRectGetMaxY(self.bounds)) {
touchPoint.y = CGRectGetMaxY(self.bounds);
}
// 四条边缩放
CGFloat offsetX = touchPoint.x - _touchedPoint.x;
CGFloat offsetY = touchPoint.y - _touchedPoint.y;
CGRect preFrame = self.pictureFrame;
if (_zoomingDirection == Top) {
if (offsetY >= preFrame.size.height - self.minHeight) {
offsetY = preFrame.size.height - self.minHeight;
} else if (offsetY <= -CGRectGetMinY(preFrame)) {
offsetY = -CGRectGetMinY(preFrame);
}
self.pictureFrame = CGRectMake(preFrame.origin.x, preFrame.origin.y+offsetY, preFrame.size.width, preFrame.size.height-offsetY);
[self setNeedsDisplay];
_touchedPoint = touchPoint;
return YES;
} else if (_zoomingDirection == Bottom) {
if (-offsetY >= preFrame.size.height - self.minHeight) {
// offsetY = self.minHeight - preFrame.size.height;
// tempFloat = -offsetY
float tempFloat = preFrame.size.height - self.minHeight;
offsetY = -tempFloat;
} else if (offsetY >= self.bounds.size.height - CGRectGetMaxY(preFrame)) {
offsetY = self.bounds.size.height - CGRectGetMaxY(preFrame);
}
self.pictureFrame = CGRectMake(preFrame.origin.x, preFrame.origin.y, preFrame.size.width, preFrame.size.height+offsetY);
[self setNeedsDisplay];
_touchedPoint = touchPoint;
return YES;
} else if (_zoomingDirection == Left) {
if (offsetX >= preFrame.size.width - self.minWidth) {
offsetX = preFrame.size.width - self.minWidth;
} else if (offsetX <= -CGRectGetMinX(preFrame)) {
offsetX = -CGRectGetMinX(preFrame);
}
self.pictureFrame = CGRectMake(preFrame.origin.x+offsetX, preFrame.origin.y, preFrame.size.width-offsetX, preFrame.size.height);
[self setNeedsDisplay];
_touchedPoint = touchPoint;
return YES;
} else if (_zoomingDirection == Right) {
if (-offsetX >= preFrame.size.width - self.minWidth) {
// offsetX = self.minWidth - preFrame.size.width;
// tempFloat = -offsetX
float tempFloat = preFrame.size.width - self.minWidth;
offsetX = -tempFloat;
} else if (offsetX >= self.bounds.size.width - CGRectGetMaxX(preFrame)) {
offsetX = self.bounds.size.width - CGRectGetMaxX(preFrame);
}
self.pictureFrame = CGRectMake(preFrame.origin.x, preFrame.origin.y, preFrame.size.width+offsetX, preFrame.size.height);
[self setNeedsDisplay];
_touchedPoint = touchPoint;
return YES;
}
// 四个角缩放
CGPoint diagonalPoint;
CGPoint cornerPoint;
switch (_zoomingIndex) {
case TopLeft: {
diagonalPoint = _cornerPoint.bottomRightPoint;
cornerPoint = _cornerPoint.topLeftPoint;
break;
}
case TopRight: {
diagonalPoint = _cornerPoint.bottomLeftPoint;
cornerPoint = _cornerPoint.topRightPoint;
break;
}
case BottomLeft: {
diagonalPoint = _cornerPoint.topRightPoint;
cornerPoint = _cornerPoint.bottomLeftPoint;
break;
}
case BottomRight: {
diagonalPoint = _cornerPoint.topLeftPoint;
cornerPoint = _cornerPoint.bottomRightPoint;
break;
}
default:
break;
}
self.pictureFrame = [self pointToFrame:touchPoint cornerPoint:cornerPoint diagonalPoint:diagonalPoint zoomingIndex:_zoomingIndex];
} else {
// 移动剪切框
// X和Y方向上的偏移量
CGFloat offsetX = touchPoint.x - _touchedPoint.x;
CGFloat offsetY = touchPoint.y - _touchedPoint.y;
CGRect rect = self.pictureFrame;
rect.origin.x += offsetX;
rect.origin.y += offsetY;
// 让剪切框不超过self.bounds的范围
if (rect.origin.x < 0) {
rect.origin.x = 0;
}
if (rect.origin.y < 0) {
rect.origin.y = 0;
}
if (CGRectGetMaxX(rect) > self.bounds.size.width) {
rect.origin.x = self.bounds.size.width - self.pictureFrame.size.width;
}
if (CGRectGetMaxY(rect) > self.bounds.size.height) {
rect.origin.y = self.bounds.size.height - self.pictureFrame.size.height;
}
self.pictureFrame = rect;
}
_touchedPoint = touchPoint;
[self setNeedsDisplay];
return YES;
}
// 结束触摸跟踪
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
// 代理回调
if ([self.delegate respondsToSelector:@selector(photoCutView:shotFrame:)]) {
[self.delegate photoCutView:self shotFrame:self.pictureFrame];
}
// 还原数据
_touchedPoint = CGPointZero;
_zooming = NO;
_zoomingDirection = -1;
}
// 取消触摸跟踪
- (void)cancelTrackingWithEvent:(UIEvent *)event {
// 还原数据
_touchedPoint = CGPointZero;
_zooming = NO;
_zoomingDirection = -1;
}
// 根据触摸点,计算这个矩形的frame
- (CGRect)pointToFrame:(CGPoint)touching cornerPoint:(CGPoint)corner diagonalPoint:(CGPoint)diagonalPoint zoomingIndex:(CornerIndex)index {
CGFloat width,height;
CGPoint origin;
CGFloat offsetX = touching.x - _touchedPoint.x;
CGFloat offsetY = touching.y - _touchedPoint.y;
switch (index) {
case TopLeft: {
if (corner.x + offsetX <= 0) {
offsetX = - corner.x;
}
if (corner.y + offsetY <= 0) {
offsetY = - corner.y;
}
origin = CGPointMake(corner.x + offsetX, corner.y + offsetY);
width = diagonalPoint.x - corner.x - offsetX;
height = diagonalPoint.y - corner.y - offsetY;
if (width < self.minWidth) {
width = self.minWidth;
origin.x = diagonalPoint.x-self.minWidth;
}
if (height < self.minHeight) {
height = self.minHeight;
origin.y = diagonalPoint.y-self.minHeight;
}
break;
}
case TopRight: {
if (corner.y + offsetY <= 0) {
offsetY = - corner.y;
}
if (corner.x + offsetX >= self.bounds.size.width) {
offsetX = self.bounds.size.width - corner.x;
}
origin = CGPointMake(diagonalPoint.x, corner.y + offsetY);
width = corner.x + offsetX - diagonalPoint.x;
height = diagonalPoint.y - corner.y - offsetY;
if (width < self.minWidth) {
width = self.minWidth;
origin.x = diagonalPoint.x;
}
if (height < self.minHeight) {
height = self.minHeight;
origin.y = diagonalPoint.y - self.minHeight;
}
break;
}
case BottomLeft: {
if (corner.x + offsetX <= 0) {
offsetX = - corner.x;
}
if (corner.y + offsetY >= self.bounds.size.height) {
offsetY = self.bounds.size.height - corner.y;
}
origin = CGPointMake(corner.x + offsetX, diagonalPoint.y);
width = diagonalPoint.x - corner.x - offsetX;
height = corner.y + offsetY - diagonalPoint.y;
if (width < self.minWidth) {
width = self.minWidth;
origin.x = diagonalPoint.x-self.minWidth;
}
if (height < self.minHeight) {
height = self.minHeight;
origin.y = diagonalPoint.y;
}
break;
}
case BottomRight: {
if (corner.x + offsetX >= self.bounds.size.width) {
offsetX = self.bounds.size.width - corner.x;
}
if (corner.y + offsetY >= self.bounds.size.height) {
offsetY = self.bounds.size.height - corner.y;
}
origin = diagonalPoint;
width = corner.x + offsetX - diagonalPoint.x;
height = corner.y + offsetY - diagonalPoint.y;
if (width < self.minWidth) {
width = self.minWidth;
origin.x = diagonalPoint.x;
}
if (height < self.minHeight) {
height = self.minHeight;
origin.y = diagonalPoint.y;
}
break;
}
default: {
width = 0;
height = 0;
break;
}
}
CGRect rect = CGRectMake(origin.x, origin.y, width, height);
return rect;
}
// 判断touchPoint是否在以circleCenter为圆心、半径为touchRadius的圆内
- (BOOL)touchInCircleWithPoint:(CGPoint)touchPoint circleCenter:(CGPoint)circleCenter {
YBPolarCoordinate polar = decartToPolar(circleCenter, touchPoint);
if(polar.radius >= touchRadius) {
return NO;
} else {
return YES;
}
}
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 蒙版层
[[UIColor colorWithWhite:0.0 alpha:0.3] setFill];
CGContextFillRect(ctx, self.bounds);
CGContextClearRect(ctx, self.pictureFrame);
// 矩形框
[[UIColor whiteColor] setStroke];
CGContextAddRect(ctx, self.pictureFrame);
CGContextStrokePath(ctx);
CGFloat edge_3 = 3;
CGFloat edge_20 = 20;
[[UIColor whiteColor] setFill];
// 左上
CGContextAddRect(ctx, CGRectMake(_cornerPoint.topLeftPoint.x-edge_3, _cornerPoint.topLeftPoint.y-edge_3, edge_20, edge_3));
CGContextFillPath(ctx);
CGContextAddRect(ctx, CGRectMake(_cornerPoint.topLeftPoint.x-edge_3, _cornerPoint.topLeftPoint.y-edge_3, edge_3, edge_20));
CGContextFillPath(ctx);
// 左下
CGContextAddRect(ctx, CGRectMake(_cornerPoint.bottomLeftPoint.x-edge_3, _cornerPoint.bottomLeftPoint.y, edge_20, edge_3));
CGContextFillPath(ctx);
CGContextAddRect(ctx, CGRectMake(_cornerPoint.bottomLeftPoint.x-edge_3, _cornerPoint.bottomLeftPoint.y-edge_20+edge_3, edge_3, edge_20));
CGContextFillPath(ctx);
// 右上
CGContextAddRect(ctx, CGRectMake(_cornerPoint.topRightPoint.x-edge_20+edge_3, _cornerPoint.topRightPoint.y-edge_3, edge_20, edge_3));
CGContextFillPath(ctx);
CGContextAddRect(ctx, CGRectMake(_cornerPoint.topRightPoint.x, _cornerPoint.topRightPoint.y-edge_3, edge_3, edge_20));
CGContextFillPath(ctx);
// 右下
CGContextAddRect(ctx, CGRectMake(_cornerPoint.topRightPoint.x-edge_20+edge_3, _cornerPoint.bottomRightPoint.y, edge_20, edge_3));
CGContextFillPath(ctx);
CGContextAddRect(ctx, CGRectMake(_cornerPoint.bottomRightPoint.x, _cornerPoint.bottomRightPoint.y-edge_20+edge_3, edge_3, edge_20));
CGContextFillPath(ctx);
CGFloat direction_30 = 30;
DirectionRect frame = directionPointToDirectionRect(_directionPoint, direction_30, edge_3);
// 上
CGContextAddRect(ctx, frame.topRect);
CGContextFillPath(ctx);
// 下
CGContextAddRect(ctx, frame.bottomRect);
CGContextFillPath(ctx);
// 左
CGContextAddRect(ctx, frame.leftRect);
CGContextFillPath(ctx);
// 右
CGContextAddRect(ctx, frame.rightRect);
CGContextFillPath(ctx);
CGFloat height_1 = 1/[UIScreen mainScreen].scale;
// 横一
CGContextAddRect(ctx, CGRectMake((int)_cornerPoint.topLeftPoint.x, (int)(_cornerPoint.bottomLeftPoint.y-(_cornerPoint.bottomLeftPoint.y-_cornerPoint.topLeftPoint.y)/3*2), _cornerPoint.topRightPoint.x-_cornerPoint.topLeftPoint.x, height_1));
CGContextFillPath(ctx);
// 横二
CGContextAddRect(ctx, CGRectMake((int)_cornerPoint.topLeftPoint.x, (int)(_cornerPoint.bottomLeftPoint.y-(_cornerPoint.bottomLeftPoint.y-_cornerPoint.topLeftPoint.y)/3), _cornerPoint.topRightPoint.x-_cornerPoint.topLeftPoint.x, height_1));
CGContextFillPath(ctx);
// 竖一
CGContextAddRect(ctx, CGRectMake((int)(_cornerPoint.topRightPoint.x-(_cornerPoint.topRightPoint.x-_cornerPoint.topLeftPoint.x)/3*2), (int)_cornerPoint.topRightPoint.y, height_1, _cornerPoint.bottomLeftPoint.y-_cornerPoint.topLeftPoint.y));
CGContextFillPath(ctx);
// 竖二
CGContextAddRect(ctx, CGRectMake((int)(_cornerPoint.topRightPoint.x-(_cornerPoint.topRightPoint.x-_cornerPoint.topLeftPoint.x)/3), (int)_cornerPoint.topRightPoint.y, height_1, _cornerPoint.bottomLeftPoint.y-_cornerPoint.topLeftPoint.y));
CGContextFillPath(ctx);
}
@end
@implementation YBMath
// 直角坐标转极坐标
YBPolarCoordinate decartToPolar(CGPoint center, CGPoint point){
double x = point.x - center.x;
double y = point.y - center.y;
YBPolarCoordinate polar;
// pow(x,y); 其作用是计算x的y次方。x、y及函数值都是double型
// sqrt(double); 计算平方根
polar.radius = sqrt(pow(x, 2.0) + pow(y, 2.0));
// double acos(double); 反余弦函数
polar.angle = acos(x/(sqrt(pow(x, 2.0) + pow(y, 2.0))));
if (y < 0) {
polar.angle = 2 * M_PI - polar.angle;
}
return polar;
}
// 根据frame计算矩形四个角的坐标
CornerPoint frameToCornerPoint(CGRect frame) {
CornerPoint corner;
corner.topLeftPoint = frame.origin;
corner.topRightPoint = CGPointMake(frame.origin.x+frame.size.width, frame.origin.y);
corner.bottomLeftPoint = CGPointMake(frame.origin.x, frame.origin.y+frame.size.height);
corner.bottomRightPoint = CGPointMake(frame.origin.x+frame.size.width, frame.origin.y+frame.size.height);
return corner;
}
DirectionPoint cornerPointToDirection(CornerPoint corner) {
DirectionPoint direction;
direction.top = CGPointMake(corner.topRightPoint.x-(corner.topRightPoint.x-corner.topLeftPoint.x)/2, corner.topRightPoint.y);
direction.bottom = CGPointMake(corner.topRightPoint.x-(corner.topRightPoint.x-corner.topLeftPoint.x)/2, corner.bottomRightPoint.y);
direction.left = CGPointMake(corner.topLeftPoint.x, corner.bottomLeftPoint.y-(corner.bottomLeftPoint.y-corner.topLeftPoint.y)/2);
direction.right = CGPointMake(corner.topRightPoint.x, corner.bottomLeftPoint.y-(corner.bottomLeftPoint.y-corner.topLeftPoint.y)/2);
return direction;
}
// 宽高是水平方向的,如果是竖直方向则宽高反过来
DirectionRect directionPointToDirectionRect(DirectionPoint drt_point, CGFloat width, CGFloat height) {
DirectionRect rect;
rect.topRect = CGRectMake(drt_point.top.x-width/2, drt_point.top.y-height, width, height);
rect.bottomRect = CGRectMake(drt_point.bottom.x-width/2, drt_point.bottom.y, width, height);
rect.leftRect = CGRectMake(drt_point.left.x-height, drt_point.left.y-width/2, height, width);
rect.rightRect = CGRectMake(drt_point.right.x, drt_point.right.y-width/2, height, width);
return rect;
}
@end
UIImage+FixOrientation
用来处理获取图片的方向并非全是竖屏的问题,不然会导致剪切获取的图片不准确。
#import <UIKit/UIKit.h>
@interface UIImage (FixOrientation)
- (UIImage *)fixOrientation;
@end
#import "UIImage+FixOrientation.h"
@implementation UIImage (FixOrientation)
- (UIImage *)fixOrientation {
// No-op if the orientation is already correct
if (self.imageOrientation == UIImageOrientationUp) return self;
// We need to calculate the proper transformation to make the image upright.
// We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.
CGAffineTransform transform = CGAffineTransformIdentity;
switch (self.imageOrientation) {
case UIImageOrientationDown:
case UIImageOrientationDownMirrored:
transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height);
transform = CGAffineTransformRotate(transform, M_PI);
break;
case UIImageOrientationLeft:
case UIImageOrientationLeftMirrored:
transform = CGAffineTransformTranslate(transform, self.size.width, 0);
transform = CGAffineTransformRotate(transform, M_PI_2);
break;
case UIImageOrientationRight:
case UIImageOrientationRightMirrored:
transform = CGAffineTransformTranslate(transform, 0, self.size.height);
transform = CGAffineTransformRotate(transform, -M_PI_2);
break;
case UIImageOrientationUp:
case UIImageOrientationUpMirrored:
break;
}
switch (self.imageOrientation) {
case UIImageOrientationUpMirrored:
case UIImageOrientationDownMirrored:
transform = CGAffineTransformTranslate(transform, self.size.width, 0);
transform = CGAffineTransformScale(transform, -1, 1);
break;
case UIImageOrientationLeftMirrored:
case UIImageOrientationRightMirrored:
transform = CGAffineTransformTranslate(transform, self.size.height, 0);
transform = CGAffineTransformScale(transform, -1, 1);
break;
case UIImageOrientationUp:
case UIImageOrientationDown:
case UIImageOrientationLeft:
case UIImageOrientationRight:
break;
}
// Now we draw the underlying CGImage into a new context, applying the transform
// calculated above.
CGContextRef ctx = CGBitmapContextCreate(NULL, self.size.width, self.size.height,
CGImageGetBitsPerComponent(self.CGImage), 0,
CGImageGetColorSpace(self.CGImage),
CGImageGetBitmapInfo(self.CGImage));
CGContextConcatCTM(ctx, transform);
switch (self.imageOrientation) {
case UIImageOrientationLeft:
case UIImageOrientationLeftMirrored:
case UIImageOrientationRight:
case UIImageOrientationRightMirrored:
// Grr...
CGContextDrawImage(ctx, CGRectMake(0,0,self.size.height,self.size.width), self.CGImage);
break;
default:
CGContextDrawImage(ctx, CGRectMake(0,0,self.size.width,self.size.height), self.CGImage);
break;
}
// And now we just create a new UIImage from the drawing context
CGImageRef cgimg = CGBitmapContextCreateImage(ctx);
UIImage *img = [UIImage imageWithCGImage:cgimg];
CGContextRelease(ctx);
CGImageRelease(cgimg);
return img;
}
@end