// // HMScanner.m // HMQRCodeScanner // // Created by 刘凡 on 16/1/2. // Copyright © 2016年 itheima. All rights reserved. // #import "HMScanner.h" #import /// 最大检测次数 #define kMaxDetectedCount 20 @interface HMScanner() /// 父视图弱引用 @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