LXDScanView.m 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. //
  2. // LXDScanView.m
  3. // LXDScanQRCode
  4. //
  5. // Created by 林欣达 on 15/10/14.
  6. // Copyright © 2015年 cnpayany. All rights reserved.
  7. //
  8. #import "LXDScanView.h"
  9. #import <AVFoundation/AVFoundation.h>
  10. NSString * const LXDSuccessScanQRCodeNotification = @"LXDSuccessScanQRCodeNotification";
  11. NSString * const LXDScanQRCodeMessageKey = @"LXDScanQRCodeMessageKey";
  12. #define SCANSPACEOFFSET 0.15f
  13. #define REMINDTEXT @"将二维码/条码放入框内,即可自动扫描"
  14. #define SCREENBOUNDS [UIScreen mainScreen].bounds
  15. #define SCREENWIDTH CGRectGetWidth([UIScreen mainScreen].bounds)
  16. #define SCREENHEIGHT CGRectGetHeight([UIScreen mainScreen].bounds)
  17. @interface LXDScanView ()<AVCaptureMetadataOutputObjectsDelegate>
  18. @property (nonatomic, strong) AVCaptureSession * session;
  19. @property (nonatomic, strong) AVCaptureDeviceInput * input;
  20. @property (nonatomic, strong) AVCaptureMetadataOutput * output;
  21. @property (nonatomic, strong) AVCaptureVideoPreviewLayer * scanView;
  22. @property (nonatomic, strong) CAShapeLayer * maskLayer;
  23. @property (nonatomic, strong) CAShapeLayer * shadowLayer;
  24. @property (nonatomic, strong) CAShapeLayer * scanRectLayer;
  25. @property (nonatomic, assign) CGRect scanRect;
  26. @property (nonatomic, strong) UILabel * remind;
  27. @end
  28. @implementation LXDScanView
  29. #pragma mark - initial
  30. + (instancetype)scanViewShowInController: (UIViewController *)controller
  31. {
  32. if (!controller) { return nil; }
  33. LXDScanView * scanView = [[LXDScanView alloc] initWithFrame: [UIScreen mainScreen].bounds];
  34. if ([controller conformsToProtocol: @protocol(LXDScanViewDelegate)]) {
  35. scanView.delegate = (UIViewController<LXDScanViewDelegate> *)controller;
  36. }
  37. return scanView;
  38. }
  39. - (instancetype)initWithFrame: (CGRect)frame
  40. {
  41. frame = SCREENBOUNDS;
  42. if (self = [super initWithFrame: frame]) {
  43. self.backgroundColor = [UIColor colorWithWhite: 0.f alpha: 0.2f];
  44. [self.layer addSublayer: self.scanView];
  45. [self setupScanRect];
  46. [self addSubview: self.remind];
  47. self.layer.masksToBounds = YES;
  48. }
  49. return self;
  50. }
  51. #pragma mark - life
  52. /**
  53. * 释放前停止会话
  54. */
  55. - (void)dealloc
  56. {
  57. [self stop];
  58. }
  59. #pragma mark - operate
  60. /**
  61. * 开始视频会话
  62. */
  63. - (void)start
  64. {
  65. [self.session startRunning];
  66. }
  67. /**
  68. * 停止视频会话
  69. */
  70. - (void)stop
  71. {
  72. [self.session stopRunning];
  73. }
  74. #pragma mark - lazy load
  75. #pragma mark >> capture I/O <<
  76. /**
  77. * 会话对象
  78. */
  79. - (AVCaptureSession *)session
  80. {
  81. if (!_session) {
  82. _session = [AVCaptureSession new];
  83. [_session setSessionPreset: AVCaptureSessionPresetHigh]; //高质量采集
  84. [self setupIODevice];
  85. }
  86. return _session;
  87. }
  88. /**
  89. * 视频输入设备
  90. */
  91. - (AVCaptureDeviceInput *)input
  92. {
  93. if (!_input) {
  94. AVCaptureDevice * device = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeVideo];
  95. _input = [AVCaptureDeviceInput deviceInputWithDevice: device error: nil];
  96. }
  97. return _input;
  98. }
  99. /**
  100. * 数据输出对象
  101. */
  102. - (AVCaptureMetadataOutput *)output
  103. {
  104. if (!_output) {
  105. _output = [AVCaptureMetadataOutput new];
  106. [_output setMetadataObjectsDelegate: self queue: dispatch_get_main_queue()];
  107. }
  108. return _output;
  109. }
  110. /**
  111. * 扫描视图
  112. */
  113. - (AVCaptureVideoPreviewLayer *)scanView
  114. {
  115. if (!_scanView) {
  116. _scanView = [AVCaptureVideoPreviewLayer layerWithSession: self.session];
  117. _scanView.videoGravity = AVLayerVideoGravityResizeAspectFill;
  118. _scanView.frame = self.bounds;
  119. }
  120. return _scanView;
  121. }
  122. #pragma mark >> common <<
  123. /**
  124. * 扫描范围
  125. */
  126. - (CGRect)scanRect
  127. {
  128. if (CGRectEqualToRect(_scanRect, CGRectZero)) {
  129. CGRect rectOfInterest = self.output.rectOfInterest;
  130. CGFloat yOffset = rectOfInterest.size.width - rectOfInterest.origin.x;
  131. CGFloat xOffset = 1 - 2 * SCANSPACEOFFSET;
  132. _scanRect = CGRectMake(rectOfInterest.origin.y * SCREENWIDTH, rectOfInterest.origin.x * SCREENHEIGHT, xOffset * SCREENWIDTH, yOffset * SCREENHEIGHT);
  133. }
  134. return _scanRect;
  135. }
  136. /**
  137. * 提示文本
  138. */
  139. - (UILabel *)remind
  140. {
  141. if (!_remind) {
  142. CGRect textRect = self.scanRect;
  143. textRect.origin.y += CGRectGetHeight(textRect) + 20;
  144. textRect.size.height = 25.f;
  145. _remind = [[UILabel alloc] initWithFrame: textRect];
  146. _remind.font = [UIFont systemFontOfSize: 15.f * SCREENWIDTH / 375.f];
  147. _remind.textColor = [UIColor whiteColor];
  148. _remind.textAlignment = NSTextAlignmentCenter;
  149. _remind.text = REMINDTEXT;
  150. _remind.backgroundColor = [UIColor clearColor];
  151. }
  152. return _remind;
  153. }
  154. #pragma mark >> layer <<
  155. /**
  156. * 扫描框
  157. */
  158. - (CAShapeLayer *)scanRectLayer
  159. {
  160. if (!_scanRectLayer) {
  161. CGRect scanRect = self.scanRect;
  162. scanRect.origin.x -= 1;
  163. scanRect.origin.y -= 1;
  164. scanRect.size.width += 2;
  165. scanRect.size.height += 2;
  166. _scanRectLayer = [CAShapeLayer layer];
  167. _scanRectLayer.path = [UIBezierPath bezierPathWithRect: scanRect].CGPath;
  168. _scanRectLayer.fillColor = [UIColor clearColor].CGColor;
  169. _scanRectLayer.strokeColor = [UIColor orangeColor].CGColor;
  170. }
  171. return _scanRectLayer;
  172. }
  173. /**
  174. * 阴影层
  175. */
  176. - (CAShapeLayer *)shadowLayer
  177. {
  178. if (!_shadowLayer) {
  179. _shadowLayer = [CAShapeLayer layer];
  180. _shadowLayer.path = [UIBezierPath bezierPathWithRect: self.bounds].CGPath;
  181. _shadowLayer.fillColor = [UIColor colorWithWhite: 0 alpha: 0.75].CGColor;
  182. _shadowLayer.mask = self.maskLayer;
  183. }
  184. return _shadowLayer;
  185. }
  186. /**
  187. * 遮掩层
  188. */
  189. - (CAShapeLayer *)maskLayer
  190. {
  191. if (!_maskLayer) {
  192. _maskLayer = [CAShapeLayer layer];
  193. _maskLayer = [self generateMaskLayerWithRect: SCREENBOUNDS exceptRect: self.scanRect];
  194. }
  195. return _maskLayer;
  196. }
  197. #pragma mark - generate
  198. /**
  199. * 生成空缺部分rect的layer
  200. */
  201. - (CAShapeLayer *)generateMaskLayerWithRect: (CGRect)rect exceptRect: (CGRect)exceptRect
  202. {
  203. CAShapeLayer * maskLayer = [CAShapeLayer layer];
  204. if (!CGRectContainsRect(rect, exceptRect)) {
  205. return nil;
  206. }
  207. else if (CGRectEqualToRect(rect, CGRectZero)) {
  208. maskLayer.path = [UIBezierPath bezierPathWithRect: rect].CGPath;
  209. return maskLayer;
  210. }
  211. CGFloat boundsInitX = CGRectGetMinX(rect);
  212. CGFloat boundsInitY = CGRectGetMinY(rect);
  213. CGFloat boundsWidth = CGRectGetWidth(rect);
  214. CGFloat boundsHeight = CGRectGetHeight(rect);
  215. CGFloat minX = CGRectGetMinX(exceptRect);
  216. CGFloat maxX = CGRectGetMaxX(exceptRect);
  217. CGFloat minY = CGRectGetMinY(exceptRect);
  218. CGFloat maxY = CGRectGetMaxY(exceptRect);
  219. CGFloat width = CGRectGetWidth(exceptRect);
  220. /** 添加路径*/
  221. UIBezierPath * path = [UIBezierPath bezierPathWithRect: CGRectMake(boundsInitX, boundsInitY, minX, boundsHeight)];
  222. [path appendPath: [UIBezierPath bezierPathWithRect: CGRectMake(minX, boundsInitY, width, minY)]];
  223. [path appendPath: [UIBezierPath bezierPathWithRect: CGRectMake(maxX, boundsInitY, boundsWidth - maxX, boundsHeight)]];
  224. [path appendPath: [UIBezierPath bezierPathWithRect: CGRectMake(minX, maxY, width, boundsHeight - maxY)]];
  225. maskLayer.path = path.CGPath;
  226. return maskLayer;
  227. }
  228. #pragma mark - setup
  229. /**
  230. * 配置输入输出设置
  231. */
  232. - (void)setupIODevice
  233. {
  234. if ([self.session canAddInput: self.input]) {
  235. [_session addInput: _input];
  236. }
  237. if ([self.session canAddOutput: self.output]) {
  238. [_session addOutput: _output];
  239. _output.metadataObjectTypes = @[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code];
  240. }
  241. }
  242. /**
  243. * 配置扫描范围
  244. */
  245. - (void)setupScanRect
  246. {
  247. CGFloat size = SCREENWIDTH * (1 - 2 * SCANSPACEOFFSET);
  248. CGFloat minY = (SCREENHEIGHT - size) * 0.5 / SCREENHEIGHT;
  249. CGFloat maxY = (SCREENHEIGHT + size) * 0.5 / SCREENHEIGHT;
  250. self.output.rectOfInterest = CGRectMake(minY, SCANSPACEOFFSET, maxY, 1 - SCANSPACEOFFSET * 2);
  251. [self.layer addSublayer: self.shadowLayer];
  252. [self.layer addSublayer: self.scanRectLayer];
  253. }
  254. #pragma mark - touch
  255. /**
  256. * 点击空白处停止扫描
  257. */
  258. - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
  259. {
  260. [self stop];
  261. [self removeFromSuperview];
  262. }
  263. #pragma mark - AVCaptureMetadataOutputObjectsDelegate
  264. /**
  265. * 二维码扫描数据返回
  266. */
  267. - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
  268. {
  269. if (metadataObjects.count > 0) {
  270. [self stop];
  271. AVMetadataMachineReadableCodeObject * metadataObject = metadataObjects[0];
  272. if ([self.delegate respondsToSelector: @selector(scanView:codeInfo:)]) {
  273. [self.delegate scanView: self codeInfo: metadataObject.stringValue];
  274. [self removeFromSuperview];
  275. } else {
  276. [[NSNotificationCenter defaultCenter] postNotificationName: LXDSuccessScanQRCodeNotification object: self userInfo: @{ LXDScanQRCodeMessageKey: metadataObject.stringValue }];
  277. }
  278. }
  279. }
  280. @end