自iOS7以后巡蘸,iOS掃描二維碼就并不需要借助第三方的框架,這次項(xiàng)目的需求擂送,自己實(shí)現(xiàn)一個(gè)掃描的功能悦荒,蘋果在AVFoundation中原生支持掃描二維碼的API,主要有5個(gè)類嘹吨,這5個(gè)類在自定義相機(jī)或者視頻時(shí)都用的上搬味,這5個(gè)類分別是:
1.AVCaptureDevice:代表抽象的硬件設(shè)備
2.AVCaptureSession:會(huì)話對(duì)象,連接輸入設(shè)備和輸出設(shè)備蟀拷。
3.AVCaptureDeviceInput:輸入設(shè)備碰纬。設(shè)備輸入數(shù)據(jù)管理對(duì)象,可以根據(jù)AVCaptureDevice創(chuàng)建對(duì)應(yīng)的AVCaptureDeviceInput對(duì)象问芬,該對(duì)象將會(huì)被添加到AVCaptureSession中管理
4.AVCaptureOutput:輸出設(shè)備悦析。輸出設(shè)備管理對(duì)象,用于接收各類輸出數(shù)據(jù)此衅,有很多子類强戴,每一個(gè)子類用途都不一樣亭螟,該對(duì)象將會(huì)被添加到AVCaptureSession中管理
5.AVCaptureVideoPreviewLayer:相機(jī)拍攝預(yù)覽圖層,是CALayer的子類骑歹,使用該對(duì)象可以實(shí)時(shí)的查看拍照或視屏錄制的效果预烙,設(shè)置好尺寸后需要添加到父view的layer中
核心的步驟:
1.創(chuàng)建AVCaptureSession會(huì)話
2.創(chuàng)建AVCaptureDevice設(shè)備
3.創(chuàng)建輸入AVCaptureDeviceInput與輸出設(shè)備AVCaptureOutput,并添加到上面的繪畫中
4.創(chuàng)建預(yù)覽層
5.設(shè)置掃描區(qū)域
具體的代碼如下:
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
@interface ViewController ()<AVCaptureMetadataOutputObjectsDelegate,CALayerDelegate>
@property (weak, nonatomic) IBOutlet UIView *scanView;
@property (weak, nonatomic) IBOutlet UIImageView *scanline;
//結(jié)果
@property (weak, nonatomic) IBOutlet UILabel *result;
/**掃描區(qū)域的高度約束(高寬一致)*/
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *scanViewH;
//掃描線頂部的約束
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *scanlineTop;
@property (nonatomic,strong) CALayer *maskLayer;
/**
*五個(gè)類
*/
@property (nonatomic,strong)AVCaptureDevice *device;
@property (nonatomic,strong)AVCaptureDeviceInput *input;
@property (nonatomic,strong) AVCaptureMetadataOutput *output;
@property (nonatomic,strong) AVCaptureSession *session;
@property (nonatomic,strong) AVCaptureVideoPreviewLayer *layer;
@end
@implementation ViewController
#pragma mark - 懶加載
-(AVCaptureDevice *)device{
if (_device == nil) {
_device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
}
return _device;
}
-(AVCaptureDeviceInput *)input{
if (_input == nil) {
_input = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:nil];
}
return _input;
}
-(AVCaptureMetadataOutput *)output{
if (_output == nil) {
_output = [[AVCaptureMetadataOutput alloc]init];
[_output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
}
return _output;
}
#pragma mark - ViewController生命周期
/**
* 掃描的那條線動(dòng)起來
*/
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[UIView animateKeyframesWithDuration:3.0 delay:0 options:UIViewKeyframeAnimationOptionRepeat animations:^{
self.scanlineTop.constant = self.scanViewH.constant-4;
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
}];
}
//-(void)viewWillAppear:(BOOL)animated{
//
// [super viewWillAppear:animated];
//
// [UIView animateWithDuration:3.0 delay:0 options:UIViewAnimationOptionRepeat animations:^{
//
// self.scanlineTop.constant = self.scanViewH.constant - 4;
//
// [self.scanline layoutIfNeeded];
//
// } completion:nil];
//
//}
- (void)viewDidLoad {
[super viewDidLoad];
//1道媚、創(chuàng)建會(huì)話
AVCaptureSession *session = [[AVCaptureSession alloc]init];
if ([session canSetSessionPreset:AVCaptureSessionPresetHigh]) {
[session setSessionPreset:AVCaptureSessionPresetHigh];
}
//2扁掸、添加輸入和輸出設(shè)備
if([session canAddInput:self.input]){
[session addInput:self.input];
}
if([session canAddOutput:self.output]){
[session addOutput:self.output];
}
//3、設(shè)置這次掃描的數(shù)據(jù)類型
self.output.metadataObjectTypes = self.output.availableMetadataObjectTypes;
//4最域、創(chuàng)建預(yù)覽層
AVCaptureVideoPreviewLayer *layer = [AVCaptureVideoPreviewLayer layerWithSession:session];
layer.frame = self.view.bounds;
[self.view.layer insertSublayer:layer atIndex:0];
//5谴分、創(chuàng)建周圍的遮罩層
CALayer *maskLayer = [[CALayer alloc]init];
maskLayer.frame = self.view.bounds;
//此時(shí)設(shè)置的顏色就是中間掃描區(qū)域最終的顏色
maskLayer.backgroundColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:0.2].CGColor;
maskLayer.delegate = self;
[self.view.layer insertSublayer:maskLayer above:layer];
//讓代理方法調(diào)用 將周圍的蒙版顏色加深
[maskLayer setNeedsDisplay];
//6、關(guān)鍵設(shè)置掃描的區(qū)域 方法一:自己計(jì)算
// CGFloat x = (self.view.bounds.size.width - self.scanViewH.constant) * 0.5;
//
// CGFloat y = (self.view.bounds.size.height- self.scanViewH.constant) * 0.5;
//
// CGFloat w = self.scanViewH.constant;
//
// CGFloat h = w;
//
//
// self.output.rectOfInterest = CGRectMake(y/self.view.bounds.size.height, x/self.view.bounds.size.width, h/self.view.bounds.size.height, w/self.view.bounds.size.width);
//6镀脂、關(guān)鍵設(shè)置掃描的區(qū)域狸剃,方法二:直接轉(zhuǎn)換,但是要在 AVCaptureInputPortFormatDescriptionDidChangeNotification 通知里設(shè)置,否則 metadataOutputRectOfInterestForRect: 轉(zhuǎn)換方法會(huì)返回 (0, 0, 0, 0)狗热。
__weak __typeof(&*self)weakSelf = self;
[[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureInputPortFormatDescriptionDidChangeNotification object:nil queue:[NSOperationQueue currentQueue] usingBlock: ^(NSNotification *_Nonnull note) {
weakSelf.output.rectOfInterest = [weakSelf.layer metadataOutputRectOfInterestForRect:self.scanView.frame];
}];
//7钞馁、開始掃描
[session startRunning];
self.session = session;
self.layer = layer;
self.maskLayer = maskLayer;
}
-(void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - 代理方法
/**
* 如果掃描到了二維碼 回調(diào)該代理方法
*/
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
if(metadataObjects.count > 0 && metadataObjects != nil){
AVMetadataMachineReadableCodeObject *metadataObject = [metadataObjects lastObject];
NSString *result = metadataObject.stringValue;
self.result.text = result;
[self.session stopRunning];
[self.scanline removeFromSuperview];
}
}
/**
* 蒙版中間一塊要空出來
*/
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
if (layer == self.maskLayer) {
UIGraphicsBeginImageContextWithOptions(self.maskLayer.frame.size, NO, 1.0);
//蒙版新顏色
CGContextSetFillColorWithColor(ctx, [UIColor colorWithRed:0 green:0 blue:0 alpha:0.6].CGColor);
//補(bǔ)充當(dāng)前填充顏色的rect
CGContextFillRect(ctx, self.maskLayer.frame);
//轉(zhuǎn)換坐標(biāo)
CGRect scanFrame = [self.view convertRect:self.scanView.frame fromView:self.scanView.superview];
//空出中間一塊
CGContextClearRect(ctx, scanFrame);
}
}
@end
特別說明
在iOS10的權(quán)限問題,因?yàn)閕OS10對(duì)權(quán)限的要求更高匿刮,如果我們不設(shè)置相應(yīng)的權(quán)限問題僧凰,會(huì)直接導(dǎo)致程序崩潰,這時(shí)熟丸,我們需要在plist文件中加上相機(jī)權(quán)限的描述:
<key>NSCameraUsageDescription</key>
<string>cameraDesciption</string>