123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387 |
- //
- // SGQRCodeObtain.m
- // SGQRCodeExample
- //
- // Created by kingsic on 2016/8/16.
- // Copyright © 2016年 kingsic. All rights reserved.
- //
- #import "SGQRCodeObtain.h"
- #import "SGQRCodeObtainConfigure.h"
- #import <Photos/Photos.h>
- @interface SGQRCodeObtain () <AVCaptureMetadataOutputObjectsDelegate, AVCaptureVideoDataOutputSampleBufferDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate>
- @property (nonatomic, weak) UIViewController *controller;
- @property (nonatomic, strong) SGQRCodeObtainConfigure *configure;
- @property (nonatomic, strong) AVCaptureSession *captureSession;
- @property (nonatomic, copy) SGQRCodeObtainScanResultBlock scanResultBlock;
- @property (nonatomic, copy) SGQRCodeObtainScanBrightnessBlock scanBrightnessBlock;
- @property (nonatomic, copy) SGQRCodeObtainAlbumDidCancelImagePickerControllerBlock albumDidCancelImagePickerControllerBlock;
- @property (nonatomic, copy) SGQRCodeObtainAlbumResultBlock albumResultBlock;
- @property (nonatomic, copy) NSString *detectorString;
- @end
- @implementation SGQRCodeObtain
- + (instancetype)QRCodeObtain {
- return [[self alloc] init];
- }
- - (void)dealloc {
- if (_configure.openLog == YES) {
- NSLog(@"SGQRCodeObtain - - dealloc");
- }
- }
- #pragma mark - - 生成二维码相关方法
- /**
- * 生成二维码
- *
- * @param data 二维码数据
- * @param size 二维码大小
- */
- + (UIImage *)generateQRCodeWithData:(NSString *)data size:(CGFloat)size {
- return [self generateQRCodeWithData:data size:size color:[UIColor blackColor] backgroundColor:[UIColor whiteColor]];
- }
- /**
- * 生成二维码
- *
- * @param data 二维码数据
- * @param size 二维码大小
- * @param color 二维码颜色
- * @param backgroundColor 二维码背景颜色
- */
- + (UIImage *)generateQRCodeWithData:(NSString *)data size:(CGFloat)size color:(UIColor *)color backgroundColor:(UIColor *)backgroundColor {
- NSData *string_data = [data dataUsingEncoding:NSUTF8StringEncoding];
- // 1、二维码滤镜
- CIFilter *fileter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
- [fileter setValue:string_data forKey:@"inputMessage"];
- [fileter setValue:@"H" forKey:@"inputCorrectionLevel"];
- CIImage *ciImage = fileter.outputImage;
- // 2、颜色滤镜
- CIFilter *color_filter = [CIFilter filterWithName:@"CIFalseColor"];
- [color_filter setValue:ciImage forKey:@"inputImage"];
- [color_filter setValue:[CIColor colorWithCGColor:color.CGColor] forKey:@"inputColor0"];
- [color_filter setValue:[CIColor colorWithCGColor:backgroundColor.CGColor] forKey:@"inputColor1"];
- // 3、生成处理
- CIImage *outImage = color_filter.outputImage;
- CGFloat scale = size / outImage.extent.size.width;
- outImage = [outImage imageByApplyingTransform:CGAffineTransformMakeScale(scale, scale)];
- return [UIImage imageWithCIImage:outImage];
- }
- /**
- * 生成带 logo 的二维码(推荐使用)
- *
- * @param data 二维码数据
- * @param size 二维码大小
- * @param logoImage logo
- * @param ratio logo 相对二维码的比例
- */
- + (UIImage *)generateQRCodeWithData:(NSString *)data size:(CGFloat)size logoImage:(UIImage *)logoImage ratio:(CGFloat)ratio {
- return [self generateQRCodeWithData:data size:size logoImage:logoImage ratio:ratio logoImageCornerRadius:5 logoImageBorderWidth:5 logoImageBorderColor:[UIColor whiteColor]];
- }
- /**
- * 生成带 logo 的二维码(拓展)
- *
- * @param data 二维码数据
- * @param size 二维码大小
- * @param logoImage logo
- * @param ratio logo 相对二维码的比例
- * @param logoImageCornerRadius logo 外边框圆角
- * @param logoImageBorderWidth logo 外边框宽度
- * @param logoImageBorderColor logo 外边框颜色
- */
- + (UIImage *)generateQRCodeWithData:(NSString *)data size:(CGFloat)size logoImage:(UIImage *)logoImage ratio:(CGFloat)ratio logoImageCornerRadius:(CGFloat)logoImageCornerRadius logoImageBorderWidth:(CGFloat)logoImageBorderWidth logoImageBorderColor:(UIColor *)logoImageBorderColor {
- UIImage *image = [self generateQRCodeWithData:data size:size color:[UIColor blackColor] backgroundColor:[UIColor whiteColor]];
- if (logoImage == nil) return image;
- if (ratio < 0.0 || ratio > 0.5) {
- ratio = 0.25;
- }
- CGFloat logoImageW = ratio * size;
- CGFloat logoImageH = logoImageW;
- CGFloat logoImageX = 0.5 * (image.size.width - logoImageW);
- CGFloat logoImageY = 0.5 * (image.size.height - logoImageH);
- CGRect logoImageRect = CGRectMake(logoImageX, logoImageY, logoImageW, logoImageH);
- // 绘制logo
- UIGraphicsBeginImageContextWithOptions(image.size, false, [UIScreen mainScreen].scale);
- [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
- if (logoImageCornerRadius < 0.0 || logoImageCornerRadius > 10) {
- logoImageCornerRadius = 5;
- }
- UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:logoImageRect cornerRadius:logoImageCornerRadius];
- if (logoImageBorderWidth < 0.0 || logoImageBorderWidth > 10) {
- logoImageBorderWidth = 5;
- }
- path.lineWidth = logoImageBorderWidth;
- [logoImageBorderColor setStroke];
- [path stroke];
- [path addClip];
- [logoImage drawInRect:logoImageRect];
- UIImage *QRCodeImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return QRCodeImage;
- }
- #pragma mark - - 扫描二维码相关方法
- - (void)establishQRCodeObtainScanWithController:(UIViewController *)controller configure:(SGQRCodeObtainConfigure *)configure {
- if (controller == nil) {
- @throw [NSException exceptionWithName:@"SGQRCode" reason:@"SGQRCodeObtain 中 establishQRCodeObtainScanWithController:configuration:方法的 controller 参数不能为空" userInfo:nil];
- }
- if (configure == nil) {
- @throw [NSException exceptionWithName:@"SGQRCode" reason:@"SGQRCodeObtain 中 establishQRCodeObtainScanWithController:configure:方法的 configure 参数不能为空" userInfo:nil];
- }
-
- _controller = controller;
- _configure = configure;
-
- AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
- // 1、捕获设备输入流
- AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
- // 2、捕获元数据输出流
- AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];
- [metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
-
- // 设置扫描范围(每一个取值 0 ~ 1,以屏幕右上角为坐标原点)
- // 注:微信二维码的扫描范围是整个屏幕,这里并没有做处理(可不用设置)
- if (configure.rectOfInterest.origin.x == 0 && configure.rectOfInterest.origin.y == 0 && configure.rectOfInterest.size.width == 0 && configure.rectOfInterest.size.height == 0) {
- } else {
- metadataOutput.rectOfInterest = configure.rectOfInterest;
- }
- // 3、设置会话采集率
- self.captureSession.sessionPreset = configure.sessionPreset;
-
- // 4(1)、添加捕获元数据输出流到会话对象
- [_captureSession addOutput:metadataOutput];
- // 4(2)、添加捕获输出流到会话对象;构成识了别光线强弱
- if (configure.sampleBufferDelegate == YES) {
- AVCaptureVideoDataOutput *videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
- [videoDataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
- [_captureSession addOutput:videoDataOutput];
- }
- // 4(3)、添加捕获设备输入流到会话对象
- [_captureSession addInput:deviceInput];
-
- // 5、设置数据输出类型,需要将数据输出添加到会话后,才能指定元数据类型,否则会报错
- metadataOutput.metadataObjectTypes = configure.metadataObjectTypes;
-
- // 6、预览图层
- AVCaptureVideoPreviewLayer *videoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_captureSession];
- // 保持纵横比,填充层边界
- videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
- videoPreviewLayer.frame = controller.view.frame;
- [controller.view.layer insertSublayer:videoPreviewLayer atIndex:0];
- }
- - (AVCaptureSession *)captureSession {
- if (!_captureSession) {
- _captureSession = [[AVCaptureSession alloc] init];
- }
- return _captureSession;
- }
- - (void)startRunningWithBefore:(void (^)(void))before completion:(void (^)(void))completion {
- if (before) {
- before();
- }
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- [self.captureSession startRunning];
- dispatch_async(dispatch_get_main_queue(), ^{
- if (completion) {
- completion();
- }
- });
- });
- }
- - (void)stopRunning {
- if (self.captureSession.isRunning) {
- [self.captureSession stopRunning];
- }
- }
- #pragma mark - - AVCaptureMetadataOutputObjectsDelegate 的方法
- - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
- NSString *resultString = nil;
- if (metadataObjects != nil && metadataObjects.count > 0) {
- AVMetadataMachineReadableCodeObject *obj = metadataObjects[0];
- resultString = [obj stringValue];
- if (_scanResultBlock) {
- _scanResultBlock(self, resultString);
- }
- }
- }
- #pragma mark - - AVCaptureVideoDataOutputSampleBufferDelegate 的方法
- - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
- CFDictionaryRef metadataDict = CMCopyDictionaryOfAttachments(NULL,sampleBuffer, kCMAttachmentMode_ShouldPropagate);
- NSDictionary *metadata = [[NSMutableDictionary alloc] initWithDictionary:(__bridge NSDictionary*)metadataDict];
- CFRelease(metadataDict);
- NSDictionary *exifMetadata = [[metadata objectForKey:(NSString *)kCGImagePropertyExifDictionary] mutableCopy];
- CGFloat brightnessValue = [[exifMetadata objectForKey:(NSString *)kCGImagePropertyExifBrightnessValue] floatValue];
- if (_scanBrightnessBlock) {
- _scanBrightnessBlock(self, brightnessValue);
- }
- }
- - (void)setBlockWithQRCodeObtainScanResult:(SGQRCodeObtainScanResultBlock)block {
- _scanResultBlock = block;
- }
- - (void)setBlockWithQRCodeObtainScanBrightness:(SGQRCodeObtainScanBrightnessBlock)block {
- _scanBrightnessBlock = block;
- }
- - (void)playSoundName:(NSString *)name {
- /// 静态库 path 的获取
- NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:nil];
- if (!path) {
- /// 动态库 path 的获取
- path = [[NSBundle bundleForClass:[self class]] pathForResource:name ofType:nil];
- }
- NSURL *fileUrl = [NSURL fileURLWithPath:path];
-
- SystemSoundID soundID = 0;
- AudioServicesCreateSystemSoundID((__bridge CFURLRef)(fileUrl), &soundID);
- AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallback, NULL);
- AudioServicesPlaySystemSound(soundID);
- }
- void soundCompleteCallback(SystemSoundID soundID, void *clientData){
-
- }
- #pragma mark - - 相册中读取二维码相关方法
- - (void)establishAuthorizationQRCodeObtainAlbumWithController:(UIViewController *)controller {
- if (controller == nil && _controller == nil) {
- @throw [NSException exceptionWithName:@"SGQRCode" reason:@"SGQRCodeObtain 中 establishAuthorizationQRCodeObtainAlbumWithController: 方法的 controller 参数不能为空" userInfo:nil];
- }
- if (_controller == nil) {
- _controller = controller;
- }
-
- AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
- if (device) {
- // 判断授权状态
- PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
- if (status == PHAuthorizationStatusNotDetermined) { // 用户还没有做出选择
- // 弹框请求用户授权
- [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
- if (status == PHAuthorizationStatusAuthorized) { // 用户第一次同意了访问相册权限
- self.isPHAuthorization = YES;
- dispatch_sync(dispatch_get_main_queue(), ^{
- [self P_enterImagePickerController];
- });
- if (self.configure.openLog == YES) {
- NSLog(@"用户第一次同意了访问相册权限");
- }
- } else { // 用户第一次拒绝了访问相机权限
- if (self.configure.openLog == YES) {
- NSLog(@"用户第一次拒绝了访问相册权限");
- }
- }
- }];
- } else if (status == PHAuthorizationStatusAuthorized) { // 用户允许当前应用访问相册
- self.isPHAuthorization = YES;
- if (self.configure.openLog == YES) {
- NSLog(@"用户允许访问相册权限");
- }
- [self P_enterImagePickerController];
- } else if (status == PHAuthorizationStatusDenied) { // 用户拒绝当前应用访问相册
- NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
- NSString *app_Name = [infoDict objectForKey:@"CFBundleDisplayName"];
- if (app_Name == nil) {
- app_Name = [infoDict objectForKey:@"CFBundleName"];
- }
-
- NSString *messageString = [NSString stringWithFormat:@"[前往:设置 - 隐私 - 照片 - %@] 允许应用访问", app_Name];
- UIAlertController *alertC = [UIAlertController alertControllerWithTitle:@"温馨提示" message:messageString preferredStyle:(UIAlertControllerStyleAlert)];
- UIAlertAction *alertA = [UIAlertAction actionWithTitle:@"确定" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
-
- }];
-
- [alertC addAction:alertA];
- [_controller presentViewController:alertC animated:YES completion:nil];
- } else if (status == PHAuthorizationStatusRestricted) {
- UIAlertController *alertC = [UIAlertController alertControllerWithTitle:@"温馨提示" message:@"由于系统原因, 无法访问相册" preferredStyle:(UIAlertControllerStyleAlert)];
- UIAlertAction *alertA = [UIAlertAction actionWithTitle:@"确定" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
-
- }];
-
- [alertC addAction:alertA];
- [_controller presentViewController:alertC animated:YES completion:nil];
- }
- }
- }
- - (void)P_enterImagePickerController {
- UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
- imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
- imagePicker.delegate = self;
- [_controller presentViewController:imagePicker animated:YES completion:nil];
- }
- #pragma mark - - UIImagePickerControllerDelegate 的方法
- - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
- [_controller dismissViewControllerAnimated:YES completion:nil];
- if (_albumDidCancelImagePickerControllerBlock) {
- _albumDidCancelImagePickerControllerBlock(self);
- }
- }
- - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {
- UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
- // 创建 CIDetector,并设定识别类型:CIDetectorTypeQRCode
- CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{CIDetectorAccuracy: CIDetectorAccuracyHigh}];
- // 获取识别结果
- NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image.CGImage]];
- if (features.count == 0) {
- [_controller dismissViewControllerAnimated:YES completion:^{
- if (self.albumResultBlock) {
- self.albumResultBlock(self, nil);
- }
- }];
- return;
- } else {
- for (int index = 0; index < [features count]; index ++) {
- CIQRCodeFeature *feature = [features objectAtIndex:index];
- NSString *resultStr = feature.messageString;
- if (_configure.openLog == YES) {
- NSLog(@"相册中读取二维码数据信息 - - %@", resultStr);
- }
- self.detectorString = resultStr;
- }
- [_controller dismissViewControllerAnimated:YES completion:^{
- if (self.albumResultBlock) {
- self.albumResultBlock(self, self.detectorString);
- }
- }];
- }
- }
- - (void)setBlockWithQRCodeObtainAlbumDidCancelImagePickerController:(SGQRCodeObtainAlbumDidCancelImagePickerControllerBlock)block {
- _albumDidCancelImagePickerControllerBlock = block;
- }
- - (void)setBlockWithQRCodeObtainAlbumResult:(SGQRCodeObtainAlbumResultBlock)block {
- _albumResultBlock = block;
- }
- #pragma mark - - 手电筒相关方法
- - (void)openFlashlight {
- AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
- NSError *error = nil;
- if ([captureDevice hasTorch]) {
- BOOL locked = [captureDevice lockForConfiguration:&error];
- if (locked) {
- [captureDevice setTorchMode:AVCaptureTorchModeOn];
- [captureDevice unlockForConfiguration];
- }
- }
- }
- - (void)closeFlashlight {
- AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
- if ([captureDevice hasTorch]) {
- [captureDevice lockForConfiguration:nil];
- [captureDevice setTorchMode:AVCaptureTorchModeOff];
- [captureDevice unlockForConfiguration];
- }
- }
- @end
|