ScanQRCodeViewController.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. //
  2. // ScanQRCodeViewController.m
  3. // MIT_Shop
  4. //
  5. // Created by 翟玉磊 on 2017/11/18.
  6. // Copyright © 2017年 翟玉磊. All rights reserved.
  7. //
  8. #import "ScanQRCodeViewController.h"
  9. #import <AVFoundation/AVFoundation.h>
  10. #import <AssetsLibrary/AssetsLibrary.h>
  11. #import "LWQRCodeBackgroundView.h"
  12. #import "LWQRCodeScanView.h"
  13. #define ScanY 150 //扫描区域y
  14. #define ScanWidth 250 //扫描区域宽度
  15. #define ScanHeight 250 //扫描区域高度
  16. @interface ScanQRCodeViewController ()<AVCaptureMetadataOutputObjectsDelegate,UIAlertViewDelegate,UIImagePickerControllerDelegate,UINavigationControllerDelegate>
  17. @property(nonatomic,strong)AVCaptureDevice *device;//创建相机
  18. @property(nonatomic,strong)AVCaptureDeviceInput *input;//创建输入设备
  19. @property(nonatomic,strong)AVCaptureMetadataOutput *output;//创建输出设备
  20. @property(nonatomic,strong)AVCaptureSession *session;//创建捕捉类
  21. @property(strong,nonatomic)AVCaptureVideoPreviewLayer *preview;//视觉输出预览层
  22. @property(strong,nonatomic)LWQRCodeScanView *scanView;
  23. @end
  24. @implementation ScanQRCodeViewController
  25. - (void)dealloc
  26. {
  27. self.device = nil;
  28. [self.session stopRunning];
  29. self.session = nil;
  30. self.input = nil;
  31. self.output = nil;
  32. self.preview = nil;
  33. [self.scanView stopAnimaion];
  34. self.scanView = nil;
  35. }
  36. - (void)viewDidLoad {
  37. [super viewDidLoad];
  38. // Do any additional setup after loading the view.
  39. self.title = @"二维码/条码";
  40. [self capture];
  41. [self UI];
  42. }
  43. #pragma mark - 初始化UI
  44. - (void)UI
  45. {
  46. self.view.backgroundColor = [UIColor whiteColor];
  47. //扫描区域
  48. CGRect scanFrame = CGRectMake((SCREEN_WIDTH-ScanWidth)/2, ScanY, ScanWidth, ScanHeight);
  49. // 创建view,用来辅助展示扫描的区域
  50. self.scanView = [[LWQRCodeScanView alloc] initWithFrame:scanFrame];
  51. [self.view addSubview:self.scanView];
  52. //扫描区域外的背景
  53. LWQRCodeBackgroundView *qrcodeBackgroundView = [[LWQRCodeBackgroundView alloc] initWithFrame:self.view.bounds];
  54. qrcodeBackgroundView.scanFrame = scanFrame;
  55. [self.view addSubview:qrcodeBackgroundView];
  56. //提示文字
  57. UILabel *label = [UILabel new];
  58. label.text = @"将二维码/条形码放入框内,即可自动扫描";
  59. label.textAlignment = NSTextAlignmentCenter;
  60. label.font = [UIFont systemFontOfSize:15];
  61. label.textColor = [UIColor colorWithRed:224/255.0 green:224/255.0 blue:224/255.0 alpha:1.0];
  62. label.frame = CGRectMake(0, CGRectGetMaxY(self.scanView.frame)+10, SCREEN_WIDTH, 20);
  63. [self.view addSubview:label];
  64. //灯光和相册
  65. NSArray *arr = @[@"灯光",@"相册"];
  66. for (int i = 0; i < 2; i++) {
  67. UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
  68. [btn setTitle:arr[i] forState:UIControlStateNormal];
  69. btn.tag = i;
  70. btn.backgroundColor = [UIColor colorWithRed:0/255.0 green:0/255.0 blue:0/255.0 alpha:0.5];
  71. btn.frame = CGRectMake(SCREEN_WIDTH/2*i, SCREEN_HEIGHT- 50 - HOME_INDICATOR_HEIGHT, SCREEN_WIDTH/2, 50);
  72. [btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
  73. [self.view addSubview:btn];
  74. }
  75. }
  76. #pragma mark - 菜单按钮点击事件
  77. - (void)btnClick:(UIButton *)sender
  78. {
  79. if (sender.tag == 0) {
  80. Class capture = NSClassFromString(@"AVCaptureDevice");
  81. if (capture != nil) {
  82. if ([self.device hasTorch] && [self.device hasFlash]) {
  83. [self.device lockForConfiguration:nil];
  84. sender.selected = !sender.selected;
  85. if (sender.selected) {
  86. [self.device setTorchMode:AVCaptureTorchModeOn];
  87. [self.device setFlashMode:AVCaptureFlashModeOn];
  88. } else {
  89. [self.device setTorchMode:AVCaptureTorchModeOff];
  90. [self.device setFlashMode:AVCaptureFlashModeOff];
  91. }
  92. [self.device unlockForConfiguration];
  93. }
  94. }
  95. } else {
  96. if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]){
  97. UIImagePickerController *imagePicker = [[UIImagePickerController alloc]init];
  98. imagePicker.allowsEditing = YES;
  99. imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
  100. imagePicker.delegate = self;
  101. [self presentViewController:imagePicker animated:YES completion:^{
  102. NSLog(@"打开相册");
  103. }];
  104. }else{
  105. NSLog(@"不能打开相册");
  106. }
  107. }
  108. }
  109. #pragma mark - 从相册选择识别二维码
  110. -(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{
  111. //获取选中的照片
  112. UIImage *image = info[UIImagePickerControllerEditedImage];
  113. if (!image) {
  114. image = info[UIImagePickerControllerOriginalImage];
  115. }
  116. //初始化 将类型设置为二维码
  117. CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:nil];
  118. [picker dismissViewControllerAnimated:YES completion:^{
  119. //扫描成功播放音效
  120. [self playSoundEffect:@"Qcodesound.caf"];
  121. //设置数组,放置识别完之后的数据
  122. NSArray *features = [detector featuresInImage:[CIImage imageWithData:UIImagePNGRepresentation(image)]];
  123. //判断是否有数据(即是否是二维码)
  124. if (features.count >= 1) {
  125. //取第一个元素就是二维码所存放的文本信息
  126. CIQRCodeFeature *feature = features[0];
  127. NSString *scannedResult = feature.messageString;
  128. //扫描结果回调
  129. if (self->_delegate && [self->_delegate respondsToSelector:@selector(scanCompletionCallbackWithText:ScanQRCodeViewController:)]) {
  130. [self.session startRunning];
  131. [self.scanView startAnimaion];
  132. [self->_delegate scanCompletionCallbackWithText:scannedResult ScanQRCodeViewController:self];
  133. // [self.navigationController popViewControllerAnimated:YES];
  134. }
  135. }else{
  136. UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"扫描结果"
  137. message:@"不是二维码/条码图片"
  138. delegate:self
  139. cancelButtonTitle:nil
  140. otherButtonTitles:@"确定", nil];
  141. [self.view addSubview:alert];
  142. [alert show];
  143. }
  144. }];
  145. }
  146. //进入拍摄页面点击取消按钮
  147. - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{
  148. [self dismissViewControllerAnimated:YES completion:nil];
  149. }
  150. #pragma mark - 初始化扫描设备
  151. - (void)capture
  152. {
  153. //如果是模拟器返回(模拟器获取不到摄像头)
  154. if (TARGET_IPHONE_SIMULATOR) {
  155. return;
  156. }
  157. // 下面的是比较重要的,也是最容易出现崩溃的原因,就是我们的输出流的类型
  158. // 1.这里可以设置多种输出类型,这里必须要保证session层包括输出流
  159. // 2.必须要当前项目访问相机权限必须通过,所以最好在程序进入当前页面的时候进行一次权限访问的判断
  160. AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
  161. if(authStatus ==AVAuthorizationStatusRestricted|| authStatus ==AVAuthorizationStatusDenied){
  162. UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:@"请在iPhone的“设置”-“隐私”-“相机”功能中,找到“某某应用”打开相机访问权限" delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil];
  163. [alert show];
  164. return;
  165. }
  166. //初始化基础"引擎"Device
  167. self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
  168. //初始化输入流 Input,并添加Device
  169. self.input = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:nil];
  170. //初始化输出流Output
  171. self.output = [[AVCaptureMetadataOutput alloc] init];
  172. //设置输出流的相关属性
  173. // 确定输出流的代理和所在的线程,这里代理遵循的就是上面我们在准备工作中提到的第一个代理,至于线程的选择,建议选在主线程,这样方便当前页面对数据的捕获.
  174. [self.output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
  175. //设置扫描区域的大小 rectOfInterest 默认值是CGRectMake(0, 0, 1, 1) 按比例设置
  176. self.output.rectOfInterest = CGRectMake(ScanY/SCREEN_HEIGHT,((SCREEN_WIDTH-ScanWidth)/2)/SCREEN_WIDTH,ScanHeight/SCREEN_HEIGHT,ScanWidth/SCREEN_WIDTH);
  177. /*
  178. // AVCaptureSession 预设适用于高分辨率照片质量的输出
  179. AVF_EXPORT NSString *const AVCaptureSessionPresetPhoto NS_AVAILABLE(10_7, 4_0) __TVOS_PROHIBITED;
  180. // AVCaptureSession 预设适用于高分辨率照片质量的输出
  181. AVF_EXPORT NSString *const AVCaptureSessionPresetHigh NS_AVAILABLE(10_7, 4_0) __TVOS_PROHIBITED;
  182. // AVCaptureSession 预设适用于中等质量的输出。 实现的输出适合于在无线网络共享的视频和音频比特率。
  183. AVF_EXPORT NSString *const AVCaptureSessionPresetMedium NS_AVAILABLE(10_7, 4_0) __TVOS_PROHIBITED;
  184. // AVCaptureSession 预设适用于低质量的输出。为了实现的输出视频和音频比特率适合共享 3G。
  185. AVF_EXPORT NSString *const AVCaptureSessionPresetLow NS_AVAILABLE(10_7, 4_0) __TVOS_PROHIBITED;
  186. */
  187. // 初始化session
  188. self.session = [[AVCaptureSession alloc]init];
  189. // 设置session类型,AVCaptureSessionPresetHigh 是 sessionPreset 的默认值。
  190. [_session setSessionPreset:AVCaptureSessionPresetHigh];
  191. //将输入流和输出流添加到session中
  192. // 添加输入流
  193. if ([_session canAddInput:self.input]) {
  194. [_session addInput:self.input];
  195. }
  196. // 添加输出流
  197. if ([_session canAddOutput:self.output]) {
  198. [_session addOutput:self.output];
  199. //扫描格式
  200. NSMutableArray *metadataObjectTypes = [NSMutableArray array];
  201. [metadataObjectTypes addObjectsFromArray:@[
  202. AVMetadataObjectTypeQRCode,
  203. AVMetadataObjectTypeEAN13Code,
  204. AVMetadataObjectTypeEAN8Code,
  205. AVMetadataObjectTypeCode128Code,
  206. AVMetadataObjectTypeCode39Code,
  207. AVMetadataObjectTypeCode93Code,
  208. AVMetadataObjectTypeCode39Mod43Code,
  209. AVMetadataObjectTypePDF417Code,
  210. AVMetadataObjectTypeAztecCode,
  211. AVMetadataObjectTypeUPCECode,
  212. ]];
  213. // >= ios 8
  214. if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_7_1) {
  215. [metadataObjectTypes addObjectsFromArray:@[AVMetadataObjectTypeInterleaved2of5Code,
  216. AVMetadataObjectTypeITF14Code,
  217. AVMetadataObjectTypeDataMatrixCode]];
  218. }
  219. //设置扫描格式
  220. self.output.metadataObjectTypes= metadataObjectTypes;
  221. }
  222. //设置输出展示平台AVCaptureVideoPreviewLayer
  223. // 初始化
  224. self.preview =[AVCaptureVideoPreviewLayer layerWithSession:_session];
  225. // 设置Video Gravity,顾名思义就是视频播放时的拉伸方式,默认是AVLayerVideoGravityResizeAspect
  226. // AVLayerVideoGravityResizeAspect 保持视频的宽高比并使播放内容自动适应播放窗口的大小。
  227. // AVLayerVideoGravityResizeAspectFill 和前者类似,但它是以播放内容填充而不是适应播放窗口的大小。最后一个值会拉伸播放内容以适应播放窗口.
  228. // 因为考虑到全屏显示以及设备自适应,这里我们采用fill填充
  229. self.preview.videoGravity =AVLayerVideoGravityResizeAspectFill;
  230. // 设置展示平台的frame
  231. self.preview.frame = CGRectMake(0, 0, SCREEN_WIDTH, VIEW_BOUNDS_HEIGHT);
  232. // 因为 AVCaptureVideoPreviewLayer是继承CALayer,所以添加到当前view的layer层
  233. [self.view.layer insertSublayer:self.preview atIndex:0];
  234. //开始
  235. [self.session startRunning];
  236. }
  237. - (void)viewWillDisappear:(BOOL)animated
  238. {
  239. [super viewWillDisappear:animated];
  240. [self.session stopRunning];
  241. [self.scanView stopAnimaion];
  242. }
  243. - (void)viewWillAppear:(BOOL)animated
  244. {
  245. [super viewWillAppear:animated];
  246. [self.session startRunning];
  247. [self.scanView startAnimaion];
  248. }
  249. - (void)startAnimaion {
  250. [self.session startRunning];
  251. [self.scanView startAnimaion];
  252. }
  253. #pragma mark - AVCaptureMetadataOutputObjectsDelegate
  254. #pragma mark - 扫描结果处理
  255. - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
  256. {
  257. //扫描成功播放音效
  258. [self playSoundEffect:@"Qcodesound.caf"];
  259. // 判断扫描结果的数据是否存在
  260. if ([metadataObjects count] >0){
  261. // 如果存在数据,停止扫描
  262. [self.session stopRunning];
  263. [self.scanView stopAnimaion];
  264. // AVMetadataMachineReadableCodeObject是AVMetadataObject的具体子类定义的特性检测一维或二维条形码。
  265. // AVMetadataMachineReadableCodeObject代表一个单一的照片中发现机器可读的代码。这是一个不可变对象描述条码的特性和载荷。
  266. // 在支持的平台上,AVCaptureMetadataOutput输出检测机器可读的代码对象的数组
  267. AVMetadataMachineReadableCodeObject * metadataObject = [metadataObjects objectAtIndex:0];
  268. // 获取扫描到的信息
  269. NSString *stringValue = metadataObject.stringValue;
  270. //扫描结果回调
  271. if (_delegate && [_delegate respondsToSelector:@selector(scanCompletionCallbackWithText: ScanQRCodeViewController:)]) {
  272. [_delegate scanCompletionCallbackWithText:stringValue ScanQRCodeViewController:self];
  273. }
  274. } else {
  275. UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"扫描结果"
  276. message:@"不是二维码/条码图片"
  277. delegate:self
  278. cancelButtonTitle:nil
  279. otherButtonTitles:@"确定", nil];
  280. [self.view addSubview:alert];
  281. [alert show];
  282. }
  283. }
  284. - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
  285. [self.session startRunning];
  286. [self.scanView startAnimaion];
  287. }
  288. #pragma mark - - - 扫描提示声
  289. /** 播放音效文件 */
  290. - (void)playSoundEffect:(NSString *)name{
  291. // 获取音效
  292. NSString *audioFile = [[NSBundle mainBundle] pathForResource:name ofType:nil];
  293. NSURL *fileUrl = [NSURL fileURLWithPath:audioFile];
  294. // 1、获得系统声音ID
  295. SystemSoundID soundID = 0;
  296. AudioServicesCreateSystemSoundID((__bridge CFURLRef)(fileUrl), &soundID);
  297. // 如果需要在播放完之后执行某些操作,可以调用如下方法注册一个播放完成回调函数
  298. AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, scanSoundCompleteCallback, NULL);
  299. // 2、播放音频
  300. AudioServicesPlaySystemSound(soundID); // 播放音效
  301. }
  302. /**
  303. * 播放完成回调函数
  304. *
  305. * @param soundID 系统声音ID
  306. * @param clientData 回调时传递的数据
  307. */
  308. void scanSoundCompleteCallback(SystemSoundID soundID, void *clientData){
  309. NSLog(@"播放完成...");
  310. }
  311. /*
  312. #pragma mark - Navigation
  313. // In a storyboard-based application, you will often want to do a little preparation before navigation
  314. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  315. // Get the new view controller using [segue destinationViewController].
  316. // Pass the selected object to the new view controller.
  317. }
  318. */
  319. @end