SGQRCodeObtain.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. //
  2. // SGQRCodeObtain.m
  3. // SGQRCodeExample
  4. //
  5. // Created by kingsic on 2016/8/16.
  6. // Copyright © 2016年 kingsic. All rights reserved.
  7. //
  8. #import "SGQRCodeObtain.h"
  9. #import "SGQRCodeObtainConfigure.h"
  10. #import <Photos/Photos.h>
  11. @interface SGQRCodeObtain () <AVCaptureMetadataOutputObjectsDelegate, AVCaptureVideoDataOutputSampleBufferDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate>
  12. @property (nonatomic, weak) UIViewController *controller;
  13. @property (nonatomic, strong) SGQRCodeObtainConfigure *configure;
  14. @property (nonatomic, strong) AVCaptureSession *captureSession;
  15. @property (nonatomic, copy) SGQRCodeObtainScanResultBlock scanResultBlock;
  16. @property (nonatomic, copy) SGQRCodeObtainScanBrightnessBlock scanBrightnessBlock;
  17. @property (nonatomic, copy) SGQRCodeObtainAlbumDidCancelImagePickerControllerBlock albumDidCancelImagePickerControllerBlock;
  18. @property (nonatomic, copy) SGQRCodeObtainAlbumResultBlock albumResultBlock;
  19. @property (nonatomic, copy) NSString *detectorString;
  20. @end
  21. @implementation SGQRCodeObtain
  22. + (instancetype)QRCodeObtain {
  23. return [[self alloc] init];
  24. }
  25. - (void)dealloc {
  26. if (_configure.openLog == YES) {
  27. NSLog(@"SGQRCodeObtain - - dealloc");
  28. }
  29. }
  30. #pragma mark - - 生成二维码相关方法
  31. /**
  32. * 生成二维码
  33. *
  34. * @param data 二维码数据
  35. * @param size 二维码大小
  36. */
  37. + (UIImage *)generateQRCodeWithData:(NSString *)data size:(CGFloat)size {
  38. return [self generateQRCodeWithData:data size:size color:[UIColor blackColor] backgroundColor:[UIColor whiteColor]];
  39. }
  40. /**
  41. * 生成二维码
  42. *
  43. * @param data 二维码数据
  44. * @param size 二维码大小
  45. * @param color 二维码颜色
  46. * @param backgroundColor 二维码背景颜色
  47. */
  48. + (UIImage *)generateQRCodeWithData:(NSString *)data size:(CGFloat)size color:(UIColor *)color backgroundColor:(UIColor *)backgroundColor {
  49. NSData *string_data = [data dataUsingEncoding:NSUTF8StringEncoding];
  50. // 1、二维码滤镜
  51. CIFilter *fileter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
  52. [fileter setValue:string_data forKey:@"inputMessage"];
  53. [fileter setValue:@"H" forKey:@"inputCorrectionLevel"];
  54. CIImage *ciImage = fileter.outputImage;
  55. // 2、颜色滤镜
  56. CIFilter *color_filter = [CIFilter filterWithName:@"CIFalseColor"];
  57. [color_filter setValue:ciImage forKey:@"inputImage"];
  58. [color_filter setValue:[CIColor colorWithCGColor:color.CGColor] forKey:@"inputColor0"];
  59. [color_filter setValue:[CIColor colorWithCGColor:backgroundColor.CGColor] forKey:@"inputColor1"];
  60. // 3、生成处理
  61. CIImage *outImage = color_filter.outputImage;
  62. CGFloat scale = size / outImage.extent.size.width;
  63. outImage = [outImage imageByApplyingTransform:CGAffineTransformMakeScale(scale, scale)];
  64. return [UIImage imageWithCIImage:outImage];
  65. }
  66. /**
  67. * 生成带 logo 的二维码(推荐使用)
  68. *
  69. * @param data 二维码数据
  70. * @param size 二维码大小
  71. * @param logoImage logo
  72. * @param ratio logo 相对二维码的比例
  73. */
  74. + (UIImage *)generateQRCodeWithData:(NSString *)data size:(CGFloat)size logoImage:(UIImage *)logoImage ratio:(CGFloat)ratio {
  75. return [self generateQRCodeWithData:data size:size logoImage:logoImage ratio:ratio logoImageCornerRadius:5 logoImageBorderWidth:5 logoImageBorderColor:[UIColor whiteColor]];
  76. }
  77. /**
  78. * 生成带 logo 的二维码(拓展)
  79. *
  80. * @param data 二维码数据
  81. * @param size 二维码大小
  82. * @param logoImage logo
  83. * @param ratio logo 相对二维码的比例
  84. * @param logoImageCornerRadius logo 外边框圆角
  85. * @param logoImageBorderWidth logo 外边框宽度
  86. * @param logoImageBorderColor logo 外边框颜色
  87. */
  88. + (UIImage *)generateQRCodeWithData:(NSString *)data size:(CGFloat)size logoImage:(UIImage *)logoImage ratio:(CGFloat)ratio logoImageCornerRadius:(CGFloat)logoImageCornerRadius logoImageBorderWidth:(CGFloat)logoImageBorderWidth logoImageBorderColor:(UIColor *)logoImageBorderColor {
  89. UIImage *image = [self generateQRCodeWithData:data size:size color:[UIColor blackColor] backgroundColor:[UIColor whiteColor]];
  90. if (logoImage == nil) return image;
  91. if (ratio < 0.0 || ratio > 0.5) {
  92. ratio = 0.25;
  93. }
  94. CGFloat logoImageW = ratio * size;
  95. CGFloat logoImageH = logoImageW;
  96. CGFloat logoImageX = 0.5 * (image.size.width - logoImageW);
  97. CGFloat logoImageY = 0.5 * (image.size.height - logoImageH);
  98. CGRect logoImageRect = CGRectMake(logoImageX, logoImageY, logoImageW, logoImageH);
  99. // 绘制logo
  100. UIGraphicsBeginImageContextWithOptions(image.size, false, [UIScreen mainScreen].scale);
  101. [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
  102. if (logoImageCornerRadius < 0.0 || logoImageCornerRadius > 10) {
  103. logoImageCornerRadius = 5;
  104. }
  105. UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:logoImageRect cornerRadius:logoImageCornerRadius];
  106. if (logoImageBorderWidth < 0.0 || logoImageBorderWidth > 10) {
  107. logoImageBorderWidth = 5;
  108. }
  109. path.lineWidth = logoImageBorderWidth;
  110. [logoImageBorderColor setStroke];
  111. [path stroke];
  112. [path addClip];
  113. [logoImage drawInRect:logoImageRect];
  114. UIImage *QRCodeImage = UIGraphicsGetImageFromCurrentImageContext();
  115. UIGraphicsEndImageContext();
  116. return QRCodeImage;
  117. }
  118. #pragma mark - - 扫描二维码相关方法
  119. - (void)establishQRCodeObtainScanWithController:(UIViewController *)controller configure:(SGQRCodeObtainConfigure *)configure {
  120. if (controller == nil) {
  121. @throw [NSException exceptionWithName:@"SGQRCode" reason:@"SGQRCodeObtain 中 establishQRCodeObtainScanWithController:configuration:方法的 controller 参数不能为空" userInfo:nil];
  122. }
  123. if (configure == nil) {
  124. @throw [NSException exceptionWithName:@"SGQRCode" reason:@"SGQRCodeObtain 中 establishQRCodeObtainScanWithController:configure:方法的 configure 参数不能为空" userInfo:nil];
  125. }
  126. _controller = controller;
  127. _configure = configure;
  128. AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
  129. // 1、捕获设备输入流
  130. AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
  131. // 2、捕获元数据输出流
  132. AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];
  133. [metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
  134. // 设置扫描范围(每一个取值 0 ~ 1,以屏幕右上角为坐标原点)
  135. // 注:微信二维码的扫描范围是整个屏幕,这里并没有做处理(可不用设置)
  136. if (configure.rectOfInterest.origin.x == 0 && configure.rectOfInterest.origin.y == 0 && configure.rectOfInterest.size.width == 0 && configure.rectOfInterest.size.height == 0) {
  137. } else {
  138. metadataOutput.rectOfInterest = configure.rectOfInterest;
  139. }
  140. // 3、设置会话采集率
  141. self.captureSession.sessionPreset = configure.sessionPreset;
  142. // 4(1)、添加捕获元数据输出流到会话对象
  143. [_captureSession addOutput:metadataOutput];
  144. // 4(2)、添加捕获输出流到会话对象;构成识了别光线强弱
  145. if (configure.sampleBufferDelegate == YES) {
  146. AVCaptureVideoDataOutput *videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
  147. [videoDataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
  148. [_captureSession addOutput:videoDataOutput];
  149. }
  150. // 4(3)、添加捕获设备输入流到会话对象
  151. [_captureSession addInput:deviceInput];
  152. // 5、设置数据输出类型,需要将数据输出添加到会话后,才能指定元数据类型,否则会报错
  153. metadataOutput.metadataObjectTypes = configure.metadataObjectTypes;
  154. // 6、预览图层
  155. AVCaptureVideoPreviewLayer *videoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_captureSession];
  156. // 保持纵横比,填充层边界
  157. videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
  158. videoPreviewLayer.frame = controller.view.frame;
  159. [controller.view.layer insertSublayer:videoPreviewLayer atIndex:0];
  160. }
  161. - (AVCaptureSession *)captureSession {
  162. if (!_captureSession) {
  163. _captureSession = [[AVCaptureSession alloc] init];
  164. }
  165. return _captureSession;
  166. }
  167. - (void)startRunningWithBefore:(void (^)(void))before completion:(void (^)(void))completion {
  168. if (before) {
  169. before();
  170. }
  171. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  172. [self.captureSession startRunning];
  173. dispatch_async(dispatch_get_main_queue(), ^{
  174. if (completion) {
  175. completion();
  176. }
  177. });
  178. });
  179. }
  180. - (void)stopRunning {
  181. if (self.captureSession.isRunning) {
  182. [self.captureSession stopRunning];
  183. }
  184. }
  185. #pragma mark - - AVCaptureMetadataOutputObjectsDelegate 的方法
  186. - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
  187. NSString *resultString = nil;
  188. if (metadataObjects != nil && metadataObjects.count > 0) {
  189. AVMetadataMachineReadableCodeObject *obj = metadataObjects[0];
  190. resultString = [obj stringValue];
  191. if (_scanResultBlock) {
  192. _scanResultBlock(self, resultString);
  193. }
  194. }
  195. }
  196. #pragma mark - - AVCaptureVideoDataOutputSampleBufferDelegate 的方法
  197. - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
  198. CFDictionaryRef metadataDict = CMCopyDictionaryOfAttachments(NULL,sampleBuffer, kCMAttachmentMode_ShouldPropagate);
  199. NSDictionary *metadata = [[NSMutableDictionary alloc] initWithDictionary:(__bridge NSDictionary*)metadataDict];
  200. CFRelease(metadataDict);
  201. NSDictionary *exifMetadata = [[metadata objectForKey:(NSString *)kCGImagePropertyExifDictionary] mutableCopy];
  202. CGFloat brightnessValue = [[exifMetadata objectForKey:(NSString *)kCGImagePropertyExifBrightnessValue] floatValue];
  203. if (_scanBrightnessBlock) {
  204. _scanBrightnessBlock(self, brightnessValue);
  205. }
  206. }
  207. - (void)setBlockWithQRCodeObtainScanResult:(SGQRCodeObtainScanResultBlock)block {
  208. _scanResultBlock = block;
  209. }
  210. - (void)setBlockWithQRCodeObtainScanBrightness:(SGQRCodeObtainScanBrightnessBlock)block {
  211. _scanBrightnessBlock = block;
  212. }
  213. - (void)playSoundName:(NSString *)name {
  214. /// 静态库 path 的获取
  215. NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:nil];
  216. if (!path) {
  217. /// 动态库 path 的获取
  218. path = [[NSBundle bundleForClass:[self class]] pathForResource:name ofType:nil];
  219. }
  220. NSURL *fileUrl = [NSURL fileURLWithPath:path];
  221. SystemSoundID soundID = 0;
  222. AudioServicesCreateSystemSoundID((__bridge CFURLRef)(fileUrl), &soundID);
  223. AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallback, NULL);
  224. AudioServicesPlaySystemSound(soundID);
  225. }
  226. void soundCompleteCallback(SystemSoundID soundID, void *clientData){
  227. }
  228. #pragma mark - - 相册中读取二维码相关方法
  229. - (void)establishAuthorizationQRCodeObtainAlbumWithController:(UIViewController *)controller {
  230. if (controller == nil && _controller == nil) {
  231. @throw [NSException exceptionWithName:@"SGQRCode" reason:@"SGQRCodeObtain 中 establishAuthorizationQRCodeObtainAlbumWithController: 方法的 controller 参数不能为空" userInfo:nil];
  232. }
  233. if (_controller == nil) {
  234. _controller = controller;
  235. }
  236. AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
  237. if (device) {
  238. // 判断授权状态
  239. PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
  240. if (status == PHAuthorizationStatusNotDetermined) { // 用户还没有做出选择
  241. // 弹框请求用户授权
  242. [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
  243. if (status == PHAuthorizationStatusAuthorized) { // 用户第一次同意了访问相册权限
  244. self.isPHAuthorization = YES;
  245. dispatch_sync(dispatch_get_main_queue(), ^{
  246. [self P_enterImagePickerController];
  247. });
  248. if (self.configure.openLog == YES) {
  249. NSLog(@"用户第一次同意了访问相册权限");
  250. }
  251. } else { // 用户第一次拒绝了访问相机权限
  252. if (self.configure.openLog == YES) {
  253. NSLog(@"用户第一次拒绝了访问相册权限");
  254. }
  255. }
  256. }];
  257. } else if (status == PHAuthorizationStatusAuthorized) { // 用户允许当前应用访问相册
  258. self.isPHAuthorization = YES;
  259. if (self.configure.openLog == YES) {
  260. NSLog(@"用户允许访问相册权限");
  261. }
  262. [self P_enterImagePickerController];
  263. } else if (status == PHAuthorizationStatusDenied) { // 用户拒绝当前应用访问相册
  264. NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
  265. NSString *app_Name = [infoDict objectForKey:@"CFBundleDisplayName"];
  266. if (app_Name == nil) {
  267. app_Name = [infoDict objectForKey:@"CFBundleName"];
  268. }
  269. NSString *messageString = [NSString stringWithFormat:@"[前往:设置 - 隐私 - 照片 - %@] 允许应用访问", app_Name];
  270. UIAlertController *alertC = [UIAlertController alertControllerWithTitle:@"温馨提示" message:messageString preferredStyle:(UIAlertControllerStyleAlert)];
  271. UIAlertAction *alertA = [UIAlertAction actionWithTitle:@"确定" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
  272. }];
  273. [alertC addAction:alertA];
  274. [_controller presentViewController:alertC animated:YES completion:nil];
  275. } else if (status == PHAuthorizationStatusRestricted) {
  276. UIAlertController *alertC = [UIAlertController alertControllerWithTitle:@"温馨提示" message:@"由于系统原因, 无法访问相册" preferredStyle:(UIAlertControllerStyleAlert)];
  277. UIAlertAction *alertA = [UIAlertAction actionWithTitle:@"确定" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
  278. }];
  279. [alertC addAction:alertA];
  280. [_controller presentViewController:alertC animated:YES completion:nil];
  281. }
  282. }
  283. }
  284. - (void)P_enterImagePickerController {
  285. UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
  286. imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
  287. imagePicker.delegate = self;
  288. [_controller presentViewController:imagePicker animated:YES completion:nil];
  289. }
  290. #pragma mark - - UIImagePickerControllerDelegate 的方法
  291. - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
  292. [_controller dismissViewControllerAnimated:YES completion:nil];
  293. if (_albumDidCancelImagePickerControllerBlock) {
  294. _albumDidCancelImagePickerControllerBlock(self);
  295. }
  296. }
  297. - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {
  298. UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
  299. // 创建 CIDetector,并设定识别类型:CIDetectorTypeQRCode
  300. CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{CIDetectorAccuracy: CIDetectorAccuracyHigh}];
  301. // 获取识别结果
  302. NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image.CGImage]];
  303. if (features.count == 0) {
  304. [_controller dismissViewControllerAnimated:YES completion:^{
  305. if (self.albumResultBlock) {
  306. self.albumResultBlock(self, nil);
  307. }
  308. }];
  309. return;
  310. } else {
  311. for (int index = 0; index < [features count]; index ++) {
  312. CIQRCodeFeature *feature = [features objectAtIndex:index];
  313. NSString *resultStr = feature.messageString;
  314. if (_configure.openLog == YES) {
  315. NSLog(@"相册中读取二维码数据信息 - - %@", resultStr);
  316. }
  317. self.detectorString = resultStr;
  318. }
  319. [_controller dismissViewControllerAnimated:YES completion:^{
  320. if (self.albumResultBlock) {
  321. self.albumResultBlock(self, self.detectorString);
  322. }
  323. }];
  324. }
  325. }
  326. - (void)setBlockWithQRCodeObtainAlbumDidCancelImagePickerController:(SGQRCodeObtainAlbumDidCancelImagePickerControllerBlock)block {
  327. _albumDidCancelImagePickerControllerBlock = block;
  328. }
  329. - (void)setBlockWithQRCodeObtainAlbumResult:(SGQRCodeObtainAlbumResultBlock)block {
  330. _albumResultBlock = block;
  331. }
  332. #pragma mark - - 手电筒相关方法
  333. - (void)openFlashlight {
  334. AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
  335. NSError *error = nil;
  336. if ([captureDevice hasTorch]) {
  337. BOOL locked = [captureDevice lockForConfiguration:&error];
  338. if (locked) {
  339. [captureDevice setTorchMode:AVCaptureTorchModeOn];
  340. [captureDevice unlockForConfiguration];
  341. }
  342. }
  343. }
  344. - (void)closeFlashlight {
  345. AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
  346. if ([captureDevice hasTorch]) {
  347. [captureDevice lockForConfiguration:nil];
  348. [captureDevice setTorchMode:AVCaptureTorchModeOff];
  349. [captureDevice unlockForConfiguration];
  350. }
  351. }
  352. @end