标签:
本文中我们介绍一下如何在iOS平台实现二维码扫描,我们可以借助一些优秀的第三方库,比如ZBar SDK和ZXing。在iOS7.0之后,我们也可以使用iOS系统原生扫描,通过AVFoundation框架来实现二维码扫描,不过AVFoundation框架不能识别相册中的二维码图片。
我们先来介绍一下如何使用AVFoundation框架实现二维码扫描。在iOS7之后,苹果自身提供了二维码的扫描功能,从效率上来说,原生的二维码远高于这些第三方框架。之前我们使用AVFoundation框架实现过拍照功能,基本的原理差不多类似;需要AVCaptureSession对象管理输入流和输出流;需要一个AVCaptureVideoPreviewLayer对象来显示信息。基本流程如下:
图中涉及到的几个相关类的说明如下:
下面我们看一下具体的实现步骤:
示例代码:
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
@interface ViewController ()<AVCaptureMetadataOutputObjectsDelegate>
@property (weak, nonatomic) IBOutlet UISwitch *lightSwitch;// 闪光开关
@property (nonatomic) AVCaptureSession *captureSession;//捕获的管理会话
@property (nonatomic) AVCaptureVideoPreviewLayer *previewLayer;// 预览图层
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
// 开始二维码扫描
- (IBAction)startScan:(UIButton *)sender {
// 1. 初始化捕获会话,并添加输入对象
self.captureSession = [[AVCaptureSession alloc] init];
// 2. 初始化捕获设备对象AVCaptureDevice
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
// 3. 初始化捕获设备的输入对象AVCaptureDeviceInput
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
if (input == nil) {
NSLog(@"输入对象初始化失败");
return;
}
// 将输入对象添加到会话
[self.captureSession addInput:input];
// 4. 初始化输出对象AVCaptureMetadataOutput
AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc] init];
// 将输出对象添加到会话
[self.captureSession addOutput:output];
// 设置输出对象源数据类型:AVMetadataObjectTypeQRCode条码类型
[output setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]];
// 设置输出对象代理,并实现代理方法
[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
// 设置扫码区域为预览区域 默认全屏扫描
CGFloat w = self.view.frame.size.width;
CGFloat h = self.view.frame.size.height;
// rectOfInterest属性:区域大小是相对于设备的大小的,默认值是(0, 0, 1, 1)。但是区域的坐标系统X和Y是互换的,同时,宽高的比例也互换了。
[output setRectOfInterest:CGRectMake((h/2 -100)/h, (w/2 -100)/w, 200/h, 200/w)];
NSLog(@"%@",NSStringFromCGRect(output.rectOfInterest));
// 5. 初始化预览图层
self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession];
self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;// 拉伸属性
self.previewLayer.frame = self.view.bounds;
[self.view.layer addSublayer:self.previewLayer];
// 6.开始会话
[self.captureSession startRunning];
// 关闭闪光灯
self.lightSwitch.on = NO;
[self.view bringSubviewToFront:self.lightSwitch];
}
#pragma mark -
#pragma mark - AVCaptureMetadataOutputObjectsDelegate
// 此方法是在识别到QRCode,并且完成转换。如果QRCode的内容越大,转换需要的时间就越长
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
if (metadataObjects != nil && [metadataObjects count] > 0) {
// 扫码成功 停止扫描,
[self.captureSession stopRunning];
// 获取扫码结果:
AVMetadataMachineReadableCodeObject *metadataObj = [metadataObjects objectAtIndex:0];
NSString *result;
if ([[metadataObj type] isEqualToString:AVMetadataObjectTypeQRCode]) {
result = metadataObj.stringValue;
NSLog(@"%@",result);
} else {
NSLog(@"不是二维码");
}
}
}
#pragma mark -
#pragma mark - 开启闪光灯
- (IBAction)lightOpenOrClose:(UISwitch *)sender {
BOOL isOpen = sender.on;
// 获取设备
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if ([device hasTorch]) {
[device lockForConfiguration:nil];
if (isOpen) {
[device setTorchMode:AVCaptureTorchModeOn];
} else {
[device setTorchMode:AVCaptureTorchModeOff];
}
[device unlockForConfiguration];
}
}
@end
1.2 AVFoundation框架扫描二维码的功能封装
上面是系统自带的二维码扫描的基本功能实现,下面我们来实现一下在实际开发中二维码扫描的实际效果,不过就是添加了一条扫描的线条。
示例代码:
Scan2CodeViewController.h
#import <UIKit/UIKit.h>
@protocol ScanImageViewDelegate<NSObject>
- (void)reportScanResult:(NSString *)result;
@end
@interface Scan2CodeViewController : UIViewController
@property (nonatomic,weak)id <ScanImageViewDelegate>delegate;//代理方法传递数据
@end
示例代码:
Scan2CodeViewController.m
#import "Scan2CodeViewController.h"
#import <AVFoundation/AVFoundation.h>
#define SCANVIEW_EdgeTop 70.0
#define SCANVIEW_EdgeLeft 50.0
#define TINTCOLOR_ALPHA 0.2 //浅色透明度
#define DARKCOLOR_ALPHA 0.3 //深色透明度
#define VIEW_WIDTH [UIScreen mainScreen].bounds.size.width
#define VIEW_HEIGHT [UIScreen mainScreen].bounds.size.height
static const char *kScanQRCodeQueueName = "ScanQRCodeQueue";
@interface Scan2CodeViewController () <AVCaptureMetadataOutputObjectsDelegate>{
//设置扫描画面
UIView *_scanView;
NSTimer *_timer;
UIView *_QrCodeline;
UIView *_QrCodeline1;
UIImageView *_scanCropView;//扫描窗口
UIButton *_lightButton;//灯光按钮
AVCaptureSession *_captureSession;
AVCaptureVideoPreviewLayer *_videoPreviewLayer;
}
@end
@implementation Scan2CodeViewController
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self stopTimer];
}
- (void)viewDidLoad {
[super viewDidLoad];
//初始化扫描界面
[self setScanView];
// 开始扫描
[self startReading];
//启动定时器
[self createTimer];
}
- (BOOL)startReading
{
// 获取 AVCaptureDevice 实例
NSError * error;
AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
// 初始化输入流
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
if (!input) {
NSLog(@"%@", [error localizedDescription]);
return NO;
}
// 创建会话
_captureSession = [[AVCaptureSession alloc] init];
//提高图片质量为1080P,提高识别效果
_captureSession.sessionPreset = AVCaptureSessionPreset1920x1080;
// 添加输入流
[_captureSession addInput:input];
// 初始化输出流
AVCaptureMetadataOutput *captureMetadataOutput = [[AVCaptureMetadataOutput alloc] init];
//设置扫描范围
captureMetadataOutput.rectOfInterest =CGRectMake((_scanCropView.frame.origin.y-10)/VIEW_HEIGHT, (_scanCropView.frame.origin.x-10)/VIEW_WIDTH, (_scanCropView.frame.size.width+10)/VIEW_HEIGHT, (_scanCropView.frame.size.height+10)/VIEW_WIDTH);
// 添加输出流
[_captureSession addOutput:captureMetadataOutput];
// 创建dispatch queue.
dispatch_queue_t dispatchQueue;
dispatchQueue = dispatch_queue_create(kScanQRCodeQueueName, NULL);
[captureMetadataOutput setMetadataObjectsDelegate:self queue:dispatchQueue];
// 设置元数据类型 AVMetadataObjectTypeQRCode
[captureMetadataOutput setMetadataObjectTypes:[NSArray arrayWithObject:AVMetadataObjectTypeQRCode]];
// 创建输出对象
_videoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];
[_videoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[_videoPreviewLayer setFrame:_scanView.layer.bounds];
[_scanView.layer insertSublayer:_videoPreviewLayer atIndex:0];
// 开始会话
[_captureSession startRunning];
return YES;
}
- (void)stopReading
{
// 停止会话
[_captureSession stopRunning];
_captureSession = nil;
}
#pragma mark -
#pragma mark - AVCaptureMetadataOutputObjectsDelegate
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
fromConnection:(AVCaptureConnection *)connection
{
if (metadataObjects != nil && [metadataObjects count] > 0) {
AVMetadataMachineReadableCodeObject *metadataObj = [metadataObjects objectAtIndex:0];
NSString *result;
if ([[metadataObj type] isEqualToString:AVMetadataObjectTypeQRCode]) {
result = metadataObj.stringValue;
} else {
NSLog(@"不是二维码");
}
//调用代理对象的协议方法来实现数据传递
[self dismissViewControllerAnimated:YES completion:nil];
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.delegate respondsToSelector:@selector(reportScanResult:)]) {
[self.delegate reportScanResult:result];
}
});
[self stopReading];
}
return;
}
- (void)createTimer
{
_timer=[NSTimer scheduledTimerWithTimeInterval:2.2 target:self selector:@selector(moveUpAndDownLine) userInfo:nil repeats:YES];
}
- (void)stopTimer
{
if ([_timer isValid] == YES) {
[_timer invalidate];
_timer = nil;
}
}
// 二维码的扫描区域
- (void)setScanView
{
_scanView=[[UIView alloc] initWithFrame:CGRectMake(0,0, VIEW_WIDTH,VIEW_HEIGHT )];
_scanView.backgroundColor=[UIColor clearColor];
[self.view addSubview:_scanView];
//最上部view
UIView *upView = [[UIView alloc] initWithFrame:CGRectMake(0,0, VIEW_WIDTH,SCANVIEW_EdgeTop)];
upView.alpha =TINTCOLOR_ALPHA;
upView.backgroundColor = [UIColor blackColor];
[_scanView addSubview:upView];
//左侧的view
UIView *leftView = [[UIView alloc] initWithFrame:CGRectMake(0, SCANVIEW_EdgeTop, SCANVIEW_EdgeLeft,VIEW_WIDTH - 2 * SCANVIEW_EdgeLeft)];
leftView.alpha =TINTCOLOR_ALPHA;
leftView.backgroundColor = [UIColor blackColor];
[_scanView addSubview:leftView];
// 中间扫描区
_scanCropView=[[UIImageView alloc] initWithFrame:CGRectMake(SCANVIEW_EdgeLeft,SCANVIEW_EdgeTop, VIEW_WIDTH - 2 * SCANVIEW_EdgeLeft, VIEW_WIDTH - 2 * SCANVIEW_EdgeLeft)];
//scanCropView.image=[UIImage imageNamed:@""];
_scanCropView.layer.borderColor=[UIColor greenColor].CGColor;
_scanCropView.layer.borderWidth=2.0;
_scanCropView.backgroundColor=[UIColor clearColor];
[_scanView addSubview:_scanCropView];
//右侧的view
UIView *rightView = [[UIView alloc] initWithFrame:CGRectMake(VIEW_WIDTH - SCANVIEW_EdgeLeft,SCANVIEW_EdgeTop, SCANVIEW_EdgeLeft, VIEW_WIDTH - 2 * SCANVIEW_EdgeLeft)];
rightView.alpha =TINTCOLOR_ALPHA;
rightView.backgroundColor = [UIColor blackColor];
[_scanView addSubview:rightView];
//底部view
UIView *downView = [[UIView alloc] initWithFrame:CGRectMake(0, VIEW_WIDTH - 2 * SCANVIEW_EdgeLeft + SCANVIEW_EdgeTop, VIEW_WIDTH, VIEW_HEIGHT - (VIEW_WIDTH - 2 * SCANVIEW_EdgeLeft + SCANVIEW_EdgeTop))];
//downView.alpha = TINTCOLOR_ALPHA;
downView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:TINTCOLOR_ALPHA];
[_scanView addSubview:downView];
//用于说明的label
UILabel *labIntroudction= [[UILabel alloc] init];
labIntroudction.backgroundColor = [UIColor clearColor];
labIntroudction.frame=CGRectMake(0,5, VIEW_WIDTH,20);
labIntroudction.numberOfLines=1;
labIntroudction.font=[UIFont systemFontOfSize:15.0];
labIntroudction.textAlignment=NSTextAlignmentCenter;
labIntroudction.textColor=[UIColor whiteColor];
labIntroudction.text=@"将二维码对准方框,即可自动扫描";
[downView addSubview:labIntroudction];
//取消button
UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
cancelButton.backgroundColor =[[UIColor blackColor] colorWithAlphaComponent:DARKCOLOR_ALPHA];
cancelButton.frame =CGRectMake(0, 90, VIEW_WIDTH, 40);
cancelButton.titleLabel.font =[UIFont systemFontOfSize:15.0];
cancelButton.titleLabel.textAlignment = NSTextAlignmentCenter;
[cancelButton setTitle:@"取消" forState:UIControlStateNormal];
[cancelButton addTarget:self action:@selector(cancelAction) forControlEvents:UIControlEventTouchUpInside];
[downView addSubview:cancelButton];
//用于开关灯操作的button
_lightButton = [UIButton buttonWithType:UIButtonTypeCustom];
_lightButton.frame =CGRectMake(0, 40, VIEW_WIDTH, 40);
[_lightButton setTitle:@"开启闪光灯" forState:UIControlStateNormal];
[_lightButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
_lightButton.backgroundColor =[[UIColor blackColor] colorWithAlphaComponent:DARKCOLOR_ALPHA];
_lightButton.titleLabel.textAlignment=NSTextAlignmentCenter;
_lightButton.titleLabel.font=[UIFont systemFontOfSize:15.0];
[_lightButton addTarget:self action:@selector(openLight) forControlEvents:UIControlEventTouchUpInside];
[downView addSubview:_lightButton];
//画中间的基准线
_QrCodeline = [[UIView alloc] initWithFrame:CGRectMake(SCANVIEW_EdgeLeft, SCANVIEW_EdgeTop, VIEW_WIDTH- 2 * SCANVIEW_EdgeLeft, 2)];
_QrCodeline.backgroundColor = [UIColor greenColor];
[_scanView addSubview:_QrCodeline];
//画中间的基准线
_QrCodeline1 = [[UIView alloc] initWithFrame:CGRectMake(SCANVIEW_EdgeLeft, SCANVIEW_EdgeTop, VIEW_WIDTH- 2 * SCANVIEW_EdgeLeft, 2)];
_QrCodeline1.backgroundColor = [UIColor greenColor];
[_scanView addSubview:_QrCodeline1];
// 先让第二根线运动一次,避免定时器执行的时差,让用户感到启动App后,横线就开始移动
[UIView animateWithDuration:2.2 animations:^{
_QrCodeline1.frame = CGRectMake(SCANVIEW_EdgeLeft, VIEW_WIDTH - 2 * SCANVIEW_EdgeLeft + SCANVIEW_EdgeTop - 2, VIEW_WIDTH - 2 * SCANVIEW_EdgeLeft, 1);
}];
}
// 当地一根线到达底部时,第二根线开始下落运动,此时第一根线已经在顶部,当第一根线接着下落时,第二根线到达顶部.依次循环
- (void)moveUpAndDownLine
{
CGFloat Y = _QrCodeline.frame.origin.y;
if (Y == SCANVIEW_EdgeTop) {
[UIView animateWithDuration:2.2 animations:^{
_QrCodeline.frame = CGRectMake(SCANVIEW_EdgeLeft, VIEW_WIDTH - 2 * SCANVIEW_EdgeLeft + SCANVIEW_EdgeTop - 2, VIEW_WIDTH - 2 * SCANVIEW_EdgeLeft, 1);
}];
_QrCodeline1.frame = CGRectMake(SCANVIEW_EdgeLeft, SCANVIEW_EdgeTop, VIEW_WIDTH - 2 * SCANVIEW_EdgeLeft, 1);
}
else if (Y == VIEW_WIDTH - 2 * SCANVIEW_EdgeLeft + SCANVIEW_EdgeTop - 2) {
_QrCodeline.frame = CGRectMake(SCANVIEW_EdgeLeft, SCANVIEW_EdgeTop, VIEW_WIDTH - 2 * SCANVIEW_EdgeLeft, 1);
[UIView animateWithDuration:2.2 animations:^{
_QrCodeline1.frame = CGRectMake(SCANVIEW_EdgeLeft, VIEW_WIDTH - 2 * SCANVIEW_EdgeLeft + SCANVIEW_EdgeTop - 2, VIEW_WIDTH - 2 * SCANVIEW_EdgeLeft, 1);
}];
}
}
//照明灯光
-(void)openLight{
if ([_lightButton.titleLabel.text isEqualToString:@"开启闪光灯"]) {
[self systemLightSwitch:YES];
} else {
[self systemLightSwitch:NO];
}
}
- (void)systemLightSwitch:(BOOL)open
{
if (open) {
[_lightButton setTitle:@"关闭闪光灯" forState:UIControlStateNormal];
} else {
[_lightButton setTitle:@"开启闪光灯" forState:UIControlStateNormal];
}
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if ([device hasTorch]) {
[device lockForConfiguration:nil];
if (open) {
[device setTorchMode:AVCaptureTorchModeOn];
} else {
[device setTorchMode:AVCaptureTorchModeOff];
}
[device unlockForConfiguration];
}
}
// 取消button
- (void)cancelAction{
[self dismissViewControllerAnimated:YES completion:nil];
}
@end
在iOS7.0以前,大部分应用识别二维码都是通过第三方库来实现的。事实上,在主流APP中二维码/条形码的作用主要分三种表现形式来集成:
识别相册图片中的二维码图片。
首先我们先来看一下如何集成ZBarSDK:
第一步:将 ZBarSDK导入到工程,并集成相关的框架,
libiconv.dylib
如下图:
第二步:关闭代码优化
导入相关框架后,系统编译仍然不通过,提示的错误如下:
这是由于系统代码优化产生的错误,将Xcode的bitcode选项设置为No就可以通过编译了。
第三步:导入头文件,初始化ZBar相机控制器,并实现相关代理;
示例代码:
#import "ViewController.h"
#import "ZBarSDK.h"
@interface ViewController ()<ZBarReaderDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (IBAction)systemCamara:(id)sender {
// 初始化扫码视图控制器
ZBarReaderViewController *reader = [ZBarReaderViewController new];
// 设置代理
reader.readerDelegate = self;
// 设置识别类型
ZBarImageScanner *scanner = reader.scanner;
[scanner setSymbology:ZBAR_I25
config:ZBAR_CFG_ENABLE
to:0];
reader.showsZBarControls = YES;
// 推出扫码视图控制器
[self presentViewController:reader animated:YES completion:nil];
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {
id results = [info objectForKey:ZBarReaderControllerResults];
ZBarSymbol * symbol;
for(symbol in results) {
NSString *result = symbol.data;
NSLog(@"%@",result);
}
// 退出拍照界面
[picker dismissViewControllerAnimated:YES completion:nil];
}
@end
注释:
直接调用系统相机来实现二维码扫描虽然简单,但是存在极大的问题,那就是这样只能做全屏扫描,不能自定义二维码扫描的选取框。
项目中实现的二维码扫描功能我们发现都是可以设置扫描范围内的,下面我们来看一下如何自定义扫描的窗口。
示例代码:
#import "ViewController.h"
#import "ZBarSDK.h"
@interface ViewController ()<ZBarReaderViewDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
#pragma mark -
#pragma mark - 自定义扫描窗口
- (IBAction)scan2Code:(id)sender {
// 初始化扫描窗口
ZBarReaderView *readerView = [[ZBarReaderView alloc] init];
// 自定义大小
readerView.frame = CGRectMake(self.view.frame.size.width/2-100, self.view.frame.size.height/2-100, 200, 200);
// 设置代理
readerView.readerDelegate = self;
// 添加到父视图
[self.view addSubview:readerView];
// 二维码识别设置
ZBarImageScanner *scanner = readerView.scanner;
[scanner setSymbology:ZBAR_I25
config:ZBAR_CFG_ENABLE
to:0];
// 开始扫描
[readerView start];
}
#pragma mark - ZBarReaderViewDelegate
- (void)readerView:(ZBarReaderView *)readerView didReadSymbols:(ZBarSymbolSet *)symbols fromImage:(UIImage *)image {
// 获取扫描的标识
ZBarSymbol *symbol = nil;
for (symbol in symbols) {
NSString *result = symbol.data;
NSLog(@"%@",result);
}
// 停止扫描
[readerView stop];
}
@end
使用ZBarSDK可以直接选中相册中的二维码图片进行识别,这是系统自带的识别功能所不具备的。下面是识别相册中的二维码图片的实现方法。
示例代码:
#import "ViewController.h"
#import "ZBarSDK.h"
@interface ViewController ()<UIImagePickerControllerDelegate,UINavigationControllerDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
// 识别相册图片中的二维码
- (IBAction)pick2Code:(id)sender {
// 进入相册选中相册中的二维码图片
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;// 图片来源为相册
imagePicker.delegate = self;
// 进入相册
[self presentViewController:imagePicker animated:YES completion:nil];
}
#pragma mark -
#pragma mark - UIImagePickerControllerDelegate
// 选中图片后调用该方法
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {
// 获取选中的图片
UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
CGImageRef cgImage = image.CGImage;
ZBarReaderController *reader = [[ZBarReaderController alloc] init];
reader.delegate = self;
ZBarSymbol *symbol = nil;
for (symbol in [reader scanImage:cgImage]) {
NSString *resulot = symbol.data;
NSLog(@"%@",resulot);
}
// 识别结束后返回主界面
[picker dismissViewControllerAnimated:YES completion:nil];
}
@end
标签:
原文地址:http://blog.csdn.net/qq_32510689/article/details/51720990