可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm creating an app that uses the new barcode scanner in iOS 7 but I'm having a some problems with the delegate method. The scanner correctly identifies the barcodes and invokes the delegate method, but it does it too fast so the invocation happens many times in a row resulting in a segue being performed multiple times. Delegate method below.
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
connection.enabled = NO;
self.conn = connection;
for (AVMetadataObject *metadata in metadataObjects) {
if ([metadata.type isEqualToString:AVMetadataObjectTypeEAN8Code] || [metadata.type isEqualToString:AVMetadataObjectTypeEAN13Code]) {
self.strValue = [(AVMetadataMachineReadableCodeObject *)metadata stringValue];
NSLog(@"%@", [(AVMetadataMachineReadableCodeObject *)metadata corners]);
}
}
[self performSegueWithIdentifier:@"newSegue" sender:self];
}
The issue is that if I do not set connection.enabled = NO
in the opening line, the delegate is invoked multiple times causing a corrupt view hierarchy (and then a crash). The other issue is that when I do disable connection and then re-enable the connection using self.conn = YES
in viewWillAppear, the delegate will be invoked repeatedly from prior scans when returning to the view. This then causes another corruption in the view hierarchy.
So to sum it up: Either the delegate method is being invoked multiple times in quick succession or the delegate is being invoked with (old) scans when returning to the view. Any help would be appreciated.
Edit: I've partially managed to get around the problem with some fidgeting with the delegate, but I still have a problem with the delegate method being invoked multiple times. If you go back from the next viewcontroller in less than five seconds, the delegate method will be invoked again.
回答1:
I think you have started captureSession using
captureSession?.startRunning() method but didn't stop it once you got output from QRCode in delegate...
Just Use this [captureSession stopRunning]; // In Objective-C
below is what I have done for same issue in swift
// MARK: - AVCapture delegate to find metadata if detected
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
// Check if the metadataObjects array is not nil and it contains at least one object.
if metadataObjects == nil || metadataObjects.count == 0 {
qrCodeFrameView?.frame = CGRectZero
return
}
// Get the metadata object.
let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
if metadataObj.type == AVMetadataObjectTypeQRCode {
// If the found metadata is equal to the QR code metadata then update the status label's text and set the bounds
let barCodeObject = videoPreviewLayer?.transformedMetadataObjectForMetadataObject(metadataObj as AVMetadataMachineReadableCodeObject) as! AVMetadataMachineReadableCodeObject
qrCodeFrameView?.frame = barCodeObject.bounds;
if metadataObj.stringValue != nil {
captureSession?.stopRunning() // Stop captureSession here... :)
self.performSegueWithIdentifier("yourNextViewController", sender: self)
}
}
}
回答2:
I hope, it will save other's time.
Here is article, how to use bar code scanner
http://www.appcoda.com/qr-code-ios-programming-tutorial/
From the Apple documentation: "this method may be called frequently, your implementation should be efficient to prevent capture performance problems, including dropped metadata objects."
Now, to handle multiple invoke do the following :
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
id capturedData;
if ([metadataObjects count] > 0) {
// handle your captured data here
[self performSelectorOnMainThread:@selector(stopReading:) withObject:capturedData waitUntilDone:NO];
}
}
the stopReading: method looks (assuming your _session is AVCaptureSession object and _prevLayer is AVCaptureVideoPreviewLayer you used earlier) :
-(void)stopReading:(id) data{
NSLog(@"stop reading");
[_session stopRunning];
_session = nil;
[_prevLayer removeFromSuperlayer];
// do what you want with captured data
[self.delegate didScanBarCodeWithContext:data];
}
回答3:
Doro's answer is good ,but has bug: function 'stopReading' may not be called in time before the delegate method is invoked second times
So i do some optimization.
based on Doro's answer, i add a Static variable to tell them .
static BOOL hasOutput;
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
if (!hasOutput
&& metadataObjects.count > 0 ) {
hasOutput=YES;
[self performSelectorOnMainThread:@selector(stopReading) withObject:nil waitUntilDone:NO];
for (AVMetadataObject *current in metadataObjects) {
if ([current isKindOfClass:[AVMetadataMachineReadableCodeObject class]]
&& [_metadataObjectTypes containsObject:current.type]) {
NSString *scannedResult = [(AVMetadataMachineReadableCodeObject *) current stringValue];
if (_completionBlock) {
_completionBlock(scannedResult);
}
break;
}
}
}
}
-(void)stopReading{
NSLog(@"stop reading");
[_session stopRunning];
_session = nil;
hasOutput=NO;
}
回答4:
The workaround is to add to the delegate class a Boolean property that is switched to false after the first identification of a capture barcode event.
This solution is implemented as Calin Chitu offered.
You'll also need to initialize the property shouldSendReadBarcodeToDelegate once with YES.
@property (nonatomic, assign) BOOL shouldSendReadBarcodeToDelegate;
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
if (!self.shouldSendReadBarcodeToDelegate)
{
//this means we have already captured at least one event, then we don't want to call the delegate again
return;
}
else
{
self.shouldSendReadBarcodeToDelegate = NO;
//Your code for calling the delegate should be here
}
}
回答5:
The boolean property doesn't do the trick for me. I've ended using an operation queue to avoid multiple reads:
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputMetadataObjects:(NSArray *)metadataObjects
fromConnection:(AVCaptureConnection *)connection
{
if ([self.queue operationCount] > 0) return;
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
if ([metadataObjects count] > 0) {
// Your code here: Don't forget that you are in background now, perform
// all view related stuff on main thread
}
}];
[self.queue addOperations:@[operation] waitUntilFinished:NO];
}
Initializing the queue in the viewcontroller constructor:
self.queue = [NSOperationQueue new];