在項(xiàng)目中當(dāng)我們遇到拍照的功能模塊的時(shí)候靶擦,如果僅僅是用來拍照,系統(tǒng)提供的UIImagePickerController足夠用來完成我們的任務(wù)华烟。但是當(dāng)我們的應(yīng)用場(chǎng)景稍稍復(fù)雜點(diǎn)的時(shí)候,如要實(shí)現(xiàn)類似水印相機(jī)、美顏相機(jī)的時(shí)候漆枚,UIImagePickerController就有點(diǎn)力不從心了,需要自己去diy一個(gè)自定義相機(jī)抵知。
UIImagePickerController使用起來比較簡單易用墙基,拍照,錄制視頻刷喜、控制閃光燈残制,前后攝像頭的切換一應(yīng)俱全。但是相機(jī)支持ui界面的自定義并不好(雖然可以支持自定義)掖疮,在不同的系統(tǒng)下相機(jī)的功能界面還有所差別初茶。
以水印相機(jī)為例,在水印相機(jī)中我們需要能夠讓水印模式實(shí)時(shí)的顯示在相機(jī)的取景框中浊闪,而且水印模式還要可以左右滑動(dòng)切換恼布,在橫屏的時(shí)候水印也要跟著橫屏,還要有放大縮小鏡頭的以及點(diǎn)擊屏幕能夠聚焦等功能搁宾。
當(dāng)然有人可能會(huì)想折汞,將水印模式分成一個(gè)視圖層然后放到相機(jī)的最上層不就行了嗎?當(dāng)然是不行的盖腿,首先UIImagePickerController在不同系統(tǒng)中的封裝是略微不一樣的爽待,ui界面有所差別,界面不能夠統(tǒng)一奸忽,即便是現(xiàn)在花了很過代碼一個(gè)系統(tǒng)一個(gè)系統(tǒng)的適配堕伪,也很難保證以后不出問題,其次是手勢(shì)的識(shí)別也有問題栗菜,即便是對(duì)手勢(shì)進(jìn)行了攔截處理欠雌,也不能解決,說白了也就是不是自己封裝的東西疙筹,難以得到完美的掌控富俄。
下面就開始自定義一個(gè)相機(jī),并實(shí)現(xiàn)拍照而咆、取消霍比、閃光燈控制、前后攝像頭控制暴备、聚焦悠瞬、放大縮小、拍照后預(yù)覽、重拍浅妆、使用照片等功能望迎。
@property (nonatomic, strong) ZTImagePickerOverLayView *overlayView;//預(yù)覽圖層
@property (nonatomic) dispatch_queue_t sessionQueue;
@property (nonatomic, strong) AVCaptureSession* session;//用于捕捉視頻和音頻,協(xié)調(diào)視頻和音頻的輸入和輸出流
@property (nonatomic, strong) AVCaptureDeviceInput* videoInput;
@property (nonatomic, strong) AVCaptureStillImageOutput* stillImageOutput;//輸出靜態(tài)影像
@property (nonatomic, strong) AVCaptureDevice *device;//主要用來獲取iphone一些關(guān)于相機(jī)設(shè)備的屬性
@property (nonatomic, strong) AVCaptureVideoPreviewLayer* previewLayer;//預(yù)覽圖層layer
這里將相機(jī)的控件以及相機(jī)的實(shí)時(shí)顯示的圖層放在一個(gè)視圖類ZTImagePickerOverLayView中,拍完照后的圖層放在另外一個(gè)類ZTImagePickerPreImageView中,各個(gè)視圖間的協(xié)調(diào)及部分邏輯放在控制器中ZTImagePickerController凌外。封裝完整個(gè)相機(jī)不過用了幾百行代碼辩尊。
1.初始化
self.session = [[AVCaptureSession alloc] init];
[self.session setSessionPreset:AVCaptureSessionPresetPhoto];
NSError *error;
self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
self.videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:self.device error:&error];
self.stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
//輸出設(shè)置。AVVideoCodecJPEG 輸出jpeg格式圖片
NSDictionary * outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:AVVideoCodecJPEG,AVVideoCodecKey, nil];
[self.stillImageOutput setOutputSettings:outputSettings];
if ([self.session canAddInput:self.videoInput]) {
[self.session addInput:self.videoInput];
}
if ([self.session canAddOutput:self.stillImageOutput]) {
[self.session addOutput:self.stillImageOutput];
}
//初始化預(yù)覽圖層
self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
[self.previewLayer setVideoGravity:AVLayerVideoGravityResizeAspect];
NSLog(@"%f",ScreenWidth);
self.previewLayer.frame = CGRectMake(0, 0,ScreenWidth, ScreenHeight);
self.preview = [[ZTImagePickerOverLayView alloc] init];
self.preview.frame = CGRectMake(0, 0, ScreenWidth, ScreenHeight);
[self.preview layoutSubviews];
[self.preview.layer addSublayer:self.previewLayer];
[self.view addSubview:self.preview];
//添加頂部以及底部的自定義工具條
[self.view addSubview:self.preview.topbar];
[self.view addSubview:self.preview.buttomBar];
self.preview.topbar.frame = CGRectMake(0, 0, self.view.width, 64 * ScreenWidth/320.0);
self.preview.buttomBar.frame = CGRectMake(0, self.view.height - 70 * ScreenWidth/320.0 , self.view.width, 70* ScreenWidth/320.0);
[self.preview layoutSubviews];
//設(shè)置閃關(guān)燈模式
if(self.device.isFlashAvailable)
[self.preview setFlashModel:self.device.flashMode];
else{
self.preview.flashButton.hidden = YES;
self.preview.cameraSwitchButton.hidden = YES;
}
//設(shè)置拍照后預(yù)覽圖層
self.preImageView = [[ZTImagePickerPreImageView alloc] initWithFrame:CGRectMake(0, 0, ScreenWidth, ScreenHeight)];
[self.preImageView layoutSubviews];
self.preImageView.hidden = YES;
[self.view addSubview:self.preImageView];
2.添加手勢(shì)
給預(yù)覽層添加捏合手勢(shì)控制放大縮小,添加點(diǎn)擊手勢(shì)來聚焦
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self
action:@selector(handlePinchGesture:)];
pinch.delegate = self;
[self.preview addGestureRecognizer:pinch];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(focusAction:)];
[self.preview addGestureRecognizer:tap];
3.給控件添加響應(yīng)事件
閃光燈打開康辑、關(guān)閉摄欲、自動(dòng),攝像頭切換疮薇、取消胸墙、拍照、重新拍照惦辛、使用照片等按鈕添加響應(yīng)事件
[self.preview.cameraSwitchButton addTarget:self action:@selector(switchCameraSegmentedControlClick:) forControlEvents:UIControlEventTouchUpInside];
[self.preview.flashAutoButton addTarget:self action:@selector(flashButtonClick:) forControlEvents:UIControlEventTouchUpInside];
[self.preview.flashOpeanButton addTarget:self action:@selector(flashButtonClick:) forControlEvents:UIControlEventTouchUpInside];
[self.preview.flashCloseButton addTarget:self action:@selector(flashButtonClick:) forControlEvents:UIControlEventTouchUpInside];
[self.preview.takePictureButton addTarget:self action:@selector(takePhotoButtonClick:) forControlEvents:UIControlEventTouchUpInside];
[self.preview.cancelButton addTarget:self action:@selector(cancelButtonClick:) forControlEvents:UIControlEventTouchUpInside];
[self.preImageView.reTakeButton addTarget:self action:@selector(retakeButtonClick:) forControlEvents:UIControlEventTouchUpInside];
[self.preImageView.useImageButton addTarget:self action:@selector(useImageButtonClick:) forControlEvents:UIControlEventTouchUpInside];
4.閃光燈控制
- (void)flashButtonClick:(UIButton *)sender {
//[self.preview reSetTopbar];
[self.preview chosedFlashButton:sender];
NSLog(@"flashButtonClick");
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
//修改前必須先鎖定
[device lockForConfiguration:nil];
//必須判定是否有閃光燈劳秋,否則如果沒有閃光燈會(huì)崩潰
if ([device hasFlash]) {
if([sender.titleLabel.text isEqualToString:@"打開"]){
if([device isFlashModeSupported:AVCaptureFlashModeOn])
[device setFlashMode:AVCaptureFlashModeOn];
}else if ([sender.titleLabel.text isEqualToString:@"自動(dòng)"]){
if([device isFlashModeSupported:AVCaptureFlashModeAuto])
[device setFlashMode:AVCaptureFlashModeAuto];
}else if ([sender.titleLabel.text isEqualToString:@"關(guān)閉"]){
if([device isFlashModeSupported:AVCaptureFlashModeOff])
[device setFlashMode:AVCaptureFlashModeOff];
}
} else {
NSLog(@"設(shè)備不支持閃光燈");
}
[device unlockForConfiguration];
}
5.前后攝像頭切換
- (void)switchCameraSegmentedControlClick:(id)sender {
//NSLog(@"%ld",(long)sender.selectedSegmentIndex);
AVCaptureDevicePosition desiredPosition;
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if (isUsingFrontFacingCamera){
if(device.isFlashAvailable) self.preview.flashButton.hidden = NO;
desiredPosition = AVCaptureDevicePositionBack;
}else{
desiredPosition = AVCaptureDevicePositionFront;
[self.preview reSetTopbar];
self.preview.flashButton.hidden = YES;
}
for (AVCaptureDevice *d in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
if ([d position] == desiredPosition) {
[self.previewLayer.session beginConfiguration];
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:d error:nil];
for (AVCaptureInput *oldInput in self.previewLayer.session.inputs) {
[[self.previewLayer session] removeInput:oldInput];
}
[self.previewLayer.session addInput:input];
[self.previewLayer.session commitConfiguration];
break;
}
}
isUsingFrontFacingCamera = !isUsingFrontFacingCamera;
}
6.拍照
- (void)takePhotoButtonClick:(id )sender{
AVCaptureConnection *stillImageConnection = [self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo];
UIDeviceOrientation curDeviceOrientation = [[UIDevice currentDevice] orientation];
AVCaptureVideoOrientation avcaptureOrientation = [self avOrientationForDeviceOrientation:curDeviceOrientation];
[stillImageConnection setVideoOrientation:avcaptureOrientation];
[stillImageConnection setVideoScaleAndCropFactor:self.effectiveScale];
[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:stillImageConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
NSData *jpegData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
self.imageData = jpegData;
CFDictionaryRef attachments = CMCopyDictionaryOfAttachments(kCFAllocatorDefault,
imageDataSampleBuffer,
kCMAttachmentMode_ShouldPropagate);
UIImage *image = [UIImage imageWithData:jpegData];
[self waterMarkFixed];
self.preImageView.imageView.image = image;
[self.preview hiddenSelfAndBars:YES];
self.preImageView.hidden = NO;
ALAuthorizationStatus author = [ALAssetsLibrary authorizationStatus];
if (author == ALAuthorizationStatusRestricted || author ==ALAuthorizationStatusDenied){
//無權(quán)限
return ;
}
//保存到相冊(cè)
// ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
// [library writeImageDataToSavedPhotosAlbum:jpegData metadata:(__bridge id)attachments completionBlock:^(NSURL *assetURL, NSError *error) {
//
// }];
}];
if([self.delegate respondsToSelector:@selector(imagePickerControllerTakePhoto:)])
[self.delegate imagePickerControllerTakePhoto:self];
}
7.放大縮小
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer{
BOOL allTouchesAreOnThePreviewLayer = YES;
NSUInteger numTouches = [recognizer numberOfTouches], i;
for ( i = 0; i < numTouches; ++i ) {
CGPoint location = [recognizer locationOfTouch:i inView:self.preview];
CGPoint convertedLocation = [self.previewLayer convertPoint:location fromLayer:self.previewLayer.superlayer];
if ( ! [self.previewLayer containsPoint:convertedLocation] ) {
allTouchesAreOnThePreviewLayer = NO;
break;
}
}
if ( allTouchesAreOnThePreviewLayer ) {
self.effectiveScale = self.beginGestureScale * recognizer.scale;
if (self.effectiveScale < 1.0){
self.effectiveScale = 1.0;
}
CGFloat maxScaleAndCropFactor = [[self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo] videoMaxScaleAndCropFactor];
if (self.effectiveScale > maxScaleAndCropFactor)
self.effectiveScale = maxScaleAndCropFactor;
[CATransaction begin];
[CATransaction setAnimationDuration:.025];
[self.previewLayer setAffineTransform:CGAffineTransformMakeScale(self.effectiveScale, self.effectiveScale)];
[CATransaction commit];
}
}
8.照片方向修正
拍完照的時(shí)候,拍出的照片你會(huì)發(fā)現(xiàn)呈現(xiàn)的方向不對(duì)胖齐,需要對(duì)照片的方向進(jìn)行修正玻淑。
AVCaptureVideoOrientation avcaptureOrientation = [self avOrientationForDeviceOrientation:curDeviceOrientation];
[stillImageConnection setVideoOrientation:avcaptureOrientation];
- (AVCaptureVideoOrientation)avOrientationForDeviceOrientation:(UIDeviceOrientation)deviceOrientation
{
AVCaptureVideoOrientation result = (AVCaptureVideoOrientation)deviceOrientation;
if ( deviceOrientation == UIDeviceOrientationLandscapeLeft )
result = AVCaptureVideoOrientationLandscapeRight;
else if ( deviceOrientation == UIDeviceOrientationLandscapeRight )
result = AVCaptureVideoOrientationLandscapeLeft;
return result;
}
這里需要注意的是如果想要拍完照的效果和UIimagePickerController的效果一樣,即在橫屏下拍完照呀伙,照片要旋轉(zhuǎn)顯示并且顯示的小一些补履,那么就要對(duì)拍完照預(yù)覽圖層進(jìn)行修改。
圖片
在預(yù)覽圖層中修改imageview的大小剿另。(這部分代碼我并沒有加到demo中箫锤,如果想實(shí)現(xiàn)拍完照后照片的方向與系統(tǒng)相機(jī)的一樣,可以在ZTImagePickerPreImageView中加上)
- (void)changeImageViewFrameIfNeeded:(UIDeviceOrientation)orientation{
if(orientation == UIDeviceOrientationLandscapeLeft || orientation == UIDeviceOrientationLandscapeRight){
self.imageView.frame = CGRectMake(0, 0, self.width, 240 * [UIScreen mainScreen].bounds.size.width / 320.0);
self.imageView.centerY = self.height / 2.0;
}else{
_imageView.frame = CGRectMake(0, 0, self.width, 418 * [UIScreen mainScreen].bounds.size.width / 320.0);
_imageView.centerY = self.height / 2.0;
}
}
僅僅是這樣還不夠雨女,還要對(duì)設(shè)備方向的獲取進(jìn)行改進(jìn)谚攒。
通常我們獲取設(shè)備方向是通過[[UIDevice currentDevice] orientation] 或者通過[UIApplication sharedApplication].statusBarOrientation的方式來獲取。但這兩種方式有一個(gè)缺點(diǎn)氛堕,在豎排方向開關(guān)關(guān)閉的時(shí)候馏臭,獲取到的方向是正確的,在開關(guān)打開的時(shí)候獲取到的方向是豎直方向讼稚,在橫屏等情況下獲取的方向不正確括儒。這時(shí)候就要通過CMMotionManager來獲取方向了。(下面這段代碼也沒有加到demo中锐想,如果有這樣的功能需求帮寻,可以在ZTImagePickerController中加上這段代碼)
- (void)p_startMotionManager{
self.deviceOrientation = UIDeviceOrientationPortrait;
if (_motionManager == nil) {
_motionManager = [[CMMotionManager alloc] init];
}
_motionManager.deviceMotionUpdateInterval = 1/15.0;
if (_motionManager.deviceMotionAvailable) {
NSLog(@"Device Motion Available");
[_motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue]
withHandler: ^(CMDeviceMotion *motion, NSError *error){
[self performSelectorOnMainThread:@selector(handleDeviceMotion:) withObject:motion waitUntilDone:YES];
}];
} else {
NSLog(@"No device motion on device.");
}
}
- (void)p_stopMonotionManager{
[_motionManager stopDeviceMotionUpdates];
_motionManager = nil;
}
二、添加水印
如果要實(shí)現(xiàn)類似騰訊的水印相機(jī)形式的水印赠摇,需要對(duì)水印專門做一個(gè)圖層來進(jìn)行管理固逗。將水印圖層放在相機(jī)的最上層就可以實(shí)時(shí)看到水印了浅蚪,并且可以左右切換水印。這里使用scrollview來容納每種水印樣式抒蚜,如果水印樣式比較多當(dāng)然可以使用collectionView來容納掘鄙。
為了讓水印圖層的手勢(shì)(scrollView的左右滑動(dòng),每種水印樣式視圖中的控件響應(yīng)手勢(shì))響應(yīng)不與相機(jī)圖層的手勢(shì)響應(yīng)不沖突嗡髓,在水印圖層可以將手勢(shì)進(jìn)行攔截,根據(jù)實(shí)際情況來返回響應(yīng)手勢(shì)的視圖控件收津。
這里的代碼根據(jù)實(shí)際情況修改
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *view = [super hitTest:point withEvent:event];
CGPoint tempPoint = [self.reportView convertPoint:point fromView:self];
if(CGRectContainsPoint(self.reportView.reportTypeLb.frame, tempPoint)){
view = self.reportView.reportTypeLb;
return view;
}
NSInteger left = 0,top = 0, height = 0,width = self.contentSize.width;
height = MAX(self.reportView.xmNameLb.top, self.handleProblemView.userLb.top);
if([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft){
left = self.height - height;
width = self.width;
}
if(CGRectContainsPoint(CGRectMake(left, top, width, height), point)){
view = [self.superview.subviews objectAtIndex:0];
view = [view hitTest:point withEvent:event];
}else{
}
return view;
}
拍完照選擇好水印樣式后饿这,將水印樣式用的空間繪制到照片上,就形成了水印照片
- (UIImage *)markedImageWithType:(XBWaterMark )waterMarkType date:(NSDate *)date user:(NSString *)user placLocation:(ZTLocationModel *)locationModel withPhone:(NSString *)phone xmType:(NSString *)xmType{
if (self.size.width == 0.0 || self.size.height == 0.0) return nil;
UIImage *defaultImage = nil;
CGFloat scale = [UIScreen mainScreen].scale;
if(scale >= 3) scale = 2;
UIImage *image = [self thumbnailForMaxWidth:1024/scale maxHeight:1024/scale];
CGSize newSize = CGSizeMake(image.size.width*image.scale/scale, image.size.height*image.scale/scale);
UIView *waterMarkView = [self p_markWaterMarkView:waterMarkType date:date user:user
placLocation:locationModel withPhone:phone newSize:newSize xmType:xmType];
//將水印樣式中的控件繪制到圖片
UIGraphicsBeginImageContextWithOptions(newSize, YES, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
[image drawInRect:CGRectMake(0.0, 0.0, newSize.width, newSize.height)];
for (UIView *view in waterMarkView.subviews) {
if([view isKindOfClass:[UIImageView class]]){
UIImageView *iv = (UIImageView *)view;
[iv.image drawInRect:CGRectMake(iv.left,iv.top, iv.width, iv.height)];
}else if ([view isKindOfClass:[UILabel class]]){
UILabel *lb = (UILabel *)view;
UIImage *lbImage = [lb imageByRenderingView];
[lbImage drawInRect:CGRectMake(lb.left, lb.top, lb.width, lb.height)];
}
}
CGContextRestoreGState(context);
defaultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return defaultImage;
}
效果圖
自定義相機(jī)demo