diff --git a/Example/QRCodeReader.swift.xcodeproj/project.pbxproj b/Example/QRCodeReader.swift.xcodeproj/project.pbxproj index 42b8338..d1c8e92 100644 --- a/Example/QRCodeReader.swift.xcodeproj/project.pbxproj +++ b/Example/QRCodeReader.swift.xcodeproj/project.pbxproj @@ -90,11 +90,11 @@ CE412E9E19D9A1E4000F294E /* QRCodeReader.swiftTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = QRCodeReader.swiftTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; CE412EA319D9A1E4000F294E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CE412EA419D9A1E4000F294E /* QRCodeReader_swiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeReader_swiftTests.swift; sourceTree = ""; }; - CE4E1ED31D81838F00D2AC35 /* QRCodeReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRCodeReader.swift; sourceTree = ""; }; - CE4E1ED41D81838F00D2AC35 /* QRCodeReaderResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRCodeReaderResult.swift; sourceTree = ""; }; + CE4E1ED31D81838F00D2AC35 /* QRCodeReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = QRCodeReader.swift; sourceTree = ""; }; + CE4E1ED41D81838F00D2AC35 /* QRCodeReaderResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = QRCodeReaderResult.swift; sourceTree = ""; }; CE4E1ED51D81838F00D2AC35 /* QRCodeReaderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRCodeReaderViewController.swift; sourceTree = ""; }; CE4E1ED61D81838F00D2AC35 /* QRCodeReaderViewControllerBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRCodeReaderViewControllerBuilder.swift; sourceTree = ""; }; - CE4E1ED71D81838F00D2AC35 /* ReaderOverlayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReaderOverlayView.swift; sourceTree = ""; }; + CE4E1ED71D81838F00D2AC35 /* ReaderOverlayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = ReaderOverlayView.swift; sourceTree = ""; }; CE4E1ED81D81838F00D2AC35 /* SwitchCameraButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwitchCameraButton.swift; sourceTree = ""; }; CE4E1ED91D81838F00D2AC35 /* ToggleTorchButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToggleTorchButton.swift; sourceTree = ""; }; CE8FFED61BAB4E7F00D43F38 /* QRCodeReader.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = QRCodeReader.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -142,6 +142,7 @@ CE8FFED71BAB4E7F00D43F38 /* QRCodeReader */, CE412E8A19D9A1E4000F294E /* Products */, ); + indentWidth = 2; sourceTree = ""; }; CE412E8A19D9A1E4000F294E /* Products */ = { diff --git a/Example/QRCodeReader.swift/ViewController.swift b/Example/QRCodeReader.swift/ViewController.swift index 0e7989d..3c7c77b 100644 --- a/Example/QRCodeReader.swift/ViewController.swift +++ b/Example/QRCodeReader.swift/ViewController.swift @@ -27,48 +27,61 @@ import UIKit import AVFoundation -class ViewController: UIViewController, QRCodeReaderViewControllerDelegate { +class ViewController: UIViewController { lazy var reader = QRCodeReaderViewController(builder: QRCodeReaderViewControllerBuilder { $0.reader = QRCodeReader(metadataObjectTypes: [AVMetadataObjectTypeQRCode]) $0.showTorchButton = true }) @IBAction func scanAction(_ sender: AnyObject) { - if QRCodeReader.supportsMetadataObjectTypes() { - reader.modalPresentationStyle = .formSheet - reader.delegate = self - - reader.completionBlock = { (result: QRCodeReaderResult?) in - if let result = result { - print("Completion with result: \(result.value) of type \(result.metadataType)") - } - } - - present(reader, animated: true, completion: nil) - } - else { + + reader.codeReader.buzzWhenCodeIsFound = true + + guard QRCodeReader.supportsMetadataObjectTypes() else { let alert = UIAlertController(title: "Error", message: "Reader not supported by the current device", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) - present(alert, animated: true, completion: nil) + return } + + reader.modalPresentationStyle = .formSheet + + reader.showHighlight = false // remove any existing highlight (as QRCodeReaderViewController is reused) + reader.showHighlight = true + + // results can be returned in a delegate or a completion block + reader.delegate = self + + reader.completionBlock = { (result: QRCodeReaderResult?) in + if let result = result { + print("Completion with result: \(result.value) of type \(result.metadataType)") + } + } + + present(reader, animated: true, completion: nil) } - // MARK: - QRCodeReader Delegate Methods +} + +extension ViewController: QRCodeReaderViewControllerDelegate { func reader(_ reader: QRCodeReaderViewController, didScanResult result: QRCodeReaderResult) { reader.stopScanning() - - dismiss(animated: true) { [weak self] in - let alert = UIAlertController( - title: "QRCodeReader", - message: String (format:"%@ (of type %@)", result.value, result.metadataType), - preferredStyle: .alert - ) - alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) - - self?.present(alert, animated: true, completion: nil) - } + + // delay dismiss so we can see highlighted QR code + + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1, execute: { [weak self] in + self?.dismiss(animated: true) { [weak self] in + let alert = UIAlertController( + title: "QRCodeReader", + message: String (format:"%@ (of type %@)", result.value, result.metadataType), + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) + + self?.present(alert, animated: true, completion: nil) + } + }) } func reader(_ reader: QRCodeReaderViewController, didSwitchCamera newCaptureDevice: AVCaptureDeviceInput) { @@ -79,7 +92,8 @@ class ViewController: UIViewController, QRCodeReaderViewControllerDelegate { func readerDidCancel(_ reader: QRCodeReaderViewController) { reader.stopScanning() - + dismiss(animated: true, completion: nil) } + } diff --git a/Sources/QRCodeReader.swift b/Sources/QRCodeReader.swift index 0ddcfb7..a5f4be0 100644 --- a/Sources/QRCodeReader.swift +++ b/Sources/QRCodeReader.swift @@ -72,13 +72,19 @@ public final class QRCodeReader: NSObject, AVCaptureMetadataOutputObjectsDelegat // MARK: - Managing the Code Discovery - /// Flag to know whether the scanner should stop scanning when a code is found. - public var stopScanningWhenCodeIsFound: Bool = true + /// Flag to know whether the scanner should stop scanning when a code is found. (default: true) + public var stopScanningWhenCodeIsFound = true - /// Block is executed when a metadata object is found. + /// Flag to buzz when a code is found (default: false) + public var buzzWhenCodeIsFound = false + + /// Block to execute when a metadata object is found. public var didFindCode: ((QRCodeReaderResult) -> Void)? + + /// Block to execute when corners are found + public var didFindCorners: (([CGPoint]) -> Void)? - // MARK: - Creating the Code Reade + // MARK: - Creating the Code Reader /** Initializes the code reader with the QRCode metadata type object. @@ -340,7 +346,26 @@ public final class QRCodeReader: NSObject, AVCaptureMetadataOutputObjectsDelegat if stopScanningWhenCodeIsFound { stopScanning() } + + if buzzWhenCodeIsFound { + AudioServicesPlayAlertSound(kSystemSoundID_Vibrate) + } + // convert corners coordinates to our view + if let meta = previewLayer.transformedMetadataObject(for: _readableCodeObject) as? AVMetadataMachineReadableCodeObject { + var points:[CGPoint] = [] + if let corners = meta.corners { + points = corners.map { + let dict = $0 as! CFDictionary + return CGPoint(dictionaryRepresentation: dict)! + } + DispatchQueue.main.async(execute: { [weak self] in + self?.didFindCorners?(points) + }) + } + + } + let scannedResult = QRCodeReaderResult(value: _readableCodeObject.stringValue, metadataType:_readableCodeObject.type) DispatchQueue.main.async(execute: { [weak self] in diff --git a/Sources/QRCodeReaderViewController.swift b/Sources/QRCodeReaderViewController.swift index 87556d7..2dec304 100644 --- a/Sources/QRCodeReaderViewController.swift +++ b/Sources/QRCodeReaderViewController.swift @@ -31,7 +31,33 @@ import AVFoundation public class QRCodeReaderViewController: UIViewController { /// The code reader object used to scan the bar code. public let codeReader: QRCodeReader + + /// highlight a found QR code (default: true) + public var showHighlight:Bool { + get { return codeReader.didFindCorners != nil } + set(highlight) { + + if highlight { + + codeReader.didFindCorners = { [weak self] corners in + if let overlayView = self?.readerView.displayable.overlayView as? ReaderOverlayView { + overlayView.showHighlight(corners) + } + } + + } else { + codeReader.didFindCorners = nil + // remove any existing highlight + if let overlayView = readerView.displayable.overlayView as? ReaderOverlayView { + overlayView.showHighlight([]) + } + + } + } + + } + let readerView: QRCodeReaderContainer let startScanningAtLoad: Bool let showCancelButton: Bool @@ -73,16 +99,19 @@ public class QRCodeReaderViewController: UIViewController { view.backgroundColor = .black + showHighlight = true + codeReader.didFindCode = { [weak self] resultAsObject in if let weakSelf = self { - weakSelf.completionBlock?(resultAsObject) - weakSelf.delegate?.reader(weakSelf, didScanResult: resultAsObject) + weakSelf.completionBlock?(resultAsObject) + weakSelf.delegate?.reader(weakSelf, didScanResult: resultAsObject) } } setupUIComponentsWithCancelButtonTitle(builder.cancelButtonTitle) NotificationCenter.default.addObserver(self, selector: #selector(orientationDidChange), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil) + } required public init?(coder aDecoder: NSCoder) { @@ -93,8 +122,9 @@ public class QRCodeReaderViewController: UIViewController { showTorchButton = false showSwitchCameraButton = false showOverlayView = false - + super.init(coder: aDecoder) + } // MARK: - Responding to View Events diff --git a/Sources/QRCodeReaderViewControllerBuilder.swift b/Sources/QRCodeReaderViewControllerBuilder.swift index e7943a4..cc30ddf 100644 --- a/Sources/QRCodeReaderViewControllerBuilder.swift +++ b/Sources/QRCodeReaderViewControllerBuilder.swift @@ -78,8 +78,7 @@ public final class QRCodeReaderViewControllerBuilder { Flag to display the guide view. */ public var showOverlayView = true - - + // MARK: - Initializing a Flap View /** diff --git a/Sources/ReaderOverlayView.swift b/Sources/ReaderOverlayView.swift index 90db2f2..583c2eb 100644 --- a/Sources/ReaderOverlayView.swift +++ b/Sources/ReaderOverlayView.swift @@ -28,6 +28,10 @@ import UIKit /// Overlay over the camera view to display the area (a square) where to scan the code. public final class ReaderOverlayView: UIView { + + /// color to highlight the QR code + public var highlightColor = UIColor.orange.withAlphaComponent(0.5) + private var overlay: CAShapeLayer = { var overlay = CAShapeLayer() overlay.backgroundColor = UIColor.clear.cgColor @@ -39,7 +43,13 @@ public final class ReaderOverlayView: UIView { return overlay }() - + + lazy var highlight: CAShapeLayer = { + var layer = CAShapeLayer() + layer.fillColor = self.highlightColor.cgColor + return layer + }() + override init(frame: CGRect) { super.init(frame: frame) @@ -54,6 +64,17 @@ public final class ReaderOverlayView: UIView { private func setupOverlay() { layer.addSublayer(overlay) + layer.addSublayer(highlight) + } + + public func showHighlight(_ corners: [CGPoint]) { + + let path = UIBezierPath() + for (index,point) in corners.enumerated() { + index == 0 ? path.move(to: point) : path.addLine(to: point) + } + + self.highlight.path = path.cgPath } public override func draw(_ rect: CGRect) {