内容简介:写在前面最近项目要实现相机扫描二维码功能,具体要求:1、扫描框 2、扫描动画 3、相册识别二维码 4、声音反馈。记得之前用过三方库做过类似功能,但是也是知其然不知其所以然,然后今天自己用原生api简单封装了一个二维码扫描控件。
写在前面
最近项目要实现相机扫描二维码功能,具体要求:1、扫描框 2、扫描动画 3、相册识别二维码 4、声音反馈。
记得之前用过三方库做过类似功能,但是也是知其然不知其所以然,然后今天自己用原生api简单封装了一个二维码扫描控件。
项目结构介绍
控件封装后主要结构如图:
如图中代码目录,vender里面放的是UIView+Frame分类,Resource里面放的是图片声音资源,TZImagePickerController是第三方相册,用来获取相册中的二维码识别的。主要的就是以QR开头的文件,我们具体说一说。
QRCode.h
这个文件主要放的是各个文件的头文件,方便在各处调用
#import "QRCodeScanManager.h" #import #import "QRLightManager.h" #import "QRCodeScanView.h" #import "QRCodeHelper.h"
QRLightManager
这个类是用来开启关闭闪光灯的
/** 打开手电筒 */ + (void)openFlashLight; /** 关闭手电筒 */ + (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 关闭手电筒
+ (void)closeFlashLight {
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if ([device hasTorch]) {
[device lockForConfiguration:nil];
[device setTorchMode:AVCaptureTorchModeOff];
[device unlockForConfiguration];
}
}
QRCodeScanView
这个类是将这个界面单独封装出来,便于自定义
在.h文件中有个枚举用来标识二维码扫描四周角标的位置:
typedef enum : NSUInteger {
CornerLoactionDefault,//默认与边框同中心点
CornerLoactionInside,//在边框线内部
CornerLoactionOutside,//在边框线外部
} CornerLoaction;
自定义view各个属性:
@property (nonatomic, strong) UIColor *borderColor;/** 边框颜色*/ @property (nonatomic, assign) CornerLoaction cornerLocation;/** 边角位置 默认default*/ @property (nonatomic, strong) UIColor *cornerColor;/** 边角颜色 默认正保蓝#32d2dc*/ @property (nonatomic, assign) CGFloat cornerWidth;/** 边角宽度 默认2.f*/ @property (nonatomic, assign) CGFloat backgroundAlpha;/** 扫描区周边颜色的alpha 默认0.5*/ @property (nonatomic, assign) CGFloat timeInterval;/** 扫描间隔 默认0.02*/ @property (nonatomic, strong) UIButton *lightBtn;/** 闪光灯*/
暴露外部调用的方法:
/** 添加定时器 */ - (void)addTimer; /** 移除定时器 */ - (void)removeTimer; /** 根据灯光判断 */ - (void)lightBtnChangeWithBrightnessValue:(CGFloat)brightnessValue;
初始化默认值:
- (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的边框和边角
//空白区域设置
[[[UIColor blackColor] colorWithAlphaComponent:self.backgroundAlpha] setFill];
UIRectFill(rect);
//获取上下文,并设置混合模式 -> kCGBlendModeDestinationOut
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(context, kCGBlendModeDestinationOut);
//设置空白区
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(borderX + 0.5 * borderLineW, borderY+ 0.5 * borderLineW, borderW - borderLineW, borderH - borderLineW)];
[bezierPath fill];
//执行混合模式
CGContextSetBlendMode(context, kCGBlendModeNormal);
//边框设置
UIBezierPath *borderPath = [UIBezierPath bezierPathWithRect:CGRectMake(borderX, borderY, borderW, borderH)];
borderPath.lineCapStyle = kCGLineCapButt;
borderPath.lineWidth = borderLineW;
[self.borderColor set];
[borderPath stroke];
CGFloat cornerLength = 20;
//左上角小图标
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;
}
}
}
闪光灯按钮点击事件,开启关闭闪光灯:
- (void)lightBtnClick:(UIButton *)btn {
btn.selected = !btn.selected;
if (btn.selected) {
[QRLightManager openFlashLight];
} else {
[QRLightManager closeFlashLight];
}
}
QRCodeScanManager
这个单例用来控制所有的关于二维码扫描的事件
初始开启session 会话
//设置二维码读取率 数据类型 当前控制器
- (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 参数不能为空" userInfo:nil];
[excp raise];
}
//1、获取摄像设备
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
//2、创建设备输入流
AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
//3、创建数据输出流
AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];
[metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
//3(1)、创建设备输出流
self.videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
[_videoDataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
// 设置扫描范围(每一个取值0~1,以屏幕右上角为坐标原点)
// 注:微信二维码的扫描范围是整个屏幕,这里并没有做处理(可不用设置); 如需限制扫描范围,打开下一句注释代码并进行相应调试
// metadataOutput.rectOfInterest = CGRectMake(0.05, 0.2, 0.7, 0.6);
//4、创建会话对象
_session = [[AVCaptureSession alloc] init];
//会话采集率:AVCaptureSessionPresetHigh
_session.sessionPreset = sessionPreset;
//5、添加设备输出流到会话对象
[_session addOutput:metadataOutput];
//5(1)添加设备输出流到会话对象;与3(1)构成识别光纤强弱
[_session addOutput:_videoDataOutput];
//6、添加设备输入流到会话对象
[_session addInput:deviceInput];
//7、设置数据输出类型,需要将数据输出添加到会话后,才能指定元数据类型,否则会报错
// 设置扫码支持的编码格式(如下设置条形码和二维码兼容)
// @[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code]
metadataOutput.metadataObjectTypes = metadataObjectTypes;
//8、实例化预览图层,传递_session是为了告诉图层将来显示什么内容
_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);//捕获到的结果集合 //亮度回调 - (void)brightnessChange:(GetBrightnessBlock)getBrightnessBlock; //扫描结果 - (void)scanResult:(ScanBlock)scanBlock;
- (void)startRunning {
[_session startRunning];
}
- (void)stopRunning {
[_session stopRunning];
}
需要遵循:/**
重置根据光线强弱值打开手电筒 delegate方法
*/
- (void)resetSampleBufferDelegate;
/**
取消根据光线强弱值打开手电筒的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上,代码里注释也写的很明白
使用功能
开启、结束定时器
开启、关闭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;
//光扫描结果回调
[_scanManager scanResult:^(NSArray *metadataObjects) {
if (metadataObjects != nil && metadataObjects.count > 0) {
[weakSelf.scanManager playSoundName:@"sound.caf"];
//obj 为扫描结果
AVMetadataMachineReadableCodeObject *obj = metadataObjects[0];
NSString *url = [obj stringValue];
NSLog(@"---url = :%@", url);
} else {
NSLog(@"暂未识别出扫描的二维码");
}
}];
//光纤变化回调
[_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可用于人脸识别)进行图片解析,从而使我们可以便捷的从相册中获取到二维码
// 声明一个 CIDetector,并设定识别类型 CIDetectorTypeQRCode
// 识别精度
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{CIDetectorAccuracy:CIDetectorAccuracyHigh}];
//取得识别结果
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];
}
作者:劉光軍_Shine
链接:https://www.jianshu.com/p/08733f2bfc26
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 智能化扫描场景分析—精细化扫描SQL注入漏洞
- 漏洞扫描“全覆盖”法则 | 被动扫描如何在资产发现中发挥作用?
- 开源扫描仪的工具箱:安全行业从业人员自研开源扫描器合集
- MySQL -- 全表扫描
- 漏洞扫描
- SMB扫描
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Building Social Web Applications
Gavin Bell / O'Reilly Media / 2009-10-1 / USD 34.99
Building a social web application that attracts and retains regular visitors, and gets them to interact, isn't easy to do. This book walks you through the tough questions you'll face if you're to crea......一起来看看 《Building Social Web Applications》 这本书的介绍吧!