Commit c5460107 by Sarkizz

添加扫码控件

parent 53ec85df
...@@ -8,6 +8,12 @@ ...@@ -8,6 +8,12 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
78371DBE0C2899CF3088818C /* Pods_MRFrameworkTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 581034915993B022BF7951F9 /* Pods_MRFrameworkTests.framework */; }; 78371DBE0C2899CF3088818C /* Pods_MRFrameworkTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 581034915993B022BF7951F9 /* Pods_MRFrameworkTests.framework */; };
A7021016231CC60C009F8BC6 /* MRScanningProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A702100F231CC60C009F8BC6 /* MRScanningProtocol.swift */; };
A7021017231CC60C009F8BC6 /* MRScanningLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7021011231CC60C009F8BC6 /* MRScanningLineView.swift */; };
A7021018231CC60C009F8BC6 /* MRScanningShadowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7021012231CC60C009F8BC6 /* MRScanningShadowView.swift */; };
A7021019231CC60C009F8BC6 /* MRScanningRectLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7021013231CC60C009F8BC6 /* MRScanningRectLineView.swift */; };
A702101A231CC60C009F8BC6 /* CommonScanningViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7021014231CC60C009F8BC6 /* CommonScanningViewController.swift */; };
A702101B231CC60C009F8BC6 /* ScanningAVManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7021015231CC60C009F8BC6 /* ScanningAVManager.swift */; };
A73E5C8F23136605000C379B /* UIColor+util.swift in Sources */ = {isa = PBXBuildFile; fileRef = A73E5C8E23136605000C379B /* UIColor+util.swift */; }; A73E5C8F23136605000C379B /* UIColor+util.swift in Sources */ = {isa = PBXBuildFile; fileRef = A73E5C8E23136605000C379B /* UIColor+util.swift */; };
A73E5C922313767C000C379B /* UIButton+util.swift in Sources */ = {isa = PBXBuildFile; fileRef = A73E5C912313767C000C379B /* UIButton+util.swift */; }; A73E5C922313767C000C379B /* UIButton+util.swift in Sources */ = {isa = PBXBuildFile; fileRef = A73E5C912313767C000C379B /* UIButton+util.swift */; };
A73E5C95231376D6000C379B /* NamespaceWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A73E5C94231376D6000C379B /* NamespaceWrappable.swift */; }; A73E5C95231376D6000C379B /* NamespaceWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A73E5C94231376D6000C379B /* NamespaceWrappable.swift */; };
...@@ -56,6 +62,12 @@ ...@@ -56,6 +62,12 @@
5B88C6BB841E900D410F5545 /* Pods-MRFrameworkTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MRFrameworkTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MRFrameworkTests/Pods-MRFrameworkTests.debug.xcconfig"; sourceTree = "<group>"; }; 5B88C6BB841E900D410F5545 /* Pods-MRFrameworkTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MRFrameworkTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MRFrameworkTests/Pods-MRFrameworkTests.debug.xcconfig"; sourceTree = "<group>"; };
85282B54E24C52074F4E6023 /* Pods-MRFramework.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MRFramework.release.xcconfig"; path = "Pods/Target Support Files/Pods-MRFramework/Pods-MRFramework.release.xcconfig"; sourceTree = "<group>"; }; 85282B54E24C52074F4E6023 /* Pods-MRFramework.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MRFramework.release.xcconfig"; path = "Pods/Target Support Files/Pods-MRFramework/Pods-MRFramework.release.xcconfig"; sourceTree = "<group>"; };
95B35045DA3B1E69C27D2A08 /* Pods_MRFramework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MRFramework.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 95B35045DA3B1E69C27D2A08 /* Pods_MRFramework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MRFramework.framework; sourceTree = BUILT_PRODUCTS_DIR; };
A702100F231CC60C009F8BC6 /* MRScanningProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MRScanningProtocol.swift; sourceTree = "<group>"; };
A7021011231CC60C009F8BC6 /* MRScanningLineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MRScanningLineView.swift; sourceTree = "<group>"; };
A7021012231CC60C009F8BC6 /* MRScanningShadowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MRScanningShadowView.swift; sourceTree = "<group>"; };
A7021013231CC60C009F8BC6 /* MRScanningRectLineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MRScanningRectLineView.swift; sourceTree = "<group>"; };
A7021014231CC60C009F8BC6 /* CommonScanningViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommonScanningViewController.swift; sourceTree = "<group>"; };
A7021015231CC60C009F8BC6 /* ScanningAVManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScanningAVManager.swift; sourceTree = "<group>"; };
A73E5C8E23136605000C379B /* UIColor+util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+util.swift"; sourceTree = "<group>"; }; A73E5C8E23136605000C379B /* UIColor+util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+util.swift"; sourceTree = "<group>"; };
A73E5C912313767C000C379B /* UIButton+util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+util.swift"; sourceTree = "<group>"; }; A73E5C912313767C000C379B /* UIButton+util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+util.swift"; sourceTree = "<group>"; };
A73E5C94231376D6000C379B /* NamespaceWrappable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamespaceWrappable.swift; sourceTree = "<group>"; }; A73E5C94231376D6000C379B /* NamespaceWrappable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamespaceWrappable.swift; sourceTree = "<group>"; };
...@@ -130,6 +142,27 @@ ...@@ -130,6 +142,27 @@
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
A702100E231CC60C009F8BC6 /* Scanning */ = {
isa = PBXGroup;
children = (
A7021010231CC60C009F8BC6 /* Interface */,
A702100F231CC60C009F8BC6 /* MRScanningProtocol.swift */,
A7021014231CC60C009F8BC6 /* CommonScanningViewController.swift */,
A7021015231CC60C009F8BC6 /* ScanningAVManager.swift */,
);
path = Scanning;
sourceTree = "<group>";
};
A7021010231CC60C009F8BC6 /* Interface */ = {
isa = PBXGroup;
children = (
A7021011231CC60C009F8BC6 /* MRScanningLineView.swift */,
A7021012231CC60C009F8BC6 /* MRScanningShadowView.swift */,
A7021013231CC60C009F8BC6 /* MRScanningRectLineView.swift */,
);
path = Interface;
sourceTree = "<group>";
};
A73E5C93231376C9000C379B /* Namespace */ = { A73E5C93231376C9000C379B /* Namespace */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
...@@ -236,6 +269,7 @@ ...@@ -236,6 +269,7 @@
A7B8E155230E2FC700999DF2 /* UIControls */ = { A7B8E155230E2FC700999DF2 /* UIControls */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
A702100E231CC60C009F8BC6 /* Scanning */,
A7B8E178230FE81D00999DF2 /* WebView */, A7B8E178230FE81D00999DF2 /* WebView */,
A7B8E15F230E319700999DF2 /* PhotoPicker */, A7B8E15F230E319700999DF2 /* PhotoPicker */,
A7B8E157230E302200999DF2 /* Toast */, A7B8E157230E302200999DF2 /* Toast */,
...@@ -469,7 +503,10 @@ ...@@ -469,7 +503,10 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A7021018231CC60C009F8BC6 /* MRScanningShadowView.swift in Sources */,
A7B8E17F230FEB9500999DF2 /* MRWebBridge.swift in Sources */, A7B8E17F230FEB9500999DF2 /* MRWebBridge.swift in Sources */,
A702101A231CC60C009F8BC6 /* CommonScanningViewController.swift in Sources */,
A7021019231CC60C009F8BC6 /* MRScanningRectLineView.swift in Sources */,
A73E5C9923137A79000C379B /* UICollectionView+reusable.swift in Sources */, A73E5C9923137A79000C379B /* UICollectionView+reusable.swift in Sources */,
A7B8E17C230FEB0700999DF2 /* NSMutableURLRequest+util.swift in Sources */, A7B8E17C230FEB0700999DF2 /* NSMutableURLRequest+util.swift in Sources */,
A7B8E175230FDA5200999DF2 /* UIView+util.swift in Sources */, A7B8E175230FDA5200999DF2 /* UIView+util.swift in Sources */,
...@@ -482,7 +519,10 @@ ...@@ -482,7 +519,10 @@
A7B8E13A230B9EE200999DF2 /* Date+util.swift in Sources */, A7B8E13A230B9EE200999DF2 /* Date+util.swift in Sources */,
A73E5C97231377CD000C379B /* UITableView+reusable.swift in Sources */, A73E5C97231377CD000C379B /* UITableView+reusable.swift in Sources */,
A7B8E15D230E318400999DF2 /* BLEManager.swift in Sources */, A7B8E15D230E318400999DF2 /* BLEManager.swift in Sources */,
A7021016231CC60C009F8BC6 /* MRScanningProtocol.swift in Sources */,
A7B8E161230E319700999DF2 /* SAPhotoPicker.swift in Sources */, A7B8E161230E319700999DF2 /* SAPhotoPicker.swift in Sources */,
A7021017231CC60C009F8BC6 /* MRScanningLineView.swift in Sources */,
A702101B231CC60C009F8BC6 /* ScanningAVManager.swift in Sources */,
A73E5C922313767C000C379B /* UIButton+util.swift in Sources */, A73E5C922313767C000C379B /* UIButton+util.swift in Sources */,
A7B8E142230B9F9C00999DF2 /* MRFiles.swift in Sources */, A7B8E142230B9F9C00999DF2 /* MRFiles.swift in Sources */,
A7B8E15E230E318400999DF2 /* BLEPeripheralExtention.swift in Sources */, A7B8E15E230E318400999DF2 /* BLEPeripheralExtention.swift in Sources */,
......
//
// CommonScanningViewController.swift
// Test
//
// Created by Sarkizz on 2019/8/30.
// Copyright © 2019 fcbox. All rights reserved.
//
import UIKit
import AVFoundation
open class CommonScanningViewController: UIViewController, MRScanningProtocol {
public var overlayView: UIView?
public var overlayViewLayout: ((_ view: UIView) -> Void)?
public var isSupportTakePicture = false
public var metadataType: ScanningAVManager.MetadataType = ScanningAVManager.qrcodeType
public var didFinishScanning: ((_ controller: CommonScanningViewController, _ code: String?) -> Void)?
public var cancel: ((_ controller: CommonScanningViewController) -> Void)?
private var isFirstLoad = true
open override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
let _ = manager
setupScanningViews()
}
open override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if isFirstLoad {
isFirstLoad = false
setupCapture()
} else {
scanAndAnimate(start: true)
}
}
}
extension CommonScanningViewController {
open func setupScanningViews() {
setupScanningInterface { (_, rectLineView, lineView) in
rectLineView.borderWidth = 0.5
rectLineView.cornerLineWidth = 2
rectLineView.cornerLineLong = 20
rectLineView.lineColor = .white
lineView.color = .white
}
if let overlayView = overlayView {
view.addSubview(overlayView)
if let layout = overlayViewLayout {
layout(overlayView)
} else {
overlayView.snp.makeConstraints { (make) in
make.left.right.bottom.equalToSuperview()
}
}
}
}
open func layoutAllViews(_ bounds: CGRect) {
layoutScanningInterface(.full(topPadding: 150), in: bounds)
}
open func setScanningViews(hide: Bool) {
setAllViews(hide: hide)
overlayView?.isHidden = hide
}
open func back() {
cancel?(self)
}
open func playScanedSound() {
//Do nothing
}
open func handleCaptureError(_ error: Error, isAuthorizationFailed: Bool) {
let alertVC = UIAlertController(title: nil, message: error.localizedDescription, preferredStyle: .alert)
alertVC.addAction(.init(title: "确定", style: .default, handler: { _ in
self.back()
}))
self.present(alertVC, animated: true)
}
open func setScanningFinishHandler() {
manager.didOutputMetadataObjects = { manager, objs in
self.playScanedSound()
self.scanAndAnimate(start: false)
let obj = objs[0] as? AVMetadataMachineReadableCodeObject
self.didFinishScanning?(self, obj?.stringValue)
}
}
open func setupCapture() {
let frame = self.view.bounds
layoutAllViews(frame)
setScanningViews(hide: true)
ScanningAVManager.authorization { (error) in
if let error = error {
self.handleCaptureError(error, isAuthorizationFailed: true)
} else {
self.manager.setup(self.metadataType,
isSupportPicture: self.isSupportTakePicture,
scanRect: frame,
fullRect: frame,
completion: { (manager, error) in
if let error = error {
self.handleCaptureError(error, isAuthorizationFailed: false)
} else {
self.view.layer.insertSublayer(manager.previewLayer, at: 0)
self.scanAndAnimate(start: true)
self.setScanningViews(hide: false)
self.setScanningFinishHandler()
}
})
}
}
}
}
extension CommonScanningViewController {
public func scanAndAnimate(start: Bool) {
if start {
manager.startRunning()
startScanningAnimation()
} else {
manager.stopRunning()
stopScanningAnimation()
}
}
}
//
// MRScanningLineView.swift
// Test
//
// Created by Sarkizz on 2019/8/30.
// Copyright © 2019 fcbox. All rights reserved.
//
import UIKit
public final class MRScanningLineView: UIView {
public override var frame: CGRect {
didSet {
setupGradientLayer()
}
}
public var color: UIColor = .orange {
didSet {
setupGradientLayer()
}
}
// 2端透明比例
private let opacityRatio: Float = 0.25
private let gradientLayer = CAGradientLayer()
public override init(frame: CGRect) {
super.init(frame: frame)
setupGradientLayer()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension MRScanningLineView {
private func setupGradientLayer() {
gradientLayer.frame = layer.bounds
gradientLayer.config(colors: [.init(color: .clear, location: 0),
.init(color: color, location: 1-2*opacityRatio),
.init(color: .clear, location: 1)],
direction: .horizontal(1))
layer.addSublayer(gradientLayer)
}
}
//
// MRScanningRectLineView.swift
// Test
//
// Created by Sarkizz on 2019/8/30.
// Copyright © 2019 fcbox. All rights reserved.
//
import UIKit
public final class MRScanningRectLineView: UIView {
override public var frame: CGRect {
didSet {
borderBackground.frame = bounds
setCornersFrame()
}
}
/// 绘制四角线条的颜色
public var lineColor: UIColor = .orange {
didSet {
setCornersColor()
}
}
/// 绘制四角线条的粗细
public var cornerLineWidth: CGFloat = 2 {
didSet {
setCornersLineWidth()
}
}
/// 绘制四角线条的长度
public var cornerLineLong: CGFloat = 25 {
didSet {
setCornersSize()
}
}
public var borderColor: UIColor = .white {
didSet {
borderBackground.layer.borderColor = borderColor.cgColor
}
}
public var borderWidth: CGFloat = 0 {
didSet {
borderBackground.layer.borderWidth = borderWidth
}
}
private let borderBackground = UIView()
private let leftTop = ScanningCornerView(type: .leftTop)
private let rightTop = ScanningCornerView(type: .rightTop)
private let leftBottom = ScanningCornerView(type: .leftBottom)
private let rightBottom = ScanningCornerView(type: .rightBottom)
override public init(frame: CGRect) {
super.init(frame: frame)
addSubview(borderBackground)
addSubview(leftTop)
addSubview(rightTop)
addSubview(leftBottom)
addSubview(rightBottom)
defaultSetting()
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension MRScanningRectLineView {
private func defaultSetting() {
backgroundColor = .clear
isUserInteractionEnabled = false
borderBackground.frame = bounds
borderBackground.backgroundColor = .clear
borderBackground.isUserInteractionEnabled = false
borderBackground.layer.borderColor = borderColor.cgColor
borderBackground.layer.borderWidth = borderWidth
setCornersColor()
setCornersLineWidth()
setCornersFrame()
}
private func setCornersFrame() {
let backupSize = CGSize(width: frame.width/2, height: frame.height/2)
let size = CGSize(width: min(cornerLineLong, backupSize.width), height: min(cornerLineLong, backupSize.height))
leftTop.frame = CGRect(origin: CGPoint.zero, size: size)
rightTop.frame = CGRect(origin: CGPoint(x: frame.width - size.width, y: 0), size: size)
leftBottom.frame = CGRect(origin: CGPoint(x: 0, y: frame.height - size.height), size: size)
rightBottom.frame = CGRect(origin: CGPoint(x: frame.width - size.width, y: frame.height - size.height), size: size)
}
private func setCornersColor() {
leftTop.color = lineColor
rightTop.color = lineColor
leftBottom.color = lineColor
rightBottom.color = lineColor
}
private func setCornersLineWidth() {
leftTop.lineWidth = cornerLineWidth
rightTop.lineWidth = cornerLineWidth
leftBottom.lineWidth = cornerLineWidth
rightBottom.lineWidth = cornerLineWidth
}
private func setCornersSize() {
let backupSize = CGSize(width: frame.width/2, height: frame.height/2)
let size = CGSize(width: min(cornerLineLong, backupSize.width), height: min(cornerLineLong, backupSize.height))
leftTop.frame.size = size
rightTop.frame.size = size
leftBottom.frame.size = size
rightBottom.frame.size = size
}
}
private final class ScanningCornerView: UIView {
enum ScanCornerViewType {
case leftTop
case rightTop
case leftBottom
case rightBottom
}
override var frame: CGRect {
didSet {
setNeedsDisplay()
}
}
var type: ScanCornerViewType = .leftTop {
didSet {
setNeedsDisplay()
}
}
var color: UIColor = .orange {
didSet {
setNeedsDisplay()
}
}
var lineWidth: CGFloat = 2 {
didSet {
setNeedsDisplay()
}
}
convenience init(type: ScanCornerViewType) {
self.init(frame: CGRect.zero)
self.type = type
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .clear
isUserInteractionEnabled = false
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
super.draw(rect)
if let ctx = UIGraphicsGetCurrentContext() {
ctx.setStrokeColor(color.cgColor)
ctx.setLineWidth(lineWidth)
let fix: CGFloat = lineWidth / 2
let lineLength = rect.size.width
// rect 4角 坐标
let leftTop = CGPoint(x: rect.origin.x + fix, y: rect.origin.y + fix)
let rightTop = CGPoint(x: rect.maxX - fix, y: rect.origin.y + fix)
let leftBottom = CGPoint(x: rect.origin.x + fix, y: rect.maxY - fix)
let rightBottom = CGPoint(x: rect.maxX - fix, y: rect.maxY - fix)
switch type {
case .leftTop:
let p0 = CGPoint(x: leftTop.x + lineLength, y: leftTop.y)
let p1 = leftTop
let p2 = CGPoint(x: leftTop.x, y: leftTop.y + lineLength)
ctx.move(to: p0)
ctx.addLine(to: p1)
ctx.addLine(to: p2)
break
case .rightTop:
let p0 = CGPoint(x: rightTop.x - lineLength, y: rightTop.y)
let p1 = rightTop
let p2 = CGPoint(x: rightTop.x, y: rightTop.y + lineLength)
ctx.move(to: p0)
ctx.addLine(to: p1)
ctx.addLine(to: p2)
break
case .leftBottom:
let p0 = CGPoint(x: leftBottom.x, y: leftBottom.y - lineLength)
let p1 = leftBottom
let p2 = CGPoint(x: leftBottom.x + lineLength, y: leftBottom.y)
ctx.move(to: p0)
ctx.addLine(to: p1)
ctx.addLine(to: p2)
break
case .rightBottom:
let p0 = CGPoint(x: rightBottom.x - lineLength, y: rightBottom.y)
let p1 = rightBottom
let p2 = CGPoint(x: rightBottom.x, y: rightBottom.y - lineLength)
ctx.move(to: p0)
ctx.addLine(to: p1)
ctx.addLine(to: p2)
break
}
ctx.strokePath()
}
}
}
//
// MRScanningShadowView.swift
// Test
//
// Created by Sarkizz on 2019/8/30.
// Copyright © 2019 fcbox. All rights reserved.
//
import UIKit
public final class MRScanningShadowView: UIView {
override public var frame: CGRect {
didSet {
setNeedsDisplay()
}
}
/// 内侧矩形的frame
public var interestFrame: CGRect = .zero {
didSet {
setNeedsDisplay()
}
}
public var shadowColor: UIColor = UIColor(red: 0.15, green: 0.15, blue: 0.15, alpha: 0.6) {
didSet {
setNeedsDisplay()
}
}
override public init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .clear
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func draw(_ rect: CGRect) {
super.draw(rect)
if let ctx = UIGraphicsGetCurrentContext() {
ctx.setFillColor(shadowColor.cgColor)
ctx.fill(rect)
ctx.clear(interestFrame)
}
}
}
//
// MRScanningProtocol.swift
// Test
//
// Created by Sarkizz on 2019/8/30.
// Copyright © 2019 fcbox. All rights reserved.
//
import Foundation
import UIKit
public enum ScanningInterfaceType {
case full(topPadding: CGFloat?)
case custom(scanningRect: CGRect, innerRect: CGRect)
public func config(in bounds: CGRect) -> (scanningRect: CGRect, innerRect: CGRect) {
switch self {
case .full(let padding):
let defaultWidth = bounds.width * 4/5
let innerRect = CGRect(x: (bounds.width - defaultWidth)/2,
y: padding ?? (bounds.height - defaultWidth)/2,
width: defaultWidth, height: defaultWidth)
return (scanningRect: bounds, innerRect: innerRect)
case .custom(let scanningRect, let innerRect):
return (scanningRect: scanningRect, innerRect: innerRect)
}
}
}
private let shadowViewTag = 99032
private let innerRectViewTag = 99033
private let lineViewTag = 99034
private var managerStoreKey = "MRScanningProtocolManager"
public protocol MRScanningProtocol: NSObject {
typealias InterfaceSetting = (_ shadowView: MRScanningShadowView, _ innerView: MRScanningRectLineView,_ scanLineView: MRScanningLineView) -> Void
}
extension MRScanningProtocol where Self: UIViewController {
public var manager: ScanningAVManager {
if let m = objc_getAssociatedObject(self, &managerStoreKey) as? ScanningAVManager {
return m
} else {
let m = ScanningAVManager()
objc_setAssociatedObject(self, &managerStoreKey, m, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return m
}
}
public var shadowView: MRScanningShadowView? {
return view.viewWithTag(shadowViewTag) as? MRScanningShadowView
}
public var rectLineView: MRScanningRectLineView? {
return view.viewWithTag(innerRectViewTag) as? MRScanningRectLineView
}
public var scanLineView: MRScanningLineView? {
return view.viewWithTag(lineViewTag) as? MRScanningLineView
}
}
extension MRScanningProtocol where Self: UIViewController {
public func setupScanningInterface(_ setting: InterfaceSetting) {
let shadowView = MRScanningShadowView()
shadowView.tag = shadowViewTag
view.insertSubview(shadowView, at: 0)
let rectLineView = MRScanningRectLineView()
rectLineView.tag = innerRectViewTag
view.addSubview(rectLineView)
let scanLineView = MRScanningLineView()
scanLineView.tag = lineViewTag
view.addSubview(scanLineView)
setting(shadowView, rectLineView, scanLineView)
}
public func layoutScanningInterface(_ type: ScanningInterfaceType, in rect: CGRect) {
let config = type.config(in: rect)
shadowView?.frame = CGRect(origin: CGPoint.zero, size: rect.size)
shadowView?.interestFrame = config.innerRect
rectLineView?.frame = config.innerRect
let rectLineWidth = rectLineView?.cornerLineWidth ?? 0
var frame = scanLineView?.frame ?? CGRect.zero
if frame == CGRect.zero {
frame.origin = config.innerRect.origin + rectLineWidth
frame.size.width = config.innerRect.width - 2*rectLineWidth
frame.size.height = 2
}
scanLineView?.frame = frame
}
public func startScanningAnimation() {
if let lineView = scanLineView,
let rectHeight = rectLineView?.frame.height, let cornerHeight = rectLineView?.cornerLineWidth {
let distance = rectHeight - cornerHeight - lineView.frame.height
let animation = CABasicAnimation()
animation.keyPath = "transform.translation.y"
animation.duration = 3
animation.isRemovedOnCompletion = false
animation.timingFunction = CAMediaTimingFunction.init(name: CAMediaTimingFunctionName.linear)
animation.fillMode = CAMediaTimingFillMode.forwards
animation.fromValue = 0
animation.toValue = distance
animation.repeatCount = MAXFLOAT
lineView.layer.add(animation, forKey: "scanningAnimation")
}
}
public func stopScanningAnimation() {
scanLineView?.layer.removeAnimation(forKey: "scanningAnimation")
}
public func setAllViews(hide: Bool) {
shadowView?.isHidden = hide
rectLineView?.isHidden = hide
scanLineView?.isHidden = hide
}
}
//
// ScanningAVManager.swift
// Test
//
// Created by Sarkizz on 2019/8/29.
// Copyright © 2019 fcbox. All rights reserved.
//
import Foundation
import AVFoundation
import UIKit
public class ScanningAVManager: NSObject {
public var previewLayer: AVCaptureVideoPreviewLayer {
return _previewLayer
}
public var didOutputMetadataObjects: ((_ manager: ScanningAVManager, _ objs: [AVMetadataObject]) -> Void)?
private var session: AVCaptureSession = {
return AVCaptureSession()
}()
private var imageOutput: AVCaptureStillImageOutput = {
return AVCaptureStillImageOutput()
}()
private var _previewLayer: AVCaptureVideoPreviewLayer!
private var isSupportPicture = false
}
extension ScanningAVManager {
public class func authorization(_ complete:((_ error: Error?) -> Void)? = nil) {
DispatchQueue.main.async {
guard UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera) ||
UIImagePickerController.isCameraDeviceAvailable(UIImagePickerController.CameraDevice.rear) else {
complete?(error(code: .cameraInvild, msg: "没有摄像头或摄像头不可用"))
return
}
let userCloseTips = {
complete?(error(code: .denied, msg: "没有摄像头权限,请到设置打开摄像头"))
}
let status = AVCaptureDevice.authorizationStatus(for: AVMediaType.video)
switch status {
case .restricted, .denied:
userCloseTips()
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video, completionHandler: { granted in
DispatchQueue.main.async {
if granted {
complete?(nil)
} else {
userCloseTips()
}
}
})
case .authorized:
complete?(nil)
@unknown default:
complete?(error(code: .unknowed, msg: "未知错误"))
}
}
}
}
extension ScanningAVManager {
public func setup(_ metadataType: MetadataType = .all,
isSupportPicture: Bool = false,
scanRect: CGRect,
fullRect: CGRect,
completion: ((_ manager: ScanningAVManager, _ error: Error?) -> Void)?) {
if let device = AVCaptureDevice.default(for: .video),
let input = try? AVCaptureDeviceInput(device: device) {
if session.canAddInput(input) {
session.addInput(input)
}
} else {
completion?(self, ScanningAVManager.error(code: .cameraInvild, msg: "摄像头无法使用,请确认权限已开通"))
return
}
let output = AVCaptureMetadataOutput()
output.setMetadataObjectsDelegate(self, queue: .main)
if session.canAddOutput(output) {
session.addOutput(output)
}
let types = metadataType.types(output.availableMetadataObjectTypes)
guard types.count > 0 else {
completion?(self, ScanningAVManager.error(code: .unsupport, msg: "设备不支持扫码"))
return
}
output.metadataObjectTypes = types
output.rectOfInterest = outRectOfInterest(innerRect: scanRect, fullRect: fullRect)
self.isSupportPicture = isSupportPicture
if isSupportPicture {
setupPictureAVCapture()
}
_previewLayer = AVCaptureVideoPreviewLayer(session: session)
_previewLayer.frame = scanRect
_previewLayer.videoGravity = .resizeAspectFill
completion?(self, nil)
}
public func startRunning() {
session.startRunning()
}
public func stopRunning() {
session.stopRunning()
}
// level,光线强度0-1。
public func openFlash(level: Float? = nil) -> Bool {
if let device = AVCaptureDevice.default(for: .video), device.hasTorch {
do {
try device.lockForConfiguration()
if let level = level {
do {
try device.setTorchModeOn(level: level)
} catch {
return false
}
} else {
device.torchMode = .on
}
device.unlockForConfiguration()
return true
} catch {}
}
return false
}
public func closeFlash() -> Bool {
if let device = AVCaptureDevice.default(for: .video), device.hasTorch {
do {
try device.lockForConfiguration()
device.torchMode = .off
device.unlockForConfiguration()
return true
} catch {}
}
return false
}
public func getOutputImage(_ completion: @escaping (_ image: UIImage?, _ error: Error?) -> Void) {
if isSupportPicture, let connection = imageOutput.connection(with: .video) {
imageOutput.captureStillImageAsynchronously(from: connection) { (buffer, error) in
if let buffer = buffer,
let data = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer) {
completion(UIImage(data: data), nil)
} else {
let msg = error?.localizedDescription ?? "获取图片失败"
let error = ScanningAVManager.error(code: .photoBuffer, msg: msg)
completion(nil, error)
}
}
}
}
}
extension ScanningAVManager {
private func setupPictureAVCapture() {
if session.canAddOutput(imageOutput) {
session.addOutput(imageOutput)
}
}
private func outRectOfInterest(innerRect: CGRect, fullRect: CGRect) -> CGRect {
if innerRect == fullRect {
return CGRect(x: 0, y: 0, width: 1, height: 1)
} else {
return CGRect(x: innerRect.origin.y / fullRect.width,
y: 1 - innerRect.origin.x / fullRect.width - innerRect.width / fullRect.width,
width: innerRect.height / fullRect.height,
height: innerRect.width / fullRect.width)
}
}
}
extension ScanningAVManager {
public enum MetadataType {
case all
case filter((AVMetadataObject.ObjectType) -> Bool)
func types(_ metadataObjectTypes: [AVMetadataObject.ObjectType]) -> [AVMetadataObject.ObjectType] {
switch self {
case .all:
return metadataObjectTypes
case .filter(let f):
return metadataObjectTypes.filter({ f($0) })
}
}
}
public static let qrcodeType: MetadataType = .filter { (type) -> Bool in
return type == .qr
}
public static let barcodeType: MetadataType = .filter { (type) -> Bool in
let isSupport =
type == .code128 ||
type == .upce ||
type == .code93 ||
type == .code39 ||
type == .code39Mod43 ||
type == .ean13
return isSupport
}
}
extension ScanningAVManager {
public enum ErrorCode: Int {
case unknowed = -99
case cameraInvild = -1
case denied = -2 // 用户未授权或拒绝权限
case unsupport = -3
case photoBuffer = -4
}
private class func error(code: ErrorCode, msg: String) -> Error {
return NSError(domain: "com.ScanningAVManager.MRFramework", code: code.rawValue, userInfo: [NSLocalizedDescriptionKey: msg])
}
}
extension ScanningAVManager: AVCaptureMetadataOutputObjectsDelegate {
public func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
didOutputMetadataObjects?(self, metadataObjects)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment