Question

Using Ray Wenderlich's QRCode reader from Chapter 22 of iOS7 Tutorials, I am successfully reading QRCodes for my current app. I am now extending it that upon successfully reading a QRCode, I want to store the stringValue of the AVMetadataMachineReadableCodeObject that was read, segue to a new view, and use that data on the new view, more or less exactly how most QRCode reader apps (like RedLaser, etc...) process barcodes and QRCodes.

However, I call [captureSession stopRunning] (so that it does not read any more QRCodes and trigger additional segues) and there is a 10+ second hang. I have tried to implement an async call per this SO question, however to no avail. I have also looked at these SO Questions and they seem not to be appropriate for this purpose.

Does anyone have an idea how to remove this hanging?

Here is the code:

#import "BMQRCodeReaderViewController.h"
#import "NSString+containsString.h"
#import "BMManualExperimentDataEntryViewController.h"
@import AVFoundation;

@interface BMQRCodeReaderViewController ()
    <AVCaptureMetadataOutputObjectsDelegate>
@end

@implementation BMQRCodeReaderViewController {
    AVCaptureSession *_captureSession;
    AVCaptureDevice *_videoDevice;
    AVCaptureDeviceInput *_videoInput;
    AVCaptureVideoPreviewLayer *_previewLayer;
    BOOL _running;
    AVCaptureMetadataOutput *_metadataOutput;
}

- (void)setupCaptureSession { // 1
    if (_captureSession) return;
    // 2
    _videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    if (!_videoDevice) {
        NSLog(@"No video camera on this device!"); return;
    }
    // 3
    _captureSession = [[AVCaptureSession alloc] init];
    // 4
    _videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:_videoDevice error:nil];
    // 5
    if ([_captureSession canAddInput:_videoInput]) { [_captureSession addInput:_videoInput];
    }
    // 6
    _previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];
    _previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;

    _metadataOutput = [[AVCaptureMetadataOutput alloc] init];
    dispatch_queue_t metadataQueue = dispatch_queue_create("com.razeware.ColloQR.metadata", 0);

    [_metadataOutput setMetadataObjectsDelegate:self queue:metadataQueue];
    if ([_captureSession canAddOutput:_metadataOutput]) { [_captureSession addOutput:_metadataOutput];
    }
}

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
       fromConnection:(AVCaptureConnection *)connection {

    // This fancy BOOL is just helping me fire the segue when the correct string is found
     __block NSNumber *didFind = [NSNumber numberWithBool:NO];

    [metadataObjects enumerateObjectsUsingBlock:^(AVMetadataObject *obj, NSUInteger idx, BOOL *stop) {

        AVMetadataMachineReadableCodeObject *readableObject = (AVMetadataMachineReadableCodeObject *)obj;

        NSLog(@"Metadata: %@", readableObject);

        // [ containsString is a category I extended for NSString, just FYI
        if ([readableObject.stringValue containsString:@"CorrectString"]) {

            didFind = [NSNumber numberWithBool:YES];
             NSLog(@"Found it");
             _testName = @"NameOfTest";
             *stop = YES;
             return;
         }
    }];

    if ([didFind boolValue]) {
        NSLog(@"Confirming we found it");
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [self stopRunning];
        });
        _labelTestName.text = _testName;
        [self performSegueWithIdentifier:@"segueFromFoundQRCode" sender:self];
    }
    else {
        NSLog(@"Did not find it");
    }
}

- (void)startRunning {
    if (_running)
        return;

    [_captureSession startRunning];
    _metadataOutput.metadataObjectTypes = _metadataOutput.availableMetadataObjectTypes;
    _running = YES;
}
- (void)stopRunning {
    if (!_running) return;

    [_captureSession stopRunning];
    _running = NO;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    [self setupCaptureSession];
    [self setupNavBar];
    [self startRunning];
    _previewLayer.frame = _previewView.bounds;
    [_previewView.layer addSublayer:_previewLayer];
}
Was it helpful?

Solution

I have successively solved the issue. The issue was that the delegate method call

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
       fromConnection:(AVCaptureConnection *)connection

is running in the background. This was determined with a [NSThread isMainThread] call, which it fails.

The solution is to find the proper stringValue from the QRCode, stop your captureSession in the background, THEN call your segue on the main thread. The solution looks as such:

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
       fromConnection:(AVCaptureConnection *)connection {

    // This fancy BOOL is just helping me fire the segue when the correct string is found
     __block NSNumber *didFind = [NSNumber numberWithBool:NO];

    [metadataObjects enumerateObjectsUsingBlock:^(AVMetadataObject *obj, NSUInteger idx, BOOL *stop) {

        AVMetadataMachineReadableCodeObject *readableObject = (AVMetadataMachineReadableCodeObject *)obj;

        NSLog(@"Metadata: %@", readableObject);
        if ([NSThread isMainThread]) {
            NSLog(@"Yes Main Thread");
        }
        else {
            NSLog(@"Not main thread");
        }
        // [ containsString is a category I extended for NSString, just FYI
        if ([readableObject.stringValue containsString:@"Biomeme"]) {
            //NSLog(@"this is a test: %@", getTestName);
            didFind = [NSNumber numberWithBool:YES];
             NSLog(@"Found it");
             _testName = readableObject.stringValue;
             *stop = YES;
             return;
         }
    }];


    if ([didFind boolValue]) {
        NSLog(@"Confirming we found it");
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSDate *start = [NSDate date];

            [self stopRunning];
            NSLog(@"time took: %f", -[start timeIntervalSinceNow]);

            // *** Here is the key, make your segue on the main thread
            dispatch_async(dispatch_get_main_queue(), ^{
                [self performSegueWithIdentifier:@"segueFromFoundQRCode" sender:self];
                _labelTestName.text = _testName;
            });

        });



    }
    else {
        NSLog(@"Did not find it");
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top