This commit is contained in:
Loupehope 2021-04-29 11:12:03 +03:00 committed by GitHub
commit 520ddbd7c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 526 additions and 167 deletions

2
.gitignore vendored
View File

@ -21,6 +21,7 @@ xcuserdata/
*.moved-aside
*.xccheckout
*.xcscmblueprint
.DS_Store
## Obj-C/Swift specific
*.hmap
@ -39,6 +40,7 @@ playground.xcworkspace
# Package.pins
# Package.resolved
.build/
.swiftpm/
# CocoaPods
#

View File

@ -9,11 +9,16 @@
/* Begin PBXBuildFile section */
043BA95FA64A9E13CF6DCAD8B77014D6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB4607EFCA7C5F75397649E792E2AFCB /* Foundation.framework */; };
0CC2C063233CDCBF009A2245 /* QRCodeReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CC2C05E233CDCBF009A2245 /* QRCodeReader.swift */; };
0CC2C064233CDCBF009A2245 /* QRCodeOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CC2C060233CDCBF009A2245 /* QRCodeOverlayView.swift */; };
0CC2C065233CDCBF009A2245 /* QRCodeReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CC2C061233CDCBF009A2245 /* QRCodeReaderView.swift */; };
0CC2C066233CDCBF009A2245 /* QRCodeFocusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CC2C062233CDCBF009A2245 /* QRCodeFocusView.swift */; };
0CC2C064233CDCBF009A2245 /* OverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CC2C060233CDCBF009A2245 /* OverlayView.swift */; };
0CC2C065233CDCBF009A2245 /* BaseReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CC2C061233CDCBF009A2245 /* BaseReaderView.swift */; };
0CC2C066233CDCBF009A2245 /* FocusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CC2C062233CDCBF009A2245 /* FocusView.swift */; };
130551717DB770EA0651FE8D93C23675 /* QRCodeReader-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 95E4F97DB9CA20CC853726128BB706A6 /* QRCodeReader-dummy.m */; };
3B21E171721DD07BBAB5989E14FD624B /* QRCodeReader-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F505016E395D9F953FB3602BC202FD8 /* QRCodeReader-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
4C172F3E2632F52700E66397 /* CardReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C172F3D2632F52700E66397 /* CardReader.swift */; };
4C172F432632FFF200E66397 /* BaseReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C172F422632FFF200E66397 /* BaseReader.swift */; };
4C172F4E2633032400E66397 /* QRCodeReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C172F4D2633032400E66397 /* QRCodeReaderView.swift */; };
4C172F6E263303B000E66397 /* CardReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C172F6D263303B000E66397 /* CardReaderView.swift */; };
4C172F77263306DD00E66397 /* CardFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C172F76263306DD00E66397 /* CardFactory.swift */; };
4EEDD6031A9ACB58B426DEAF9EF13FB0 /* Pods-QRCodeReader_Tests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = EC559F1A4D837B9FD18823EDF5C5EB98 /* Pods-QRCodeReader_Tests-dummy.m */; };
66390594843D889C2E6C56C16CBC0963 /* Pods-QRCodeReader_Example-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 1CB2ED5F7E3C6EBC896BFDE238BA51A1 /* Pods-QRCodeReader_Example-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
929A90508F8A751ABD3901537D9B9FC3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB4607EFCA7C5F75397649E792E2AFCB /* Foundation.framework */; };
@ -42,9 +47,9 @@
/* Begin PBXFileReference section */
0C7471F90A704F08CC0283FC0E4B57DC /* Pods-QRCodeReader_Example-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-QRCodeReader_Example-frameworks.sh"; sourceTree = "<group>"; };
0CC2C05E233CDCBF009A2245 /* QRCodeReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRCodeReader.swift; sourceTree = "<group>"; };
0CC2C060233CDCBF009A2245 /* QRCodeOverlayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRCodeOverlayView.swift; sourceTree = "<group>"; };
0CC2C061233CDCBF009A2245 /* QRCodeReaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRCodeReaderView.swift; sourceTree = "<group>"; };
0CC2C062233CDCBF009A2245 /* QRCodeFocusView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRCodeFocusView.swift; sourceTree = "<group>"; };
0CC2C060233CDCBF009A2245 /* OverlayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverlayView.swift; sourceTree = "<group>"; };
0CC2C061233CDCBF009A2245 /* BaseReaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseReaderView.swift; sourceTree = "<group>"; };
0CC2C062233CDCBF009A2245 /* FocusView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FocusView.swift; sourceTree = "<group>"; };
1C9D135A54BC4D3FFE5BEA77B04C802D /* Pods-QRCodeReader_Example-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-QRCodeReader_Example-dummy.m"; sourceTree = "<group>"; };
1CB2ED5F7E3C6EBC896BFDE238BA51A1 /* Pods-QRCodeReader_Example-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-QRCodeReader_Example-umbrella.h"; sourceTree = "<group>"; };
288313D69136D2B7605F9BB05E7A6C03 /* Pods_QRCodeReader_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_QRCodeReader_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -52,6 +57,11 @@
436A72EE95B92D301E967BAEB928B9DD /* Pods-QRCodeReader_Example-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-QRCodeReader_Example-acknowledgements.markdown"; sourceTree = "<group>"; };
445930C222AB7DFC3993CFF4A9BBD021 /* QRCodeReader-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "QRCodeReader-prefix.pch"; sourceTree = "<group>"; };
46EC7F5010001EBDF8D51322836760E4 /* Pods-QRCodeReader_Example.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-QRCodeReader_Example.modulemap"; sourceTree = "<group>"; };
4C172F3D2632F52700E66397 /* CardReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardReader.swift; sourceTree = "<group>"; };
4C172F422632FFF200E66397 /* BaseReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseReader.swift; sourceTree = "<group>"; };
4C172F4D2633032400E66397 /* QRCodeReaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeReaderView.swift; sourceTree = "<group>"; };
4C172F6D263303B000E66397 /* CardReaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardReaderView.swift; sourceTree = "<group>"; };
4C172F76263306DD00E66397 /* CardFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardFactory.swift; sourceTree = "<group>"; };
59629FB82E167000859A076FA3816EBE /* Pods_QRCodeReader_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_QRCodeReader_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5C86CA70237BC75ACFBBCB9368BBC2C0 /* Pods-QRCodeReader_Tests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-QRCodeReader_Tests-acknowledgements.plist"; sourceTree = "<group>"; };
5F505016E395D9F953FB3602BC202FD8 /* QRCodeReader-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "QRCodeReader-umbrella.h"; sourceTree = "<group>"; };
@ -108,7 +118,9 @@
0CC2C05D233CDCBF009A2245 /* Core */ = {
isa = PBXGroup;
children = (
0CC2C05E233CDCBF009A2245 /* QRCodeReader.swift */,
4C172F5B2633036F00E66397 /* Base */,
4C172F632633038300E66397 /* QRCode */,
4C172F5F2633037B00E66397 /* Card */,
);
name = Core;
path = QRCodeReader/Classes/Core;
@ -117,9 +129,10 @@
0CC2C05F233CDCBF009A2245 /* Views */ = {
isa = PBXGroup;
children = (
0CC2C060233CDCBF009A2245 /* QRCodeOverlayView.swift */,
0CC2C061233CDCBF009A2245 /* QRCodeReaderView.swift */,
0CC2C062233CDCBF009A2245 /* QRCodeFocusView.swift */,
4C172F522633035400E66397 /* Base */,
4C172F532633035D00E66397 /* Subviews */,
4C172F572633036700E66397 /* QRCode */,
4C172F72263303B300E66397 /* CardReaderView */,
);
name = Views;
path = QRCodeReader/Classes/Views;
@ -170,6 +183,72 @@
path = ../..;
sourceTree = "<group>";
};
4C172F522633035400E66397 /* Base */ = {
isa = PBXGroup;
children = (
0CC2C061233CDCBF009A2245 /* BaseReaderView.swift */,
);
path = Base;
sourceTree = "<group>";
};
4C172F532633035D00E66397 /* Subviews */ = {
isa = PBXGroup;
children = (
0CC2C062233CDCBF009A2245 /* FocusView.swift */,
0CC2C060233CDCBF009A2245 /* OverlayView.swift */,
);
path = Subviews;
sourceTree = "<group>";
};
4C172F572633036700E66397 /* QRCode */ = {
isa = PBXGroup;
children = (
4C172F4D2633032400E66397 /* QRCodeReaderView.swift */,
);
path = QRCode;
sourceTree = "<group>";
};
4C172F5B2633036F00E66397 /* Base */ = {
isa = PBXGroup;
children = (
4C172F422632FFF200E66397 /* BaseReader.swift */,
);
path = Base;
sourceTree = "<group>";
};
4C172F5F2633037B00E66397 /* Card */ = {
isa = PBXGroup;
children = (
4C172F7B263306EC00E66397 /* Helpers */,
4C172F3D2632F52700E66397 /* CardReader.swift */,
);
path = Card;
sourceTree = "<group>";
};
4C172F632633038300E66397 /* QRCode */ = {
isa = PBXGroup;
children = (
0CC2C05E233CDCBF009A2245 /* QRCodeReader.swift */,
);
path = QRCode;
sourceTree = "<group>";
};
4C172F72263303B300E66397 /* CardReaderView */ = {
isa = PBXGroup;
children = (
4C172F6D263303B000E66397 /* CardReaderView.swift */,
);
path = CardReaderView;
sourceTree = "<group>";
};
4C172F7B263306EC00E66397 /* Helpers */ = {
isa = PBXGroup;
children = (
4C172F76263306DD00E66397 /* CardFactory.swift */,
);
path = Helpers;
sourceTree = "<group>";
};
50BF34CF4C318572786E1701303A92B1 /* Pods-QRCodeReader_Example */ = {
isa = PBXGroup;
children = (
@ -400,11 +479,16 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4C172F3E2632F52700E66397 /* CardReader.swift in Sources */,
0CC2C063233CDCBF009A2245 /* QRCodeReader.swift in Sources */,
0CC2C066233CDCBF009A2245 /* QRCodeFocusView.swift in Sources */,
0CC2C065233CDCBF009A2245 /* QRCodeReaderView.swift in Sources */,
0CC2C064233CDCBF009A2245 /* QRCodeOverlayView.swift in Sources */,
0CC2C066233CDCBF009A2245 /* FocusView.swift in Sources */,
0CC2C065233CDCBF009A2245 /* BaseReaderView.swift in Sources */,
4C172F432632FFF200E66397 /* BaseReader.swift in Sources */,
4C172F4E2633032400E66397 /* QRCodeReaderView.swift in Sources */,
0CC2C064233CDCBF009A2245 /* OverlayView.swift in Sources */,
130551717DB770EA0651FE8D93C23675 /* QRCodeReader-dummy.m in Sources */,
4C172F77263306DD00E66397 /* CardFactory.swift in Sources */,
4C172F6E263303B000E66397 /* CardReaderView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -23,93 +23,21 @@
import UIKit
import AVFoundation
open class QRCodeReader: NSObject, AVCaptureMetadataOutputObjectsDelegate {
private let sessionQueue = DispatchQueue(label: "qr_capture_session_queue")
private let metadataObjectsQueue = DispatchQueue(label: "qr_metadata_objects_queue", attributes: [], target: nil)
internal weak var readerView: QRCodeReaderView?
lazy var defaultDeviceInput: AVCaptureDeviceInput? = {
guard let defaultDevice = defaultDevice else {
return nil
}
return try? AVCaptureDeviceInput(device: defaultDevice)
}()
open class BaseReader<Result>: NSObject {
// MARK: - Public Properties
let session = AVCaptureSession()
let metadataOutput = AVCaptureMetadataOutput()
let previewLayer: AVCaptureVideoPreviewLayer
public let defaultDevice: AVCaptureDevice? = .default(for: .video)
public var stopScanningWhenCodeIsFound: Bool = true
public var didFindCode: ((AVMetadataMachineReadableCodeObject) -> Void)?
public var stopScanningWhenCodeIsFound: Bool = false
public var didFind: ((Result) -> Void)?
public var didFailDecoding: (() -> Void)?
// MARK: - Public Initializer
public override init() {
previewLayer = AVCaptureVideoPreviewLayer(session: session)
super.init()
sessionQueue.async {
self.configureDefaultComponents()
}
}
// MARK: - Deinitializer
deinit {
isTorchEnabled = false
}
// MARK: - Checking the Reader Availabilities
public class func isAvailable() -> Bool {
guard let captureDevice = AVCaptureDevice.default(for: .video) else {
return false
}
return (try? AVCaptureDeviceInput(device: captureDevice)) != nil
}
// MARK: - Controlling Reader
public func startScanning() {
readerView?.updateRectOfInterestBasedOnFocusView()
sessionQueue.async {
guard !self.session.isRunning else {
return
}
self.session.startRunning()
}
}
public func stopScanning() {
sessionQueue.async {
guard self.session.isRunning else {
return
}
self.session.stopRunning()
}
}
public var isRunning: Bool {
return session.isRunning
}
public var isTorchAvailable: Bool {
return defaultDevice?.isTorchAvailable ?? false
}
@ -124,75 +52,91 @@ open class QRCodeReader: NSObject, AVCaptureMetadataOutputObjectsDelegate {
defer {
defaultDevice?.unlockForConfiguration()
}
let newTorchMode: AVCaptureDevice.TorchMode = newValue ? .on : .off
let isTorchModeSupported = defaultDevice?.isTorchModeSupported(newTorchMode) ?? false
guard isTorchAvailable, isTorchModeSupported else {
return
}
defaultDevice?.torchMode = newTorchMode
} catch _ { }
}
}
// MARK: - Private Methods
// MARK: - Checking the Reader Availabilities
private func configureDefaultComponents() {
for output in session.outputs {
session.removeOutput(output)
}
for input in session.inputs {
session.removeInput(input)
public class func isAvailable() -> Bool {
guard let captureDevice = AVCaptureDevice.default(for: .video) else {
return false
}
if let defaultDeviceInput = defaultDeviceInput {
session.addInput(defaultDeviceInput)
}
session.addOutput(metadataOutput)
metadataOutput.setMetadataObjectsDelegate(self, queue: metadataObjectsQueue)
metadataOutput.metadataObjectTypes = [.qr, .aztec, .dataMatrix]
previewLayer.videoGravity = .resizeAspectFill
session.commitConfiguration()
return (try? AVCaptureDeviceInput(device: captureDevice)) != nil
}
// MARK: - Internal Properties
let session = AVCaptureSession()
let previewLayer: AVCaptureVideoPreviewLayer
let sessionQueue = DispatchQueue(label: "session_queue")
lazy var defaultDeviceInput: AVCaptureDeviceInput? = {
guard let defaultDevice = defaultDevice else {
return nil
}
return try? AVCaptureDeviceInput(device: defaultDevice)
}()
var onUpdateRectOfInterest: (() -> Void)?
// MARK: - Public Initializer
// MARK: - AVCaptureMetadataOutputObjectsDelegate
public init(onUpdateRectOfInterest: (() -> Void)?) {
self.onUpdateRectOfInterest = onUpdateRectOfInterest
public func metadataOutput(_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection) {
sessionQueue.async { [weak self] in
guard let self = self else {
previewLayer = AVCaptureVideoPreviewLayer(session: session)
super.init()
sessionQueue.async {
self.configureDefaultComponents()
}
}
// MARK: - Deinitializer
deinit {
isTorchEnabled = false
}
// MARK: - Controlling Reader
public func startScanning() {
onUpdateRectOfInterest?()
sessionQueue.async {
guard !self.session.isRunning else {
return
}
for current in metadataObjects {
if let readableCodeObject = current as? AVMetadataMachineReadableCodeObject,
readableCodeObject.stringValue != nil {
guard self.session.isRunning else {
return
}
if self.stopScanningWhenCodeIsFound {
self.session.stopRunning()
}
DispatchQueue.main.async {
self.didFindCode?(readableCodeObject)
}
} else {
DispatchQueue.main.async {
self.didFailDecoding?()
}
}
}
self.session.startRunning()
}
}
public func stopScanning() {
sessionQueue.async {
guard self.session.isRunning else {
return
}
self.session.stopRunning()
}
}
// MARK: - Private Methods
func configureDefaultComponents() {
// override
}
}

View File

@ -0,0 +1,128 @@
//
// Copyright (c) 2019 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
import AVFoundation
import Vision
@available(iOS 13, *)
open class CardReader: BaseReader<Card> {
private let scannerObjectsQueue = DispatchQueue(label: "scanner_objects_queue", attributes: [], target: nil)
private let factory: CardFactory
private var request: VNRecognizeTextRequest {
let request = VNRecognizeTextRequest(completionHandler: recognizeTextHandler)
request.recognitionLevel = .accurate
request.usesLanguageCorrection = false
return request
}
private let videoDataOutput = AVCaptureVideoDataOutput()
public init(factory: CardFactory, onUpdateRectOfInterest: (() -> Void)?) {
self.factory = factory
super.init(onUpdateRectOfInterest: onUpdateRectOfInterest)
}
// MARK: - Private Methods
override func configureDefaultComponents() {
session.beginConfiguration()
for output in session.outputs {
session.removeOutput(output)
}
for input in session.inputs {
session.removeInput(input)
}
if let defaultDeviceInput = defaultDeviceInput {
session.addInput(defaultDeviceInput)
}
videoDataOutput.alwaysDiscardsLateVideoFrames = true
videoDataOutput.setSampleBufferDelegate(self, queue: scannerObjectsQueue)
videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
session.addOutput(videoDataOutput)
previewLayer.videoGravity = .resizeAspectFill
session.commitConfiguration()
}
func recognizeTextHandler(request: VNRequest, error: Error?) {
guard let results = request.results as? [VNRecognizedTextObservation] else {
DispatchQueue.main.async {
self.didFailDecoding?()
}
return
}
let maximumCandidates = 1
let lines = results.flatMap { $0.topCandidates(maximumCandidates).map { $0.string } }
guard let card = factory.create(lines) else {
return
}
guard self.session.isRunning else {
return
}
if self.stopScanningWhenCodeIsFound {
self.session.stopRunning()
}
DispatchQueue.main.async {
self.didFind?(card)
}
}
}
@available(iOS 13, *)
extension CardReader: AVCaptureVideoDataOutputSampleBufferDelegate {
public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
return
}
let ciImage = CIImage(cvImageBuffer: pixelBuffer)
let requestHandler = VNImageRequestHandler(ciImage: ciImage, orientation: .right, options: [:])
sessionQueue.async { [weak self] in
guard let request = self?.request else {
return
}
try? requestHandler.perform([request])
}
}
}

View File

@ -0,0 +1,36 @@
//
// Copyright (c) 2019 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
public struct Card {
public let number: String
public init(number: String) {
self.number = number
}
}
public protocol CardFactory {
func create(_ values: [String]) -> Card?
}

View File

@ -0,0 +1,89 @@
//
// Copyright (c) 2019 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
import AVFoundation
open class QRCodeReader: BaseReader<AVMetadataMachineReadableCodeObject>, AVCaptureMetadataOutputObjectsDelegate {
private let metadataObjectsQueue = DispatchQueue(label: "qr_metadata_objects_queue", attributes: [], target: nil)
let metadataOutput = AVCaptureMetadataOutput()
// MARK: - Private Methods
override func configureDefaultComponents() {
for output in session.outputs {
session.removeOutput(output)
}
for input in session.inputs {
session.removeInput(input)
}
if let defaultDeviceInput = defaultDeviceInput {
session.addInput(defaultDeviceInput)
}
session.addOutput(metadataOutput)
metadataOutput.setMetadataObjectsDelegate(self, queue: metadataObjectsQueue)
metadataOutput.metadataObjectTypes = [.qr, .aztec, .dataMatrix]
previewLayer.videoGravity = .resizeAspectFill
session.commitConfiguration()
}
// MARK: - AVCaptureMetadataOutputObjectsDelegate
public func metadataOutput(_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection) {
sessionQueue.async { [weak self] in
guard let self = self else {
return
}
for current in metadataObjects {
if let readableCodeObject = current as? AVMetadataMachineReadableCodeObject,
readableCodeObject.stringValue != nil {
guard self.session.isRunning else {
return
}
if self.stopScanningWhenCodeIsFound {
self.session.stopRunning()
}
DispatchQueue.main.async {
self.didFind?(readableCodeObject)
}
} else {
DispatchQueue.main.async {
self.didFailDecoding?()
}
}
}
}
}
}

View File

@ -0,0 +1,47 @@
//
// Copyright (c) 2019 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
open class BaseReaderView: UIView {
public let overlay = OverlayView()
let cameraView = UIView()
// MARK: - Intializers
public init() {
super.init(frame: .zero)
overlay.backgroundColor = UIColor.black.withAlphaComponent(0.5)
addSubview(cameraView)
addSubview(overlay)
}
@available(*, unavailable)
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -0,0 +1,48 @@
//
// Copyright (c) 2019 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
@available(iOS 13, *)
open class CardReaderView: BaseReaderView {
private weak var reader: CardReader?
// MARK: - Public Methods
public func setReader(_ reader: CardReader) {
self.reader = reader
cameraView.layer.sublayers?.forEach {
$0.removeFromSuperlayer()
}
cameraView.layer.insertSublayer(reader.previewLayer, at: 0)
}
override open func layoutSubviews() {
super.layoutSubviews()
reader?.previewLayer.frame = bounds
overlay.frame = bounds
}
}

View File

@ -22,34 +22,15 @@
import UIKit
open class QRCodeReaderView: UIView {
private let cameraView = UIView()
open class QRCodeReaderView: BaseReaderView {
private weak var reader: QRCodeReader?
public let overlay = QRCodeOverlayView()
// MARK: - Intializers
public init() {
super.init(frame: .zero)
overlay.backgroundColor = UIColor.black.withAlphaComponent(0.5)
addSubview(cameraView)
addSubview(overlay)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Public Methods
public func setReader(_ reader: QRCodeReader) {
reader.readerView = self
self.reader = reader
self.reader?.onUpdateRectOfInterest = updateRectOfInterestBasedOnFocusView
cameraView.layer.sublayers?.forEach {
$0.removeFromSuperlayer()

View File

@ -22,7 +22,7 @@
import UIKit
open class QRCodeFocusView: UIView {
open class FocusView: UIView {
public var cornerColor: UIColor {
didSet {
@ -74,7 +74,7 @@ open class QRCodeFocusView: UIView {
var currentSuperview = superview
while let view = currentSuperview {
if view is QRCodeOverlayView {
if view is OverlayView {
view.setNeedsDisplay()
return
} else {

View File

@ -22,9 +22,9 @@
import UIKit
open class QRCodeOverlayView: UIView {
open class OverlayView: UIView {
public weak var focusView: QRCodeFocusView?
public weak var focusView: FocusView?
override open func draw(_ rect: CGRect) {