iOS 二維碼掃描相關(guān)功能實(shí)現(xiàn)
寫在前面
最近項目要實(shí)現(xiàn)相機(jī)掃描二維碼功能,具體要求:1、掃描框 2、掃描動畫 3、相冊識別二維碼 4、聲音反饋。
記得之前用過三方庫做過類似功能,但是也是知其然不知其所以然,然后今天自己用原生api簡單封裝了一個二維碼掃描控件。
項目結(jié)構(gòu)介紹
控件封裝后主要結(jié)構(gòu)如圖:

如圖中代碼目錄,vender里面放的是UIView+Frame分類,Resource里面放的是圖片聲音資源,TZImagePickerController是第三方相冊,用來獲取相冊中的二維碼識別的。主要的就是以QR開頭的文件,我們具體說一說。
QRCode.h
這個文件主要放的是各個文件的頭文件,方便在各處調(diào)用
#import "QRCodeScanManager.h" #import #import "QRLightManager.h" #import "QRCodeScanView.h" #import "QRCodeHelper.h"
QRLightManager
這個類是用來開啟關(guān)閉閃光燈的
/** 打開手電筒 */ + (void)openFlashLight; /** 關(guān)閉手電筒 */ + (void)closeFlashLight;
#pragma mark 打開手電筒
+ (void)openFlashLight {
AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
if ([captureDevice hasTorch]) {
BOOL locked = [captureDevice lockForConfiguration:&error];
if (locked) {
captureDevice.torchMode = AVCaptureTorchModeOn;
[captureDevice unlockForConfiguration];
}
}
}
#pragma mark 關(guān)閉手電筒
+ (void)closeFlashLight {
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if ([device hasTorch]) {
[device lockForConfiguration:nil];
[device setTorchMode:AVCaptureTorchModeOff];
[device unlockForConfiguration];
}
}
QRCodeScanView
這個類是將這個界面單獨(dú)封裝出來,便于自定義

在.h文件中有個枚舉用來標(biāo)識二維碼掃描四周角標(biāo)的位置:
typedef enum : NSUInteger {
CornerLoactionDefault,//默認(rèn)與邊框同中心點(diǎn)
CornerLoactionInside,//在邊框線內(nèi)部
CornerLoactionOutside,//在邊框線外部
} CornerLoaction;
自定義view各個屬性:
@property (nonatomic, strong) UIColor *borderColor;/** 邊框顏色*/ @property (nonatomic, assign) CornerLoaction cornerLocation;/** 邊角位置 默認(rèn)default*/ @property (nonatomic, strong) UIColor *cornerColor;/** 邊角顏色 默認(rèn)正保藍(lán)#32d2dc*/ @property (nonatomic, assign) CGFloat cornerWidth;/** 邊角寬度 默認(rèn)2.f*/ @property (nonatomic, assign) CGFloat backgroundAlpha;/** 掃描區(qū)周邊顏色的alpha 默認(rèn)0.5*/ @property (nonatomic, assign) CGFloat timeInterval;/** 掃描間隔 默認(rèn)0.02*/ @property (nonatomic, strong) UIButton *lightBtn;/** 閃光燈*/
暴露外部調(diào)用的方法:
/** 添加定時器 */ - (void)addTimer; /** 移除定時器 */ - (void)removeTimer; /** 根據(jù)燈光判斷 */ - (void)lightBtnChangeWithBrightnessValue:(CGFloat)brightnessValue;
初始化默認(rèn)值:
- (void)initilize {
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
self.borderColor = [UIColor whiteColor];
_cornerLocation = CornerLoactionDefault;
_cornerColor = [UIColor colorWithRed:50/255.0f green:210/255.0f blue:220/255.0f alpha:1.0];
_cornerWidth = 2.0;
self.timeInterval = 0.02;
_backgroundAlpha = 0.5;
[self addSubview:self.lightBtn];
}
重寫view的drawRect方法,在這個方法里面繪制scanView的邊框和邊角
//空白區(qū)域設(shè)置
[[[UIColor blackColor] colorWithAlphaComponent:self.backgroundAlpha] setFill];
UIRectFill(rect);
//獲取上下文,并設(shè)置混合模式 -> kCGBlendModeDestinationOut
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(context, kCGBlendModeDestinationOut);
//設(shè)置空白區(qū)
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(borderX + 0.5 * borderLineW, borderY+ 0.5 * borderLineW, borderW - borderLineW, borderH - borderLineW)];
[bezierPath fill];
//執(zhí)行混合模式
CGContextSetBlendMode(context, kCGBlendModeNormal);
//邊框設(shè)置
UIBezierPath *borderPath = [UIBezierPath bezierPathWithRect:CGRectMake(borderX, borderY, borderW, borderH)];
borderPath.lineCapStyle = kCGLineCapButt;
borderPath.lineWidth = borderLineW;
[self.borderColor set];
[borderPath stroke];
CGFloat cornerLength = 20;
//左上角小圖標(biāo)
UIBezierPath *leftTopPath = [UIBezierPath bezierPath];
leftTopPath.lineWidth = self.cornerWidth;
[self.cornerColor set];
CGFloat insideExcess = fabs(0.5 * (self.cornerWidth - borderLineW));
CGFloat outsideExcess = 0.5 * (borderLineW + self.cornerWidth);
if (self.cornerLocation == CornerLoactionInside) {
[leftTopPath moveToPoint:CGPointMake(borderX + insideExcess, borderY + cornerLength + insideExcess)];
[leftTopPath addLineToPoint:CGPointMake(borderX + insideExcess, borderY + insideExcess)];
[leftTopPath addLineToPoint:CGPointMake(borderX + cornerLength + insideExcess, borderY + insideExcess)];
} else if (self.cornerLocation == CornerLoactionOutside) {
[leftTopPath moveToPoint:CGPointMake(borderX - outsideExcess, borderY + cornerLength - outsideExcess)];
[leftTopPath addLineToPoint:CGPointMake(borderX - outsideExcess, borderY - outsideExcess)];
[leftTopPath addLineToPoint:CGPointMake(borderX + cornerLength - outsideExcess, borderY - outsideExcess)];
} else {
[leftTopPath moveToPoint:CGPointMake(borderX, borderY + cornerLength)];
[leftTopPath addLineToPoint:CGPointMake(borderX, borderY)];
[leftTopPath addLineToPoint:CGPointMake(borderX + cornerLength, borderY)];
}
[leftTopPath stroke];
增加定時器以及開始動畫:
- (void)addTimer {
[self addSubview:self.scanningLine];
self.timer = [NSTimer timerWithTimeInterval:self.timeInterval target:self selector:@selector(beginAnimaiton) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
}
#pragma mark 動畫
- (void)beginAnimaiton {
static BOOL isOrignPostion = YES;
if (isOrignPostion) {
_scanningLine.y = 0;
isOrignPostion = NO;
[UIView animateWithDuration:self.timeInterval animations:^{
self->_scanningLine.y += 2;
} completion:nil];
} else {
if (_scanningLine.frame.origin.y >= 0) {
CGFloat scanContent_MaxY = self.frame.size.width;
if (_scanningLine.y >= scanContent_MaxY - 10) {
_scanningLine.y = 0;
isOrignPostion = YES;
} else {
[UIView animateWithDuration:0.02 animations:^{
self->_scanningLine.y += 2;
} completion:nil];
}
} else {
isOrignPostion = !isOrignPostion;
}
}
}
閃光燈按鈕點(diǎn)擊事件,開啟關(guān)閉閃光燈:
- (void)lightBtnClick:(UIButton *)btn {
btn.selected = !btn.selected;
if (btn.selected) {
[QRLightManager openFlashLight];
} else {
[QRLightManager closeFlashLight];
}
}
QRCodeScanManager
這個單例用來控制所有的關(guān)于二維碼掃描的事件
初始開啟session 會話
//設(shè)置二維碼讀取率 數(shù)據(jù)類型 當(dāng)前控制器
- (void)setupSessionPreset:(NSString *)sessionPreset metadataObjectTypes:(NSArray *)metadataObjectTypes currentController:(UIViewController *)currentController {
if (sessionPreset == nil || metadataObjectTypes == nil || currentController == nil) {
NSException *excp = [NSException exceptionWithName:@"excp" reason:@"setupSessionPreset:metadataObjectTypes:currentController: 方法中的 sessionPreset 參數(shù)不能為空" userInfo:nil];
[excp raise];
}
//1、獲取攝像設(shè)備
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
//2、創(chuàng)建設(shè)備輸入流
AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
//3、創(chuàng)建數(shù)據(jù)輸出流
AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];
[metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
//3(1)、創(chuàng)建設(shè)備輸出流
self.videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
[_videoDataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
// 設(shè)置掃描范圍(每一個取值0~1,以屏幕右上角為坐標(biāo)原點(diǎn))
// 注:微信二維碼的掃描范圍是整個屏幕,這里并沒有做處理(可不用設(shè)置); 如需限制掃描范圍,打開下一句注釋代碼并進(jìn)行相應(yīng)調(diào)試
// metadataOutput.rectOfInterest = CGRectMake(0.05, 0.2, 0.7, 0.6);
//4、創(chuàng)建會話對象
_session = [[AVCaptureSession alloc] init];
//會話采集率:AVCaptureSessionPresetHigh
_session.sessionPreset = sessionPreset;
//5、添加設(shè)備輸出流到會話對象
[_session addOutput:metadataOutput];
//5(1)添加設(shè)備輸出流到會話對象;與3(1)構(gòu)成識別光纖強(qiáng)弱
[_session addOutput:_videoDataOutput];
//6、添加設(shè)備輸入流到會話對象
[_session addInput:deviceInput];
//7、設(shè)置數(shù)據(jù)輸出類型,需要將數(shù)據(jù)輸出添加到會話后,才能指定元數(shù)據(jù)類型,否則會報錯
// 設(shè)置掃碼支持的編碼格式(如下設(shè)置條形碼和二維碼兼容)
// @[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code]
metadataOutput.metadataObjectTypes = metadataObjectTypes;
//8、實(shí)例化預(yù)覽圖層,傳遞_session是為了告訴圖層將來顯示什么內(nèi)容
_videoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session];
//保持縱橫比;填充層邊界
_videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
CGFloat x = 0;
CGFloat y = 0;
CGFloat w = [UIScreen mainScreen].bounds.size.width;
CGFloat h = [UIScreen mainScreen].bounds.size.height;
_videoPreviewLayer.frame = CGRectMake(x, y, w, h);
[currentController.view.layer insertSublayer:_videoPreviewLayer atIndex:0];
//9、啟動會話
[_session startRunning];
}
block:
typedef void(^GetBrightnessBlock)(CGFloat brightness);//用來向外部傳遞捕獲到的亮度值以便于識別何時開啟閃光燈, typedef void(^ScanBlock)(NSArray *metadataObjects);//捕獲到的結(jié)果集合 //亮度回調(diào) - (void)brightnessChange:(GetBrightnessBlock)getBrightnessBlock; //掃描結(jié)果 - (void)scanResult:(ScanBlock)scanBlock;
- (void)startRunning {
[_session startRunning];
}
- (void)stopRunning {
[_session stopRunning];
}
需要遵循:/**
重置根據(jù)光線強(qiáng)弱值打開手電筒 delegate方法
*/
- (void)resetSampleBufferDelegate;
/**
取消根據(jù)光線強(qiáng)弱值打開手電筒的delegate方法
*/
- (void)cancelSampleBufferDelegate;
- (void)resetSampleBufferDelegate {
[_videoDataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
}
- (void)cancelSampleBufferDelegate {
[_videoDataOutput setSampleBufferDelegate:nil queue:dispatch_get_main_queue()];
}
#pragma mark 播放掃描提示音
- (void)playSoundName:(NSString *)name {
NSString *audioFile = [[NSBundle mainBundle] pathForResource:name ofType:nil];
NSURL *fileUrl = [NSURL fileURLWithPath:audioFile];
SystemSoundID soundID = 0;
AudioServicesCreateSystemSoundID((__bridge CFURLRef)(fileUrl), &soundID);
AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallback, NULL);
AudioServicesPlaySystemSound(soundID); // 播放音效
}
void soundCompleteCallback(SystemSoundID soundID, void *clientData){
}
注:這里只是截取部分重要代碼,具體功能我會將代碼放到GitHub上,代碼里注釋也寫的很明白
使用功能
開啟、結(jié)束定時器
開啟、關(guān)閉session會話
啟用、移除sampleBuffer代理
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.scanView addTimer];
[_scanManager startRunning];
[_scanManager resetSampleBufferDelegate];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.scanView removeTimer];
[_scanManager stopRunning];
[_scanManager cancelSampleBufferDelegate];
}
初始化scanManager
- (void)setupScanManager {
self.scanManager = [QRCodeScanManager sharedManager];
NSArray *arr = @[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code];
[_scanManager setupSessionPreset:AVCaptureSessionPreset1920x1080 metadataObjectTypes:arr currentController:self];
__weak __typeof(self)weakSelf = self;
//光掃描結(jié)果回調(diào)
[_scanManager scanResult:^(NSArray *metadataObjects) {
if (metadataObjects != nil && metadataObjects.count > 0) {
[weakSelf.scanManager playSoundName:@"sound.caf"];
//obj 為掃描結(jié)果
AVMetadataMachineReadableCodeObject *obj = metadataObjects[0];
NSString *url = [obj stringValue];
NSLog(@"---url = :%@", url);
} else {
NSLog(@"暫未識別出掃描的二維碼");
}
}];
//光纖變化回調(diào)
[_scanManager brightnessChange:^(CGFloat brightness) {
[weakSelf.scanView lightBtnChangeWithBrightnessValue:brightness];
}];
}
從相冊識別二維碼:
//借助第三方相冊
- (void)albumBtnClick {
TZImagePickerController *pickerController = [[TZImagePickerController alloc] initWithMaxImagesCount:1 delegate:self];
__weak __typeof(self)weakSelf = self;
[pickerController setDidFinishPickingPhotosHandle:^(NSArray *photos, NSArray *assets, BOOL isSelectOriginalPhoto) {
UIImage *image = photos[0];
// CIDetector(CIDetector可用于人臉識別)進(jìn)行圖片解析,從而使我們可以便捷的從相冊中獲取到二維碼
// 聲明一個 CIDetector,并設(shè)定識別類型 CIDetectorTypeQRCode
// 識別精度
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{CIDetectorAccuracy:CIDetectorAccuracyHigh}];
//取得識別結(jié)果
NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image.CGImage]];
NSString *resultStr;
if (features.count == 0) {
NSLog(@"暫未識別出二維碼");
} else {
for (int index = 0; index < [features count]; index++) {
CIQRCodeFeature *feature = [features objectAtIndex:index];
resultStr = feature.messageString;
}
NSLog(@"---url:%@", resultStr);
}
}];
[self presentViewController:pickerController animated:YES completion:nil];
}
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
iOS schem與Universal Link 調(diào)試時踩坑解決記錄
這篇文章主要為大家介紹了iOS schem與Universal Link 調(diào)試時踩坑解決記錄,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
詳解iOS 用于解決循環(huán)引用的block timer
這篇文章主要介紹了詳解iOS 用于解決循環(huán)引用的block timer,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12
React Native學(xué)習(xí)教程之Modal控件自定義彈出View詳解
這篇文章主要給大家介紹了關(guān)于React Native學(xué)習(xí)教程之Modal控件自定義彈出View的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用React Native具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10
iOS之?dāng)?shù)據(jù)解析之XML解析詳解
本篇文章主要介紹了iOS之?dāng)?shù)據(jù)解析之XML解析詳解,XML解析常見的兩種方式:DOM解析和SAX解析,有興趣的可以了解一下。2016-12-12

