//
|
// HMScanner.m
|
// HMQRCodeScanner
|
//
|
// Created by 刘凡 on 16/1/2.
|
// Copyright © 2016年 itheima. All rights reserved.
|
//
|
|
#import "HMScanner.h"
|
#import <AVFoundation/AVFoundation.h>
|
|
/// 最大检测次数
|
#define kMaxDetectedCount 20
|
|
@interface HMScanner() <AVCaptureMetadataOutputObjectsDelegate>
|
/// 父视图弱引用
|
@property (nonatomic, weak) UIView *parentView;
|
/// 扫描范围
|
@property (nonatomic) CGRect scanFrame;
|
/// 完成回调
|
@property (nonatomic, copy) void (^completionCallBack)(NSString *);
|
@end
|
|
@implementation HMScanner {
|
/// 拍摄会话
|
AVCaptureSession *session;
|
/// 预览图层
|
AVCaptureVideoPreviewLayer *previewLayer;
|
/// 绘制图层
|
CALayer *drawLayer;
|
/// 当前检测计数
|
NSInteger currentDetectedCount;
|
}
|
|
#pragma mark - 生成二维码
|
+ (void)qrImageWithString:(NSString *)string avatar:(UIImage *)avatar completion:(void (^)(UIImage *))completion {
|
[self qrImageWithString:string avatar:avatar scale:0.20 completion:completion];
|
}
|
|
+ (void)qrImageWithString:(NSString *)string avatar:(UIImage *)avatar scale:(CGFloat)scale completion:(void (^)(UIImage *))completion {
|
|
NSAssert(completion != nil, @"必须传入完成回调");
|
|
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
|
CIFilter *qrFilter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
|
|
[qrFilter setDefaults];
|
[qrFilter setValue:[string dataUsingEncoding:NSUTF8StringEncoding] forKey:@"inputMessage"];
|
|
CIImage *ciImage = qrFilter.outputImage;
|
|
CGAffineTransform transform = CGAffineTransformMakeScale(10, 10);
|
CIImage *transformedImage = [ciImage imageByApplyingTransform:transform];
|
|
CIContext *context = [CIContext contextWithOptions:nil];
|
CGImageRef cgImage = [context createCGImage:transformedImage fromRect:transformedImage.extent];
|
UIImage *qrImage = [UIImage imageWithCGImage:cgImage scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp];
|
CGImageRelease(cgImage);
|
|
if (avatar != nil) {
|
qrImage = [self qrcodeImage:qrImage addAvatar:avatar scale:scale];
|
}
|
|
dispatch_async(dispatch_get_main_queue(), ^{ completion(qrImage); });
|
});
|
}
|
|
+ (UIImage *)qrcodeImage:(UIImage *)qrImage addAvatar:(UIImage *)avatar scale:(CGFloat)scale {
|
|
CGFloat screenScale = [UIScreen mainScreen].scale;
|
CGRect rect = CGRectMake(0, 0, qrImage.size.width * screenScale, qrImage.size.height * screenScale);
|
|
UIGraphicsBeginImageContextWithOptions(rect.size, YES, screenScale);
|
|
[qrImage drawInRect:rect];
|
|
CGSize avatarSize = CGSizeMake(rect.size.width * scale, rect.size.height * scale);
|
CGFloat x = (rect.size.width - avatarSize.width) * 0.5;
|
CGFloat y = (rect.size.height - avatarSize.height) * 0.5;
|
[avatar drawInRect:CGRectMake(x, y, avatarSize.width, avatarSize.height)];
|
|
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
|
|
UIGraphicsEndImageContext();
|
|
return [UIImage imageWithCGImage:result.CGImage scale:screenScale orientation:UIImageOrientationUp];
|
}
|
|
#pragma mark - 扫描图像方法
|
+ (void)scaneImage:(UIImage *)image completion:(void (^)(NSArray *))completion {
|
|
NSAssert(completion != nil, @"必须传入完成回调");
|
|
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
|
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{CIDetectorAccuracy: CIDetectorAccuracyHigh}];
|
|
CIImage *ciImage = [[CIImage alloc] initWithImage:image];
|
|
NSArray *features = [detector featuresInImage:ciImage];
|
|
NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:features.count];
|
for (CIQRCodeFeature *feature in features) {
|
[arrayM addObject:feature.messageString];
|
}
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
completion(arrayM.copy);
|
});
|
});
|
}
|
|
#pragma mark - 构造函数
|
+ (instancetype)scanerWithView:(UIView *)view scanFrame:(CGRect)scanFrame completion:(void (^)(NSString *))completion {
|
NSAssert(completion != nil, @"必须传入完成回调");
|
|
return [[self alloc] initWithView:view scanFrame:scanFrame completion:completion];
|
}
|
|
- (instancetype)initWithView:(UIView *)view scanFrame:(CGRect)scanFrame completion:(void (^)(NSString *))completion {
|
self = [super init];
|
|
if (self) {
|
self.parentView = view;
|
self.scanFrame = scanFrame;
|
self.completionCallBack = completion;
|
|
[self setupSession];
|
}
|
return self;
|
}
|
|
#pragma mark - 公共方法
|
/// 开始扫描
|
- (void)startScan {
|
if ([session isRunning]) {
|
return;
|
}
|
currentDetectedCount = 0;
|
|
[session startRunning];
|
}
|
|
- (void)stopScan {
|
if (![session isRunning]) {
|
return;
|
}
|
[session stopRunning];
|
}
|
|
#pragma mark - AVCaptureMetadataOutputObjectsDelegate
|
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
|
|
[self clearDrawLayer];
|
|
for (id obj in metadataObjects) {
|
// 判断检测到的对象类型
|
if (![obj isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) {
|
return;
|
}
|
|
// 转换对象坐标
|
AVMetadataMachineReadableCodeObject *dataObject = (AVMetadataMachineReadableCodeObject *)[previewLayer transformedMetadataObjectForMetadataObject:obj];
|
|
// 判断扫描范围
|
if (!CGRectContainsRect(self.scanFrame, dataObject.bounds)) {
|
continue;
|
}
|
|
if (currentDetectedCount++ < kMaxDetectedCount) {
|
// 绘制边角
|
[self drawCornersShape:dataObject];
|
} else {
|
[self stopScan];
|
|
// 完成回调
|
if (self.completionCallBack != nil) {
|
self.completionCallBack(dataObject.stringValue);
|
}
|
}
|
}
|
}
|
|
/// 清空绘制图层
|
- (void)clearDrawLayer {
|
if (drawLayer.sublayers.count == 0) {
|
return;
|
}
|
|
[drawLayer.sublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
|
}
|
|
/// 绘制条码形状
|
///
|
/// @param dataObject 识别到的数据对象
|
- (void)drawCornersShape:(AVMetadataMachineReadableCodeObject *)dataObject {
|
|
if (dataObject.corners.count == 0) {
|
return;
|
}
|
|
CAShapeLayer *layer = [CAShapeLayer layer];
|
|
layer.lineWidth = 4;
|
layer.strokeColor = [UIColor greenColor].CGColor;
|
layer.fillColor = [UIColor clearColor].CGColor;
|
layer.path = [self cornersPath:dataObject.corners];
|
|
[drawLayer addSublayer:layer];
|
}
|
|
/// 使用 corners 数组生成绘制路径
|
///
|
/// @param corners corners 数组
|
///
|
/// @return 绘制路径
|
- (CGPathRef)cornersPath:(NSArray *)corners {
|
|
UIBezierPath *path = [UIBezierPath bezierPath];
|
CGPoint point = CGPointZero;
|
|
// 1. 移动到第一个点
|
NSInteger index = 0;
|
CGPointMakeWithDictionaryRepresentation((CFDictionaryRef)corners[index++], &point);
|
[path moveToPoint:point];
|
|
// 2. 遍历剩余的点
|
while (index < corners.count) {
|
CGPointMakeWithDictionaryRepresentation((CFDictionaryRef)corners[index++], &point);
|
[path addLineToPoint:point];
|
}
|
|
// 3. 关闭路径
|
[path closePath];
|
|
return path.CGPath;
|
}
|
|
#pragma mark - 扫描相关方法
|
/// 设置绘制图层和预览图层
|
- (void)setupLayers {
|
|
if (self.parentView == nil) {
|
NSLog(@"父视图不存在");
|
return;
|
}
|
|
if (session == nil) {
|
NSLog(@"拍摄会话不存在");
|
return;
|
}
|
|
// 绘制图层
|
drawLayer = [CALayer layer];
|
|
drawLayer.frame = self.parentView.bounds;
|
|
[self.parentView.layer insertSublayer:drawLayer atIndex:0];
|
|
// 预览图层
|
previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
|
|
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
|
previewLayer.frame = self.parentView.bounds;
|
|
[self.parentView.layer insertSublayer:previewLayer atIndex:0];
|
}
|
|
/// 设置扫描会话
|
- (void)setupSession {
|
|
// 1> 输入设备
|
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
|
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
|
|
if (videoInput == nil) {
|
NSLog(@"创建输入设备失败");
|
return;
|
}
|
|
// 2> 数据输出
|
AVCaptureMetadataOutput *dataOutput = [[AVCaptureMetadataOutput alloc] init];
|
|
// 3> 拍摄会话 - 判断能够添加设备
|
session = [[AVCaptureSession alloc] init];
|
if (![session canAddInput:videoInput]) {
|
NSLog(@"无法添加输入设备");
|
session = nil;
|
|
return;
|
}
|
if (![session canAddOutput:dataOutput]) {
|
NSLog(@"无法添加输入设备");
|
session = nil;
|
|
return;
|
}
|
|
// 4> 添加输入/输出设备
|
[session addInput:videoInput];
|
[session addOutput:dataOutput];
|
|
// 5> 设置扫描类型
|
dataOutput.metadataObjectTypes = dataOutput.availableMetadataObjectTypes;
|
[dataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
|
|
// 6> 设置预览图层会话
|
[self setupLayers];
|
}
|
|
@end
|