[翻 + 原] 如何使用AVFoundation框架扫描QR Code

译注:本文翻译自http://www.appcoda.com/qr-code-ios-programming-tutorial/。由于文章本身采用xocde 5.0实现,所以我针对现有xcode 6做了部分更新,主要是采用了size class,不需要创建两个storyboard,同时增加了扫描区域实现。

iOS7带来了相对于其他移动操作系统的大量新特性。包含了主要更新和轻微的改善,对于开发者提供了新的或更新的框架和API,同时对于用户提供了更丰富的体验。AVFoundation框架就是其中之一,它拥有强大的功能和新添加的类,AVFoundation其中一个新特性是实时读取条形码的能力。对于教程,我们特别感兴趣的是QR Code类型的条形码。

Quick Response Code(QR Code)是一种2D条形码。相对于著名的垂直条形码,QR Code设计为横竖两个方向。二维码数据可以被特定设备,或者在移动设备(智能手机,平板电脑)上使用适当的软件读取。QR Code可以存储大量数据(相对于传统条形码),因此可以用它编码各种信息。下面是一些典型的QR Code编码内容:

  • URL
  • 电话号码
  • 简单文本
  • 短信
  • QR Code主要用于市场营销,但不局限于此。你也可以为网站创建QR Code,或者任何你需要的地方。
    qr-code-over-view
    感谢iOS 7,创建扫描和读取QR Code的应用程序是小菜一碟。因此,如果你从来没有创建过这类app,或者你感兴趣学习更多,那么就请继续阅读!

    演示应用程序概述

    我们在教程中实现的demo相当简单明了,它是通用的程序,意味着同时支持iPhone和iPad,因此,你可以在任何iOS 7的设备上安装它。下面显示iPhone上结果图:
    qr-code-iphone-preview
    对于iPad,界面或多或少相似的:
    qr-code-ipad-preview
    在我继续讨论上面演示demo之前,强调一点是很重要的,任何条形码扫描,包括QR codes,都是完全基于视频捕捉,这就是为什么AVFoundation框架支持条形码读取功能。请记住这一点,这是整个应用程序的起点。

    虽然并不难看出interface的所有子视图是如何布局的,我还是会快速介绍一下他们,因此,你会很清楚我们的app包含哪些内容。

    因此,从头开始,首先,有一个UIView视图,它将会成为设备视频捕捉的预览层。其中包含一个UILabel,简单了展示了通过点击启动按钮来扫描二维码。旁边,另一个Label扮演了二维码读取的状态日志。最后底部包含了一个toolbar,其中包含了一个bar button,这个是用来开始和暂停抓取的。

    这里是演示应用程序将如何工作的:
    最初,当应用程序启动时,首先显示上面显示的画面。通过点击工具栏上的start按钮,将会初始化一个视频捕捉会话,用来扫描QR codes。一旦出现这种情况,start按钮的标题改变为Stop,同时它的功能变成在任何时间取消捕捉。

    状态label将会显示三种不同的消息,依赖当前的扫描状态:

  • 尚未执行扫描。
  • 正在执行扫描。
  • 当扫描结束后,所包含了的信息已经被解析,并准备来显示。
  • 同时,当QR code被成功读取后,将会有声音特效使我们的app更加生动。
    请注意,此应用程序不能在模拟器测试,也不能在没有摄像头的设备运行,因为一切都是基于实时视频捕捉。因此,如果你想看应用程序的效果,必须要连接你的设备来运行它。

    构建Demo App

    准备好了?让我们启动Xcode,在环境界面上,选择create a new project。
    qr_code_xcode_welcome
    在iOS section工程创建向导中,选择Application类,并选择Single View Application模版,然后点击下一步。
    qr_code_new_project_template
    下一步,输入工程名称。在Product Name中输入QRCodeReader。同时在Devices中,选择Universal,来让你的app同时支持iPhone和iPad设备。选择下一步继续:
    qr_code_new_project_name
    在最后一个窗口,选择功能保存的目录并点击创建按钮。

    Xcode为你创建了包含必要初始化的工程,包含了默认的配置。现在我们开始构建我们的App。

    设置用户界面

    译注:此段是关于布局子试图的样式内容,对于xcode 6已经有些out了,我采用size class完成了相应的布局,同时支持iPad,iPhone,并且支持横竖屏切换。具体autolayout和size class布局,请参照最后的demo工程。效果参照下图:
    屏幕快照 2015-05-19 上午9.10.34

    译注:对于源文章的代码,本人不是很认同这种风格,所以做了部分修改。
    

    到目前为止,一切都很好。对于两种设备界面我们都很好的支持了。但是我们还没有完成。我们需要创建IBOutlet方法连接我们需要在代码中访问的控件。打开ViewController.m文件,添加如下IBOutlet属性声明:

    @interface ViewController()
    
    @property (weak, nonatomic) IBOutlet UIView *viewPreview;
    @property (weak, nonatomic) IBOutlet UILabel *lblStatus;
    @property (weak, nonatomic) IBOutlet UIBarButtonItem *bbitemStart;
    
    @end
    

    如你所见,我们需要视频包含视图,状态Label和工具栏的开始按钮的属性。除了这些属性,现在也是时候声明IBAction方法了,我们用来连接工具栏的开始按钮,为了开始和停止视频捕捉。(译注:此处其实没必要声明。)

    - (IBAction)startStopReading:(id)sender;
    

    现在我们来连接它们。打开Main.storyboard,连接相关属性。(译注:此处略去部分翻译。)
    qr_code_iboutlet_connect

    扫描QR Code实现
    到现在为止,我们已经创建了工程,设置好了界面,我们已经完成了我们应用程序的控件的所有配置。现在,是时候来深入点并编写代码了。
    最佳开始写代码的点就是我们的IBAction方法(startStopReading:)。已经声明好了,并且连接了状态栏的start按钮。但是,在我们实现它之前,让我们回忆下,当我向你展示demo的工作方式时,我说过,当视频捕捉开始时,start按钮将会变成stop按钮,反之亦然。
    从程序上来说,这意味着,我们需要找到方法让app知道什么时候调用start功能,声么时候调用stop功能。有比定义标记更好的方式吗?

    如果你尚未完成,打开ViewController.m。定位到开头部分,在私有接口部分,添加如下标记:

    @interface ViewController ()
    @property (nonatomic) BOOL isReading;
    @end
    

    通过它的名字就可以看出,当isReading为NO(false)时,app没有扫描QR code,当点击start按钮时会启动视频捕捉。当isReading为YES(true)时,相反的过程将会发生。我强烈建议,当定义成员变量时,紧接着时初始化它们,后面很有可能你会忘记这些。因此,让我们在viewDidLoad方法中初始化我们的flag:

    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        self.isReading = NO;
    }
    

    现在,我们已经完全准备好来实现startStopReading: 方法了。键入如下代码段,我们将会稍后讨论它:

    - (IBAction)startStopReading:(id)sender
    {
        if (!self.isReading) {
            if ([self startReading]) {
                [self.bbitemStart setTitle:@"Stop"];
                [self.lblStatus setText:@"Scanning for QR Code..."];
            }
        }
        else{
            [self stopReading];
            [self.bbitemStart setTitle:@"Start!"];
        }
    
        self.isReading = !self.isReading;
    }
    

    首先,我们检查isReading是否为NO,表示当前没有启动QR code扫描。如果是这样的情况,我们将会调用startReading方法,我们稍后实现它。如果调用此方法一切顺理,我们改变工具栏按钮的标题为Stop,同时修改status label的文字。然而,如果App当前正在扫描QR code,并且isReading标记为YES,我们调用stopReading方法(同样稍后实现),同时修改状态栏按钮为Start!。最后,不管生么情况,我们都设置isReading为当前值的反值。

    正如你刚才看到,我们使用了两个没有实现的方法。我们将从startReading开始,正如你想的,这个是App最重要的方法之一。首先,我们需要定义它,定位到私有接口section,添加如下定义(译注:此处操作很多余呀!):

    @interface ViewController ()
    @property (nonatomic) BOOL isReading;
    
    -(BOOL)startReading;
    
    @end
    

    在我们实现它之前,不要离开这里。我们需要声明一些我们下一步需要的对象。因此,添加如下新的代码段:

    @interface ViewController ()
    @property (nonatomic) BOOL isReading;
    
    @property (nonatomic, strong) AVCaptureSession *captureSession;
    @property (nonatomic, strong) AVCaptureVideoPreviewLayer *videoPreviewLayer;
    
    -(BOOL)startReading;
    @end
    

    这时,Xcode会抱怨AVCaptureSession类和AVCaptureVideoPreviewLayer类。因为我们还没有导入AVFoundation框架(译注:这里应该指的是头文件定义)。在我们修正它之前。首先在viewDidLoad方法中初始化captureSession对象为空。

    - (void)viewDidLoad
    {
        [super viewDidLoad];
        self.isReading = NO;
        
        self.captureSession = nil;   ///<译注:没必要初始化,默认就是空
    }
    

    现在打开ViewController.h,在头部添加AVFoundation头文件。

    #import <AVFoundation/AVFoundation.h>   ///<译注:更好的方式实在ViewController.m声明,见代码
    

    你有没有注意到这里很奇怪?Xcode没有显示任何错误信息,在我们引入头文件后,尽管我们还没有在工程中链接AVFoundation框架,如下所示:
    qr_code_frameworks

    那么,到底这里发生了什么,我们遇到了Xcode 5的新的很酷的特性,更确切来说是新的编译器特性。无需理解它的细节,只是使用它。这叫做Auto Linking。他会自动默默的在我们的工程中链接任何我们需要的框架(大多数框架)。

    说了这么多,让我们在ViewController.m做更多的事情,修改@interface行如下:

    @interface ViewController() <AVCaptureMetadataOutputObjectsDelegate>
    

    我们这么做是因为我们想我们的类符合AVCaptureMetadataOutputObjectsDelegate代理。稍后有关于它的更多内容。

    让我们回到从前,并开始实现startReading方法。为了实现它,我们打开ViewController.m文件。我相信,从这里开始,对于从来没有在app中实现视频支持的人来非常重要,下面的过程直到某一段才会熟悉。对于那么从来没有在项目中使用过AVFoundation框架和视频支持的人来说,我强烈建议你们访问苹果文档,来阅读一些关于它的知识。

    添加如下代码段:

    - (BOOL)startReading {
        NSError *error;
    
        AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    
        return YES;
    }
    

    我们刚才做的是添加了AVCaptureDevice类实例并指定了媒体类型为AVMediaTypeVideo。通俗来讲,我们访问我们饿设备,同时告知我们将要捕捉视频,NSError对象将会稍后使用。

    现在添加如下几行:

        AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
    
        if (!input) {
            NSLog(@"%@", [error localizedDescription]);
            return NO;
        }
    

    这里,我们创建AVCaptureDeviceInput实例,来指定输入设别。在我们的示例中,我们使用相机作为输入设备。在deviceInputWithDevice:error: 方法中我们使用了captureDevice(我们刚才初始化的拥有AVMediaTypeVideo的媒体类型)。如果有雨一些原因,输入设备没法初始化,我们打印log信息,并且结束当前工作,返回NO。

    前面,我们定义了AVCaptureSession对象,现在,是适合使用它了。因此,让我们初始化它,并且附加我们的输入设备。

        self.captureSession = [[AVCaptureSession alloc] init];
        [self.captureSession addInput:input];
    

    抓取会话,例如captureSession对象,为了正常工作,同时需要输入设备和输出设备。和现在正常使用的输出设备相比,我们将会使用AVCaptureMetadataOutput对象。这个类包含了AVCaptureMetadataOutputObjectsDelegate协议,将会拦截输入设备的元数据(意味着相机捕捉的QR code),并翻译为用户可以理解的格式。如果某些内哦那个听起来不可思议请不要担心,或者,到现在为止你完全不理解。
    一会一切都将变得清晰。现在,添加如下的方法:

        AVCaptureMetadataOutput *captureMetadataOutput = [[AVCaptureMetadataOutput alloc] init];
        [self.captureSession addOutput:captureMetadataOutput];
    

    继续添加如下代码:

        dispatch_queue_t dispatchQueue;
        dispatchQueue = dispatch_queue_create("myQueue", NULL);
        [captureMetadataOutput setMetadataObjectsDelegate:self queue:dispatchQueue];
        [captureMetadataOutput setMetadataObjectTypes:[NSArray arrayWithObject:AVMetadataObjectTypeQRCode]];
    

    首先,我们需要设置self(我们的类)为captureMetadataOutput的代理。这也是需要实现AVCaptureMetadataOutputObjectsDelegate协议的原因,通过使用setMetadataObjectsDelegate:获取。除了代理对象,同样需要提供dispatch queue来执行代理方法。通过官方文档,这个queue必须是串行dispatch queue,并且不容许执行其他附加的任务。这就是我们为什么创建了新的dispatch_queue_t,命名为myQueue。这个将会作为方法的参数。简单来说,对于我们的任务,我们创建了一个dispatch queue。

    setMetadataObjectTypes方法也很重要,这里,我们告诉app我们感兴趣的元数据。AVMetadataObjectTypeQRCode清楚的表明了我们的目的。

    现在我们已经设置和配置了AVCaptureMetadataOutput对象,我们需要向用户显示我们的相机设备。这个可以通过AVCaptureVideoPreviewLayer实现,其实就是CALayer,同时它会被添加为viewPreview layer的sublayer。如下:

        self.videoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession];
        [self.videoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
        [self.videoPreviewLayer setFrame:self.viewPreview.layer.bounds];
        [self.viewPreview.layer addSublayer:self.videoPreviewLayer];
    

    最后,我们需要添加如下代码来使会话运行:

    [self.captureSession startRunning];
    

    如下是startReading的全部实现:

    - (BOOL)startReading {
        NSError *error;
        
        AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
        
        AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
        
        if (!input) {
            NSLog(@"%@", [error localizedDescription]);
            return NO;
        }
        
        self.captureSession = [[AVCaptureSession alloc] init];
        [self.captureSession addInput:input];
        
        AVCaptureMetadataOutput *captureMetadataOutput = [[AVCaptureMetadataOutput alloc] init];
        
        [self.captureSession addOutput:captureMetadataOutput];
        
        dispatch_queue_t dispatchQueue;
        dispatchQueue = dispatch_queue_create("myQueue", NULL);
        [captureMetadataOutput setMetadataObjectsDelegate:self queue:dispatchQueue];
        [captureMetadataOutput setMetadataObjectTypes:[NSArray arrayWithObject:AVMetadataObjectTypeQRCode]];
        
        self.videoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession];
        [self.videoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
        [self.videoPreviewLayer setFrame:self.viewPreview.layer.bounds];
        [self.viewPreview.layer addSublayer:self.videoPreviewLayer];
        
        [self.captureSession startRunning];
    
        return YES;
    }
    

    如果你现在在设备上测试你的app,当你点击start按钮后开始捕捉。不要指望停止我们的课程,当然,因为我们还没有实现的呢。另外,也不要指望读取任何QR Code,因为我们还没有显示需要的代理方法。

    AVCaptureMetadataOutputObjectsDelegate协议提供的唯一代理方法captureOutput:didOutputMetadataObjects:fromConnection:是示例工程第二重要的部分。因为,在方法中,由设备捕获元数据被识别并翻译为可读的格式。让我们开始实现它,我们将同时添加后续需要的代码。

    方法的第二个参数是NSArray对象,包含了读取的全部元数据对象。首先我们需要保证数组不为nil,当然也要保证包含了至少一个对象,所以我们可以继续处理。

    -(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{   
        if (metadataObjects != nil && [metadataObjects count] > 0) {
    
        }
    }
    

    不错。在这个演示程序中,对我们来说包含的元数据是QR code。当我们的元数据捕获后,我们停止扫描,同时,我们只对metadataObjects数组中的第一个对象感兴趣。元数据代表AVMetadataMachineReadableCodeObject类对象,并且一旦我们得到它,我们必须检查它的类型是否是期望的类型,如下所示:

     AVMetadataMachineReadableCodeObject *metadataObj = [metadataObjects objectAtIndex:0];
     if ([[metadataObj type] isEqualToString:AVMetadataObjectTypeQRCode]) {
            
     }
    

    当情况为真时,app已经获取了有效的QR code,在这里,我们将会处理它。我们这里想做的是,在status标签上显示QR Code,然后停止扫描,并再次修改工具栏按钮名称。在我向你展示之前,请记住,我们的代码大现在在另一个线程运行,所以,所有的事情必须在主线程运行,如下所示:

    [self.lblStatus performSelectorOnMainThread:@selector(setText:) withObject:[metadataObj stringValue] waitUntilDone:NO];
                
    [self performSelectorOnMainThread:@selector(stopReading) withObject:nil waitUntilDone:NO];
    [self.bbitemStart performSelectorOnMainThread:@selector(setTitle:) withObject:@"Start!" waitUntilDone:NO];
    self.isReading = NO;
    

    你看到了metadataObj对象的stringValue属性了吗?这个就是包含了用户可读的QR code,并且对我们很重要。

    这个是delete方法的全部代码:

    -(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
        if (metadataObjects != nil && [metadataObjects count] > 0) {
            AVMetadataMachineReadableCodeObject *metadataObj = [metadataObjects objectAtIndex:0];
            if ([[metadataObj type] isEqualToString:AVMetadataObjectTypeQRCode])
            {
                [self.lblStatus performSelectorOnMainThread:@selector(setText:) withObject:[metadataObj stringValue] waitUntilDone:NO];
                
                [self performSelectorOnMainThread:@selector(stopReading) withObject:nil waitUntilDone:NO];
                [self.bbitemStart performSelectorOnMainThread:@selector(setTitle:) withObject:@"Start!" waitUntilDone:NO];
                self.isReading = NO;
            }
        }
    }
    

    最后,从这里看,视频捕捉和QR Code识别功能已经完成!我们成功地实现我们的目标,因为我们的应用程序现在能够读取并识别QR Code。但是,还没有完成,所以让我们继续。

    停止QR Code读取

    当实现先前的特性,我们调用了几次stopReading方法。这个方法,如它的名字,是用来在运行时停止视频捕捉会话的,相对于startReading,这个很简单。如下代码实现:

        [self.captureSession stopRunning];
        self.captureSession = nil;
        
        [self.borderLayer removeFromSuperlayer];
    

    这个方法有三个任务:首先,停止session然后设置为nil。最后从viewPreview layer移除视频预览layer。我提醒你,这个方法是用在两个地方。第一个是startStopReading:方法。第二个是捕获输出元数据的委托方法,就是当读取到一个QR Code后,停止捕获。

    增加声音特效

    在App预览和概述section中,我提到当成功读取有效的QR Code时会播放声音。在本节中,我们要实现这个功能,通过采用已经导入到项目中的AVFoundation框架的优势。
    首先,beep.mp3文件需要被添加到该项目。所以,现在请下载它,并把它添加到你的项目中。

    AVFoundation框架中有两种方法可以使用来播放声音。要么预加载声音,要么当需要的时候才加载,或者在声音被播放的时才加载。在我们的例子中,我们将按照第一种方式,所以我们就在开始处准备播放我们的音效。此外,我们将使用一个AVAudioPlayer对象。

    再次,在私有接口部分添加AVAudioPlayer属性:

    @interface ViewController ()
    ...
    ...
    
    @property (nonatomic, strong) AVAudioPlayer *audioPlayer;
    
    @end
    

    让我们现在实现loadBeepSound方法。音频文件包含在应用程序包中,所以在这里我们获取它。下面给出了整个方法。要特别注意的是,audioPlayer对象初始化只接受NSURL对象,所以我们必须处理音频文件的路径。该代码是不言自明的:

    -(void)loadBeepSound{
        NSString *beepFilePath = [[NSBundle mainBundle] pathForResource:@"beep" ofType:@"mp3"];
        NSURL *beepURL = [NSURL URLWithString:beepFilePath];
        NSError *error;
        
        self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:beepURL error:&error];
        if (error) {
            NSLog(@"Could not play beep file.");
            NSLog(@"%@", [error localizedDescription]);
        }
        else{
            [self.audioPlayer prepareToPlay];
        }
    }
    

    实际加载声音并存储在内容中的中的代码是[self.audioPlayer prepareToPlay];一个和整个方法是都是为了这行代码。

    现在,音频文件已加载到内存中,我们需要播放它。应该在哪里操作呢?当然,是在捕获元数据的委托方法中,当一个QR Code已经被成功地获取之后。下面再一次给出的委托方法,但现在包含音频播放功能:

    -(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
        if (metadataObjects != nil && [metadataObjects count] > 0) {
            AVMetadataMachineReadableCodeObject *metadataObj = [metadataObjects objectAtIndex:0];
            if ([[metadataObj type] isEqualToString:AVMetadataObjectTypeQRCode])
            {
                [self.lblStatus performSelectorOnMainThread:@selector(setText:) withObject:[metadataObj stringValue] waitUntilDone:NO];
                
                [self performSelectorOnMainThread:@selector(stopReading) withObject:nil waitUntilDone:NO];
                [self.bbitemStart performSelectorOnMainThread:@selector(setTitle:) withObject:@"Start!" waitUntilDone:NO];
                self.isReading = NO;
                
                if (self.audioPlayer) {
                    [self.audioPlayer play];
                }
            }
        }
    }
    

    注意,我们确保音频对象已经被初始化。如果是这样的话,我们通过简单调用音频播放器对象的播放方法来播放。

    还有的是,仍然需要做的最后一件事,那就是调用loadBeepSound方法。如果你注意到了,方法(loadBeepSound)尚未被执行到。要做到这一点,最好的地方是在viewDidLoad方法,就像下面的代码片段:

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.isReading = NO;
        
        [self loadBeepSound];
    }
    

    译者增加内容,支持固定区域识别

    如果你按照教程运行代码,你会发现,扫描是全屏扫描的,所以此处增加如何支持固定区域扫描,并增加区域边框。
    对于固定区域扫描,需要调用AVCaptureMetadataOutput的rectOfInterest设置区域,同时对于区域设置,由于AVCapture输出的图片大小都是横着的,而iPhone的屏幕是竖着的,所以需要把它旋转90°,同时这里为了方便把捕捉区域固定到屏幕中间,我增加了一个view,用来根据autolayout计算屏幕中间位置,代码如下:

        CGRect cropRect = self.interestView.frame;
        CGSize containSize = self.videoRenderView.bounds.size;
        
        captureMetadataOutput.rectOfInterest = CGRectMake(cropRect.origin.x/containSize.width,
                                                          cropRect.origin.y/containSize.height,
                                                          cropRect.size.width/containSize.width,
                                                          cropRect.size.height/containSize.height);
    

    同时为了方便查看当前捕捉区域,我在捕捉区域增加了边框,具体代码就不在这里黏贴了,参照我的demo代码吧。主要思路是在videoPreviewLayer再增加一层layer,同时采用layer的代理方法进行绘制操作。

    编译运行测试最终的程序

    在完成这一切的工作后,是时候来享受我们努力的成果了。如果你还没有这样做,在Mac上连接你的设备并运行应用程序。查找各种QR Code并进行测试。甚至,在谷歌搜索QR code creators,创建自己QR Code。或者简单地使用下面的这个。如果你的应用程序正常工作,你应该看到如下消息“It’s great! Your app works!“。
    qrcode

    总结
    创建读取QR Code功能最初可能听起来很吓人,并且难以构建。通过本教程中,我希望它已经完全清晰了,这样的应用现在可以很简单,这全要感谢的iOS7,该演示项目结合各种AVFoundation特点,如视频采集,声音播放,当然,条形码读取。为了给你完整的参考,可以从这里下载Xcode项目的源代码

    译注:针对二维码,根据这个教程我实现了通用的扫描代码,参照我在github上的工程