LBXScanView.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. //
  2. // LBXScanView.swift
  3. // swiftScan
  4. //
  5. // Created by xialibing on 15/12/8.
  6. // Copyright © 2015年 xialibing. All rights reserved.
  7. //
  8. import UIKit
  9. open class LBXScanView: UIView {
  10. // 扫码区域各种参数
  11. var viewStyle = LBXScanViewStyle()
  12. // 扫码区域
  13. var scanRetangleRect = CGRect.zero
  14. // 线条扫码动画封装
  15. var scanLineAnimation: LBXScanLineAnimation?
  16. // 网格扫码动画封装
  17. var scanNetAnimation: LBXScanNetAnimation?
  18. // 线条在中间位置,不移动
  19. var scanLineStill: UIImageView?
  20. // 启动相机时 菊花等待
  21. var activityView: UIActivityIndicatorView?
  22. // 启动相机中的提示文字
  23. var labelReadying: UILabel?
  24. // 记录动画状态
  25. var isAnimationing = false
  26. /**
  27. 初始化扫描界面
  28. - parameter frame: 界面大小,一般为视频显示区域
  29. - parameter vstyle: 界面效果参数
  30. - returns: instancetype
  31. */
  32. public init(frame: CGRect, vstyle: LBXScanViewStyle) {
  33. viewStyle = vstyle
  34. switch viewStyle.anmiationStyle {
  35. case LBXScanViewAnimationStyle.LineMove:
  36. scanLineAnimation = LBXScanLineAnimation.instance()
  37. case LBXScanViewAnimationStyle.NetGrid:
  38. scanNetAnimation = LBXScanNetAnimation.instance()
  39. case LBXScanViewAnimationStyle.LineStill:
  40. scanLineStill = UIImageView()
  41. scanLineStill?.image = viewStyle.animationImage
  42. default:
  43. break
  44. }
  45. var frameTmp = frame
  46. frameTmp.origin = CGPoint.zero
  47. super.init(frame: frameTmp)
  48. backgroundColor = UIColor.clear
  49. }
  50. override init(frame: CGRect) {
  51. var frameTmp = frame
  52. frameTmp.origin = CGPoint.zero
  53. super.init(frame: frameTmp)
  54. backgroundColor = UIColor.clear
  55. }
  56. public required init?(coder aDecoder: NSCoder) {
  57. self.init()
  58. }
  59. deinit {
  60. if scanLineAnimation != nil {
  61. scanLineAnimation!.stopStepAnimating()
  62. }
  63. if scanNetAnimation != nil {
  64. scanNetAnimation!.stopStepAnimating()
  65. }
  66. }
  67. // 开始扫描动画
  68. func startScanAnimation() {
  69. guard !isAnimationing else {
  70. return
  71. }
  72. isAnimationing = true
  73. let cropRect = getScanRectForAnimation()
  74. switch viewStyle.anmiationStyle {
  75. case .LineMove:
  76. scanLineAnimation?.startAnimatingWithRect(animationRect: cropRect,
  77. parentView: self,
  78. image: viewStyle.animationImage)
  79. case .NetGrid:
  80. scanNetAnimation?.startAnimatingWithRect(animationRect: cropRect,
  81. parentView: self,
  82. image: viewStyle.animationImage)
  83. case .LineStill:
  84. let stillRect = CGRect(x: cropRect.origin.x + 20,
  85. y: cropRect.origin.y + cropRect.size.height / 2,
  86. width: cropRect.size.width - 40,
  87. height: 2)
  88. scanLineStill?.frame = stillRect
  89. addSubview(scanLineStill!)
  90. scanLineStill?.isHidden = false
  91. default: break
  92. }
  93. }
  94. // 开始扫描动画
  95. func stopScanAnimation() {
  96. isAnimationing = false
  97. switch viewStyle.anmiationStyle {
  98. case .LineMove:
  99. scanLineAnimation?.stopStepAnimating()
  100. case .NetGrid:
  101. scanNetAnimation?.stopStepAnimating()
  102. case .LineStill:
  103. scanLineStill?.isHidden = true
  104. default: break
  105. }
  106. }
  107. // Only override drawRect: if you perform custom drawing.
  108. // An empty implementation adversely affects performance during animation.
  109. open override func draw(_ rect: CGRect) {
  110. drawScanRect()
  111. }
  112. //MARK: ----- 绘制扫码效果-----
  113. func drawScanRect() {
  114. let XRetangleLeft = viewStyle.xScanRetangleOffset
  115. var sizeRetangle = CGSize(width: frame.size.width - XRetangleLeft * 2.0, height: frame.size.width - XRetangleLeft * 2.0)
  116. if viewStyle.whRatio != 1.0 {
  117. let w = sizeRetangle.width
  118. var h = w / viewStyle.whRatio
  119. h = CGFloat(Int(h))
  120. sizeRetangle = CGSize(width: w, height: h)
  121. }
  122. // 扫码区域Y轴最小坐标
  123. let YMinRetangle = frame.size.height / 2.0 - sizeRetangle.height / 2.0 - viewStyle.centerUpOffset
  124. let YMaxRetangle = YMinRetangle + sizeRetangle.height
  125. let XRetangleRight = frame.size.width - XRetangleLeft
  126. guard let context = UIGraphicsGetCurrentContext() else {
  127. return
  128. }
  129. // 非扫码区域半透明
  130. // 设置非识别区域颜色
  131. context.setFillColor(viewStyle.color_NotRecoginitonArea.cgColor)
  132. // 填充矩形
  133. // 扫码区域上面填充
  134. var rect = CGRect(x: 0, y: 0, width: frame.size.width, height: YMinRetangle)
  135. context.fill(rect)
  136. // 扫码区域左边填充
  137. rect = CGRect(x: 0, y: YMinRetangle, width: XRetangleLeft, height: sizeRetangle.height)
  138. context.fill(rect)
  139. // 扫码区域右边填充
  140. rect = CGRect(x: XRetangleRight, y: YMinRetangle, width: XRetangleLeft, height: sizeRetangle.height)
  141. context.fill(rect)
  142. // 扫码区域下面填充
  143. rect = CGRect(x: 0, y: YMaxRetangle, width: frame.size.width, height: frame.size.height - YMaxRetangle)
  144. context.fill(rect)
  145. // 执行绘画
  146. context.strokePath()
  147. if viewStyle.isNeedShowRetangle {
  148. // 中间画矩形(正方形)
  149. context.setStrokeColor(viewStyle.colorRetangleLine.cgColor)
  150. context.setLineWidth(1)
  151. context.addRect(CGRect(x: XRetangleLeft, y: YMinRetangle, width: sizeRetangle.width, height: sizeRetangle.height))
  152. // CGContextMoveToPoint(context, XRetangleLeft, YMinRetangle);
  153. // CGContextAddLineToPoint(context, XRetangleLeft+sizeRetangle.width, YMinRetangle);
  154. context.strokePath()
  155. }
  156. scanRetangleRect = CGRect(x: XRetangleLeft, y: YMinRetangle, width: sizeRetangle.width, height: sizeRetangle.height)
  157. // 画矩形框4格外围相框角
  158. // 相框角的宽度和高度
  159. let wAngle = viewStyle.photoframeAngleW
  160. let hAngle = viewStyle.photoframeAngleH
  161. // 4个角的 线的宽度
  162. let linewidthAngle = viewStyle.photoframeLineW // 经验参数:6和4
  163. // 画扫码矩形以及周边半透明黑色坐标参数
  164. var diffAngle = linewidthAngle / 3
  165. diffAngle = linewidthAngle / 2 // 框外面4个角,与框有缝隙
  166. diffAngle = linewidthAngle / 2 // 框4个角 在线上加4个角效果
  167. diffAngle = 0 // 与矩形框重合
  168. switch viewStyle.photoframeAngleStyle {
  169. case .Outer: diffAngle = linewidthAngle / 3 // 框外面4个角,与框紧密联系在一起
  170. case .On: diffAngle = 0
  171. case .Inner: diffAngle = -viewStyle.photoframeLineW / 2
  172. }
  173. context.setStrokeColor(viewStyle.colorAngle.cgColor)
  174. context.setFillColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
  175. // Draw them with a 2.0 stroke width so they are a bit more visible.
  176. context.setLineWidth(linewidthAngle)
  177. //
  178. let leftX = XRetangleLeft - diffAngle
  179. let topY = YMinRetangle - diffAngle
  180. let rightX = XRetangleRight + diffAngle
  181. let bottomY = YMaxRetangle + diffAngle
  182. // 左上角水平线
  183. context.move(to: CGPoint(x: leftX - linewidthAngle / 2, y: topY))
  184. context.addLine(to: CGPoint(x: leftX + wAngle, y: topY))
  185. // 左上角垂直线
  186. context.move(to: CGPoint(x: leftX, y: topY - linewidthAngle / 2))
  187. context.addLine(to: CGPoint(x: leftX, y: topY + hAngle))
  188. // 左下角水平线
  189. context.move(to: CGPoint(x: leftX - linewidthAngle / 2, y: bottomY))
  190. context.addLine(to: CGPoint(x: leftX + wAngle, y: bottomY))
  191. // 左下角垂直线
  192. context.move(to: CGPoint(x: leftX, y: bottomY + linewidthAngle / 2))
  193. context.addLine(to: CGPoint(x: leftX, y: bottomY - hAngle))
  194. // 右上角水平线
  195. context.move(to: CGPoint(x: rightX + linewidthAngle / 2, y: topY))
  196. context.addLine(to: CGPoint(x: rightX - wAngle, y: topY))
  197. // 右上角垂直线
  198. context.move(to: CGPoint(x: rightX, y: topY - linewidthAngle / 2))
  199. context.addLine(to: CGPoint(x: rightX, y: topY + hAngle))
  200. // 右下角水平线
  201. context.move(to: CGPoint(x: rightX + linewidthAngle / 2, y: bottomY))
  202. context.addLine(to: CGPoint(x: rightX - wAngle, y: bottomY))
  203. // 右下角垂直线
  204. context.move(to: CGPoint(x: rightX, y: bottomY + linewidthAngle / 2))
  205. context.addLine(to: CGPoint(x: rightX, y: bottomY - hAngle))
  206. context.strokePath()
  207. }
  208. // 根据矩形区域,获取识别区域
  209. static func getScanRectWithPreView(preView: UIView, style: LBXScanViewStyle) -> CGRect {
  210. let XRetangleLeft = style.xScanRetangleOffset
  211. let width = preView.frame.size.width - XRetangleLeft * 2
  212. let height = width
  213. var sizeRetangle = CGSize(width: width, height: height)
  214. if style.whRatio != 1 {
  215. let w = sizeRetangle.width
  216. var h = w / style.whRatio
  217. let hInt: Int = Int(h)
  218. h = CGFloat(hInt)
  219. sizeRetangle = CGSize(width: w, height: h)
  220. }
  221. // 扫码区域Y轴最小坐标
  222. let YMinRetangle = preView.frame.size.height / 2.0 - sizeRetangle.height / 2.0 - style.centerUpOffset
  223. // 扫码区域坐标
  224. let cropRect = CGRect(x: XRetangleLeft, y: YMinRetangle, width: sizeRetangle.width, height: sizeRetangle.height)
  225. // 计算兴趣区域
  226. var rectOfInterest: CGRect
  227. // ref:http://www.cocoachina.com/ios/20141225/10763.html
  228. let size = preView.bounds.size
  229. let p1 = size.height / size.width
  230. let p2: CGFloat = 1920.0 / 1080.0 // 使用了1080p的图像输出
  231. if p1 < p2 {
  232. let fixHeight = size.width * 1920.0 / 1080.0
  233. let fixPadding = (fixHeight - size.height) / 2
  234. rectOfInterest = CGRect(x: (cropRect.origin.y + fixPadding) / fixHeight,
  235. y: cropRect.origin.x / size.width,
  236. width: cropRect.size.height / fixHeight,
  237. height: cropRect.size.width / size.width)
  238. } else {
  239. let fixWidth = size.height * 1080.0 / 1920.0
  240. let fixPadding = (fixWidth - size.width) / 2
  241. rectOfInterest = CGRect(x: cropRect.origin.y / size.height,
  242. y: (cropRect.origin.x + fixPadding) / fixWidth,
  243. width: cropRect.size.height / size.height,
  244. height: cropRect.size.width / fixWidth)
  245. }
  246. return rectOfInterest
  247. }
  248. func getRetangeSize() -> CGSize {
  249. let XRetangleLeft = viewStyle.xScanRetangleOffset
  250. var sizeRetangle = CGSize(width: frame.size.width - XRetangleLeft * 2, height: frame.size.width - XRetangleLeft * 2)
  251. let w = sizeRetangle.width
  252. var h = w / viewStyle.whRatio
  253. h = CGFloat(Int(h))
  254. sizeRetangle = CGSize(width: w, height: h)
  255. return sizeRetangle
  256. }
  257. func deviceStartReadying(readyStr: String) {
  258. let XRetangleLeft = viewStyle.xScanRetangleOffset
  259. let sizeRetangle = getRetangeSize()
  260. // 扫码区域Y轴最小坐标
  261. let YMinRetangle = frame.size.height / 2.0 - sizeRetangle.height / 2.0 - viewStyle.centerUpOffset
  262. // 设备启动状态提示
  263. if activityView == nil {
  264. activityView = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
  265. activityView?.center = CGPoint(x: XRetangleLeft + sizeRetangle.width / 2 - 50, y: YMinRetangle + sizeRetangle.height / 2)
  266. activityView?.style = UIActivityIndicatorView.Style.whiteLarge
  267. addSubview(activityView!)
  268. let labelReadyRect = CGRect(x: activityView!.frame.origin.x + activityView!.frame.size.width + 10,
  269. y: activityView!.frame.origin.y,
  270. width: 100,
  271. height: 30)
  272. labelReadying = UILabel(frame: labelReadyRect)
  273. labelReadying?.text = readyStr
  274. labelReadying?.backgroundColor = UIColor.clear
  275. labelReadying?.textColor = UIColor.white
  276. labelReadying?.font = UIFont.systemFont(ofSize: 18.0)
  277. addSubview(labelReadying!)
  278. }
  279. addSubview(labelReadying!)
  280. activityView?.startAnimating()
  281. }
  282. func deviceStopReadying() {
  283. if activityView != nil {
  284. activityView?.stopAnimating()
  285. activityView?.removeFromSuperview()
  286. labelReadying?.removeFromSuperview()
  287. activityView = nil
  288. labelReadying = nil
  289. }
  290. }
  291. }
  292. //MARK: - 公开方法
  293. public extension LBXScanView {
  294. /// 获取扫描动画的Rect
  295. func getScanRectForAnimation() -> CGRect {
  296. let XRetangleLeft = viewStyle.xScanRetangleOffset
  297. var sizeRetangle = CGSize(width: frame.size.width - XRetangleLeft * 2,
  298. height: frame.size.width - XRetangleLeft * 2)
  299. if viewStyle.whRatio != 1 {
  300. let w = sizeRetangle.width
  301. let h = w / viewStyle.whRatio
  302. sizeRetangle = CGSize(width: w, height: CGFloat(Int(h)))
  303. }
  304. // 扫码区域Y轴最小坐标
  305. let YMinRetangle = frame.size.height / 2.0 - sizeRetangle.height / 2.0 - viewStyle.centerUpOffset
  306. // 扫码区域坐标
  307. let cropRect = CGRect(x: XRetangleLeft, y: YMinRetangle, width: sizeRetangle.width, height: sizeRetangle.height)
  308. return cropRect
  309. }
  310. }