iOS 7 What’s New in AV Foundation之二维码扫描(下)

Drawing overlays

The bounds metadata defines a rectangle that exactly contains the image, while thecorner metadata defines the coordinates of the image’s corners. The diagram belowdisplays the difference between the two: 


When the camera and the image are aligned perfectly, the bounds and cornersdepict the same region. However, holding the camera perfectly parallel to theimage is nearly impossible, so it’s useful to read and display both pieces ofmetadata.

Open ViewController.m and add the following instance variable to theimplementation block: 


NSMutableDictionary *_barcodes; 


This provides dictionary storage for all detected barcodes, keyed by the barcodes’contents.

Add the following line to viewDidLoad in ViewController.m


_barcodes = [NSMutableDictionary new]; 


_barcodes provides you with a dictionary to index the detected barcodes, but youalso need to store the metadata associated with each barcode. It sounds like a classwould be perfect for this job. 


Add the following class definition to the top of ViewController.m, directly under

the imports: 



@interface Barcode : NSObject@property (nonatomic, strong)AVMetadataMachineReadableCodeObject *metadataObject;

@property (nonatomic, strong) UIBezierPath *cornersPath;

@property (nonatomic, strong) UIBezierPath *boundingBoxPath;

@end

@implementation Barcode

@end

page629image9480

Add the following method to ViewController.m


- (Barcode*)processMetadataObject:(AVMetadataMachineReadableCodeObject*)code

{

// 1

Barcode *barcode = _barcodes[code.stringValue];

// 2

if (!barcode) {
barcode = [
Barcode new];_barcodes[code.stringValue] = barcode; 

}

// 3

barcode.metadataObject = code;

// Create the path joining code's corners

// 4

CGMutablePathRef cornersPath = CGPathCreateMutable();

// 5

CGPoint point;CGPointMakeWithDictionaryRepresentation(

(CFDictionaryRef)code.corners[0], &point);// 6 

CGPathMoveToPoint(cornersPath, nil, point.x, point.y);

// 7

for (int i = 1; i < code.corners.count; i++) {CGPointMakeWithDictionaryRepresentation(

(CFDictionaryRef)code.corners[i], &point);CGPathAddLineToPoint(cornersPath, nil,

}

// 8

point.x, point.y);

CGPathCloseSubpath(cornersPath);

// 9

barcode.cornersPath =
[
UIBezierPath bezierPathWithCGPath:cornersPath];

CGPathRelease(cornersPath);

// Create the path for the code's bounding box

// 10

barcode.boundingBoxPath =
[
UIBezierPath bezierPathWithRect:code.bounds];

// 11

return barcode;


The above method retrieves existing Barcode objects from the dictionary — orcreates new Barcode dictionary entries as required — and creates paths torepresent the bounds and corners of the stored Barcode objects.

The points below explain the method in further detail, comment by comment:

1. Query the dictionary of Barcode objects to see if a Barcode with the samecontents is already cached.

2. If not, create a new Barcode object and add it to the dictionary.

3. Store the barcode’s metadata in the cached Barcode object for later.

4. Instantiate cornersPath to store the path joining the four corners of the code.

5. Convert the first corner coordinate to CGPoint instances using someCoreGraphics calls.

6. Begin the path at the corner defined in Step 5.
7. Loop through the other three corners, creating the path as you go.8. Close the path by joining the fourth point to the first point. 

9. Create a UIBezierPath object from cornersPath and store it in the Barcode

object.
10. Create the bounding box path using
bezierPathWithRect:.11. Finally, return the Barcode object.

That takes care of calculating the image’s corner and bounds paths. Now you justneed some code that will iterate through the cached Barcode objects and drawthese paths on-screen.

Still working in ViewController.m, replace the contents ofcaptureOutput:didOutputMetadataObjects:fromConnection: with the following: 


// 1

NSMutableSet *foundBarcodes = [NSMutableSet new];

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

NSLog(@"Metadata: %@", obj);// 2
if ([obj isKindOfClass:

[AVMetadataMachineReadableCodeObject class]])

{

[_previewLayer transformedMetadataObjectForMetadataObject:obj];// 4

Barcode *barcode = [self processMetadataObject:code];

[foundBarcodes addObject:barcode];}

}];

dispatch_sync(dispatch_get_main_queue(), ^{
// Remove all old layers
// 5
NSArray *allSublayers = [_previewView.layer.sublayers copy];[allSublayers enumerateObjectsUsingBlock:

^(CALayer *layer, NSUInteger idx, BOOL *stop) {if (layer != _previewLayer) {

[layer removeFromSuperlayer];}

}];

// Add new layers
// 6
[foundBarcodes enumerateObjectsUsingBlock:

// 3

AVMetadataMachineReadableCodeObject *code =(AVMetadataMachineReadableCodeObject*) 

^(Barcode *barcode, BOOL *stop) {
CAShapeLayer *boundingBoxLayer = [CAShapeLayer new];boundingBoxLayer.path = barcode.boundingBoxPath.CGPath;boundingBoxLayer.lineWidth = 2.0f;boundingBoxLayer.strokeColor =

[UIColor greenColor].CGColor;boundingBoxLayer.fillColor =

[UIColor colorWithRed:0.0fgreen:1.0fblue:0.0f

alpha:0.5f].CGColor;[_previewView.layer addSublayer:boundingBoxLayer];

CAShapeLayer *cornersPathLayer = [CAShapeLayer new];cornersPathLayer.path = barcode.cornersPath.CGPath;cornersPathLayer.lineWidth = 2.0f;cornersPathLayer.strokeColor =

[UIColor blueColor].CGColor;cornersPathLayer.fillColor =

[UIColor colorWithRed:0.0fgreen:0.0fblue:1.0f

alpha:0.5f].CGColor;[_previewView.layer addSublayer:cornersPathLayer];

}];}); 


Here’s a description of what’s happening in the above code, comment by comment:

1. Create an NSMutableSet so you can easily enumerate the detected barcodes.

2. Process only the objects of type AVMetadataMachineReadableCodeObject. Recallthat you configured the metadata output in startRunning to accept all metadatatypes; you need to filter out anything that’s not a machine readable code.

3. Transform the image’s bounds and corner coordinates — that are represented inrelative coordinates — into the coordinate space of your containing view. In therelative coordinate system, an x value of 1.0 is the right hand side of the frame,while an x coordinate of 0.5 is in the middle of the frame. The preview layer hasthe very handy transformedMetadataObjectForMetadataObject: to do this taskfor you, which saves you from handling a ton of coordinate math.

4. Call the method you just wrote to process the barcode data, and add it to theset.

5. Remove all sublayers from the preview view by shipping a dispatch back to themain queue where the UI work is performed.

6. Finally, enumerate all the detected barcodes and add a CAShapeLayer for thebounding box path and corner path of each one. The shape layers have different 

colors to be visually distinct and have an alpha value of 0.5 so that the original

code image can be seen through the drawn shapes.

You’ve done well to get through the overlay code — you deserve to see the fruits ofyour labors!

Build and run your app, hover the camera over a QR code of your choice, and youshould see something similar to the image below: 

                          



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值