DYFCodeScannerViewController.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. //
  2. // DYFCodeScannerViewController.m
  3. //
  4. // Created by dyf on 2018/01/28.
  5. // Copyright © 2018 dyf. All rights reserved.
  6. //
  7. // Permission is hereby granted, free of charge, to any person obtaining a copy
  8. // of this software and associated documentation files (the "Software"), to deal
  9. // in the Software without restriction, including without limitation the rights
  10. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. // copies of the Software, and to permit persons to whom the Software is
  12. // furnished to do so, subject to the following conditions:
  13. //
  14. // The above copyright notice and this permission notice shall be included in
  15. // all copies or substantial portions of the Software.
  16. //
  17. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. // THE SOFTWARE.
  24. //
  25. #import "DYFCodeScannerViewController.h"
  26. #import "DYFCodeScannerPreView.h"
  27. #import "DYFCodeScannerMacros.h"
  28. #import "UIImage+QRCode.h"
  29. @interface DYFCodeScannerViewController () <AVCaptureMetadataOutputObjectsDelegate, DYFCodeScannerPreViewDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate, UIGestureRecognizerDelegate>
  30. @property (nonatomic, strong) AVCaptureSession *session; // 会话
  31. @property (nonatomic, strong) AVCaptureDeviceInput *input; // 输入流
  32. @property (nonatomic, strong) DYFCodeScannerPreView *preView; // 预览视图
  33. @end
  34. @implementation DYFCodeScannerViewController
  35. - (instancetype)init {
  36. self = [super init];
  37. if (self) {
  38. [self configure];
  39. }
  40. return self;
  41. }
  42. - (void)configure {
  43. self.scanType = DYFCodeScannerTypeAll;
  44. }
  45. - (UIStatusBarStyle)preferredStatusBarStyle {
  46. return UIStatusBarStyleLightContent;
  47. }
  48. - (BOOL)shouldAutorotate {
  49. return NO;
  50. }
  51. - (UIInterfaceOrientationMask)supportedInterfaceOrientations {
  52. return UIInterfaceOrientationMaskPortrait;
  53. }
  54. - (void)viewDidLoad {
  55. [super viewDidLoad];
  56. [self configureNavigationBar];
  57. [self addPreView];
  58. [self requestAccess];
  59. }
  60. - (void)viewWillAppear:(BOOL)animated {
  61. [super viewWillAppear:animated];
  62. if (self.navigationBarHidden) {
  63. [self hideNavigationBar:YES];
  64. }
  65. if (self.session) {
  66. [self.session startRunning];
  67. }
  68. [self.preView startScan];
  69. }
  70. - (void)viewDidAppear:(BOOL)animated {
  71. [super viewDidAppear:animated];
  72. if (self.navigationController) {
  73. self.navigationController.interactivePopGestureRecognizer.enabled = YES;
  74. }
  75. }
  76. - (void)viewWillDisappear:(BOOL)animated {
  77. [super viewWillDisappear:animated];
  78. if (self.navigationBarHidden) {
  79. [self hideNavigationBar:NO];
  80. }
  81. if (self.session) {
  82. [self.session stopRunning];
  83. }
  84. [self.preView stopScan];
  85. }
  86. - (void)hideNavigationBar:(BOOL)hidden {
  87. if (self.navigationController) {
  88. self.navigationController.navigationBarHidden = hidden;
  89. }
  90. }
  91. - (void)configureNavigationBar {
  92. if (self.navigationController) {
  93. self.navigationController.interactivePopGestureRecognizer.delegate = self;
  94. self.navigationItem.title = self.navigationTitle;
  95. if (!self.navigationBarHidden) {
  96. UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
  97. backButton.frame = CGRectMake(0, 0, 40, 40);
  98. [backButton setImage:DYFBundleImageNamed(@"code_scanner_blueBack") forState:UIControlStateNormal];
  99. [backButton addTarget:self action:@selector(backButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
  100. backButton.imageEdgeInsets = UIEdgeInsetsMake(3, -10, 3, 30);
  101. UIBarButtonItem *spaceItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
  102. spaceItem.width = -20;
  103. UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
  104. self.navigationItem.leftBarButtonItems = @[spaceItem, backItem];
  105. self.preView.hasNavigationBar = YES;
  106. } else {
  107. self.preView.hasNavigationBar = NO;
  108. }
  109. } else {
  110. self.preView.hasNavigationBar = NO;
  111. }
  112. }
  113. - (void)backButtonClicked:(UIButton *)sender {
  114. [self.navigationController popViewControllerAnimated:YES];
  115. }
  116. - (void)requestAccess {
  117. [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
  118. dispatch_async(dispatch_get_main_queue(), ^{
  119. if (granted) {
  120. [self configureScanner];
  121. } else {
  122. NSString *msg = @"请在系统“设置”-“隐私”-“相机”选项中,允许App访问你的相机";
  123. DYFLog(@"[W]: %@", msg);
  124. if (self.resultHandler) {
  125. self.resultHandler(NO, msg);
  126. }
  127. }
  128. });
  129. }];
  130. }
  131. - (DYFCodeScannerPreView *)preView {
  132. if (!_preView) {
  133. _preView = [[DYFCodeScannerPreView alloc] initWithFrame:self.view.bounds];
  134. _preView.delegate = self;
  135. _preView.title = self.navigationTitle;
  136. _preView.tipLabel.text = self.tipString;
  137. }
  138. return _preView;
  139. }
  140. - (void)addPreView {
  141. [self.view addSubview:self.preView];
  142. }
  143. - (void)configureScanner {
  144. // 获取摄像设备
  145. AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
  146. // 创建输入流
  147. AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
  148. // 创建输出流
  149. AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc] init];
  150. // 设置代理在主线程里刷新
  151. [output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
  152. [self setInput:input];
  153. [self checkTorch];
  154. // 会话
  155. self.session = [[AVCaptureSession alloc] init];
  156. if ([self.session canAddInput:input]) {
  157. [self.session addInput:input];
  158. }
  159. if([self.session canAddOutput:output]) {
  160. [self.session addOutput:output];
  161. }
  162. // 设置数据采集质量
  163. [self.session setSessionPreset:AVCaptureSessionPresetHigh];
  164. // 设置扫码支持的编码格式
  165. switch (self.scanType) {
  166. case DYFCodeScannerTypeAll:
  167. output.metadataObjectTypes = @[AVMetadataObjectTypeQRCode,
  168. AVMetadataObjectTypeEAN13Code,
  169. AVMetadataObjectTypeEAN8Code,
  170. AVMetadataObjectTypeUPCECode,
  171. AVMetadataObjectTypeCode39Code,
  172. AVMetadataObjectTypeCode39Mod43Code,
  173. AVMetadataObjectTypeCode93Code,
  174. AVMetadataObjectTypeCode128Code,
  175. AVMetadataObjectTypePDF417Code];
  176. break;
  177. case DYFCodeScannerTypeQRCode:
  178. output.metadataObjectTypes = @[AVMetadataObjectTypeQRCode];
  179. break;
  180. case DYFCodeScannerTypeBarcode:
  181. output.metadataObjectTypes = @[AVMetadataObjectTypeEAN13Code,
  182. AVMetadataObjectTypeEAN8Code,
  183. AVMetadataObjectTypeUPCECode,
  184. AVMetadataObjectTypeCode39Code,
  185. AVMetadataObjectTypeCode39Mod43Code,
  186. AVMetadataObjectTypeCode93Code,
  187. AVMetadataObjectTypeCode128Code,
  188. AVMetadataObjectTypePDF417Code];
  189. break;
  190. default:
  191. break;
  192. }
  193. if (self.input.device.isFocusPointOfInterestSupported &&
  194. [self.input.device isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
  195. [self.input.device lockForConfiguration:nil];
  196. [self.input.device setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
  197. [self.input.device unlockForConfiguration];
  198. }
  199. AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession: self.session];
  200. previewLayer.frame = self.view.layer.bounds;
  201. previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
  202. // 将预览层添加到界面中
  203. [self.view.layer insertSublayer:previewLayer atIndex:0];
  204. [self.session startRunning];
  205. }
  206. - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
  207. if (!self.preView.isReading) { return; }
  208. if (metadataObjects.count > 0) {
  209. [self.session stopRunning];
  210. [self.preView stopScan];
  211. AVMetadataMachineReadableCodeObject *metadataObject = metadataObjects[0];
  212. NSString *result = metadataObject.stringValue;
  213. [self captureOutputWithCompletion:^{
  214. !self.resultHandler ?: self.resultHandler(result ? YES : NO, result);
  215. }];
  216. }
  217. }
  218. - (void)captureOutputWithCompletion:(void (^)(void))completionHandler {
  219. UINavigationController *nc = self.navigationController;
  220. if (nc) {
  221. if (nc.viewControllers.count == 1) {
  222. [nc dismissViewControllerAnimated:YES completion:completionHandler];
  223. } else {
  224. [nc popViewControllerAnimated:YES];
  225. !completionHandler ?: completionHandler();
  226. }
  227. } else {
  228. [self dismissViewControllerAnimated:YES completion:completionHandler];
  229. }
  230. }
  231. - (void)checkTorch {
  232. AVCaptureDevice *device = self.input.device;
  233. [device lockForConfiguration:nil];
  234. self.preView.hasTorch = device.hasTorch;
  235. [device unlockForConfiguration];
  236. }
  237. #pragma mark - DYFCodeScannerPreViewDelegate
  238. - (void)back {
  239. DYFLog();
  240. [self captureOutputWithCompletion:NULL];
  241. }
  242. - (void)turnOnTorch {
  243. DYFLog();
  244. [self configureTorch:YES];
  245. }
  246. - (void)turnOffTorch {
  247. DYFLog();
  248. [self configureTorch:NO];
  249. }
  250. - (void)configureTorch:(BOOL)onOrOff {
  251. AVCaptureDevice *device = self.input.device;
  252. // 锁定
  253. [device lockForConfiguration:nil];
  254. // 判断是否有闪光灯
  255. if ([device hasTorch]) {
  256. if (onOrOff) {
  257. device.torchMode = AVCaptureTorchModeOn;
  258. } else {
  259. device.torchMode = AVCaptureTorchModeOff;
  260. }
  261. }
  262. // 解锁
  263. [device unlockForConfiguration];
  264. }
  265. - (void)openPhotoLibrary {
  266. DYFLog();
  267. [self presentImagePicker];
  268. }
  269. - (void)queryHistory {
  270. DYFLog();
  271. }
  272. - (void)presentImagePicker {
  273. if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
  274. UIImagePickerController *ipc = [[UIImagePickerController alloc] init];
  275. ipc.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
  276. ipc.delegate = self;
  277. [self presentViewController:ipc animated:YES completion:NULL];
  278. } else {
  279. DYFLog(@"[W]: ImagePicker: SourceType is not available.");
  280. }
  281. }
  282. - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *)info {
  283. [picker dismissViewControllerAnimated:YES completion:NULL];
  284. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  285. UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
  286. NSString *codeMsg = image.yf_stringValue;
  287. DYFLog(@"OriginalImage: %@, QRCode message: %@", image, codeMsg);
  288. [self captureOutputWithCompletion:^{
  289. !self.resultHandler ?: self.resultHandler(codeMsg ? YES : NO, codeMsg);
  290. }];
  291. });
  292. }
  293. - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
  294. [picker dismissViewControllerAnimated:YES completion:NULL];
  295. }
  296. - (void)zoom:(CGFloat)scale {
  297. AVCaptureDevice *device = self.input.device;
  298. [device lockForConfiguration:nil];
  299. device.videoZoomFactor = scale;
  300. [device unlockForConfiguration];
  301. }
  302. - (void)didReceiveMemoryWarning {
  303. [super didReceiveMemoryWarning];
  304. // Dispose of any resources that can be recreated.
  305. }
  306. - (void)dealloc {
  307. DYFLog();
  308. }
  309. @end