Allowing the scan area configuration #157 #106

This commit is contained in:
Yannick Loriot 2018-12-28 14:17:33 +01:00
parent dff48390b3
commit a8b4576243
8 changed files with 104 additions and 85 deletions

View File

@ -1,5 +1,9 @@
# Change log
## Version 10.0.0
- [ADD] Allowing the scan area configuration (#157 #106)
## [Version 9.0.0](https://github.com/yannickl/QRCodeReader.swift/releases/tag/9.0.0)
Release on 2018-09-19

View File

@ -30,7 +30,11 @@ import UIKit
class ViewController: UIViewController, QRCodeReaderViewControllerDelegate {
@IBOutlet weak var previewView: QRCodeReaderView! {
didSet {
previewView.setupComponents(showCancelButton: false, showSwitchCameraButton: false, showTorchButton: false, showOverlayView: true, reader: reader)
previewView.setupComponents(with: QRCodeReaderViewControllerBuilder {
$0.reader = reader
$0.showTorchButton = false
$0.showSwitchCameraButton = false
})
}
}
lazy var reader: QRCodeReader = QRCodeReader()
@ -39,6 +43,7 @@ class ViewController: UIViewController, QRCodeReaderViewControllerDelegate {
$0.reader = QRCodeReader(metadataObjectTypes: [.qr], captureDevicePosition: .back)
$0.showTorchButton = true
$0.preferredStatusBarStyle = .lightContent
$0.rectOfInterest = CGRect(x: 0.15, y: 0.15, width: 0.7, height: 0.7)
$0.reader.stopScanningWhenCodeIsFound = false
}

View File

@ -37,8 +37,8 @@ public final class QRCodeReader: NSObject, AVCaptureMetadataOutputObjectsDelegat
private let sessionQueue = DispatchQueue(label: "session queue")
private let metadataObjectsQueue = DispatchQueue(label: "com.yannickloriot.qr", attributes: [], target: nil)
var defaultDevice: AVCaptureDevice? = AVCaptureDevice.default(for: .video)
var frontDevice: AVCaptureDevice? = {
let defaultDevice: AVCaptureDevice? = AVCaptureDevice.default(for: .video)
let frontDevice: AVCaptureDevice? = {
if #available(iOS 10, *) {
return AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .front)
}
@ -67,8 +67,8 @@ public final class QRCodeReader: NSObject, AVCaptureMetadataOutputObjectsDelegat
return nil
}()
public var metadataOutput = AVCaptureMetadataOutput()
var session = AVCaptureSession()
let session = AVCaptureSession()
let metadataOutput = AVCaptureMetadataOutput()
weak var lifeCycleDelegate: QRCodeReaderLifeCycleDelegate?

View File

@ -73,16 +73,16 @@ final public class QRCodeReaderView: UIView, QRCodeReaderDisplayable {
private weak var reader: QRCodeReader?
public func setupComponents(showCancelButton: Bool, showSwitchCameraButton: Bool, showTorchButton: Bool, showOverlayView: Bool, reader: QRCodeReader?) {
self.reader = reader
public func setupComponents(with builder: QRCodeReaderViewControllerBuilder) {
self.reader = builder.reader
reader?.lifeCycleDelegate = self
addComponents()
cancelButton?.isHidden = !showCancelButton
switchCameraButton?.isHidden = !showSwitchCameraButton
toggleTorchButton?.isHidden = !showTorchButton
overlayView?.isHidden = !showOverlayView
cancelButton?.isHidden = !builder.showCancelButton
switchCameraButton?.isHidden = !builder.showSwitchCameraButton
toggleTorchButton?.isHidden = !builder.showTorchButton
overlayView?.isHidden = !builder.showOverlayView
guard let cb = cancelButton, let scb = switchCameraButton, let ttb = toggleTorchButton, let ov = overlayView else { return }
@ -90,7 +90,7 @@ final public class QRCodeReaderView: UIView, QRCodeReaderDisplayable {
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[cv]|", options: [], metrics: nil, views: views))
if showCancelButton {
if builder.showCancelButton {
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[cv][cb(40)]|", options: [], metrics: nil, views: views))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[cb]-|", options: [], metrics: nil, views: views))
}
@ -98,12 +98,12 @@ final public class QRCodeReaderView: UIView, QRCodeReaderDisplayable {
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[cv]|", options: [], metrics: nil, views: views))
}
if showSwitchCameraButton {
if builder.showSwitchCameraButton {
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[scb(50)]", options: [], metrics: nil, views: views))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[scb(70)]|", options: [], metrics: nil, views: views))
}
if showTorchButton {
if builder.showTorchButton {
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[ttb(50)]", options: [], metrics: nil, views: views))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[ttb(70)]", options: [], metrics: nil, views: views))
}
@ -111,6 +111,10 @@ final public class QRCodeReaderView: UIView, QRCodeReaderDisplayable {
for attribute in Array<NSLayoutConstraint.Attribute>([.left, .top, .right, .bottom]) {
addConstraint(NSLayoutConstraint(item: ov, attribute: attribute, relatedBy: .equal, toItem: cameraView, attribute: attribute, multiplier: 1, constant: 0))
}
if let readerOverlayView = overlayView as? ReaderOverlayView {
readerOverlayView.rectOfInterest = builder.rectOfInterest
}
}
public override func layoutSubviews() {

View File

@ -47,15 +47,11 @@ public protocol QRCodeReaderDisplayable {
func setNeedsUpdateOrientation()
/**
Method called by the container to allows you to layout your view properly using the given flags.
Method called by the container to allows you to layout your view properly using the QR code reader builder.
- Parameter showCancelButton: Flag to know whether you should display the cancel button.
- Parameter showSwitchCameraButton: Flag to know whether you should display the switch camera button.
- Parameter showTorchButton: Flag to know whether you should display the toggle torch button.
- Parameter showOverlayView: Flag to know whether you should display the overlay.
- Parameter reader: A reference to the code reader.
- Parameter builder: A QR code reader builder.
*/
func setupComponents(showCancelButton: Bool, showSwitchCameraButton: Bool, showTorchButton: Bool, showOverlayView: Bool, reader: QRCodeReader?)
func setupComponents(with builder: QRCodeReaderViewControllerBuilder)
}
/// The `QRCodeReaderContainer` structure embed the view displayed by the controller. The embeded view must be conform to the `QRCodeReaderDisplayable` protocol.
@ -75,7 +71,7 @@ public struct QRCodeReaderContainer {
// MARK: - Convenience Methods
func setupComponents(showCancelButton: Bool, showSwitchCameraButton: Bool, showTorchButton: Bool, showOverlayView: Bool, reader: QRCodeReader? = nil) {
displayable.setupComponents(showCancelButton: showCancelButton, showSwitchCameraButton: showSwitchCameraButton, showTorchButton: showTorchButton, showOverlayView: showOverlayView, reader: reader)
func setupComponents(with builder: QRCodeReaderViewControllerBuilder) {
displayable.setupComponents(with: builder)
}
}

View File

@ -29,16 +29,12 @@ import AVFoundation
/// Convenient controller to display a view to scan/read 1D or 2D bar codes like the QRCodes. It is based on the `AVFoundation` framework from Apple. It aims to replace ZXing or ZBar for iOS 7 and over.
public class QRCodeReaderViewController: UIViewController {
/// The code reader object used to scan the bar code.
public let codeReader: QRCodeReader
private let builder: QRCodeReaderViewControllerBuilder
let readerView: QRCodeReaderContainer
let startScanningAtLoad: Bool
let showCancelButton: Bool
let showSwitchCameraButton: Bool
let showTorchButton: Bool
let showOverlayView: Bool
let customPreferredStatusBarStyle: UIStatusBarStyle?
/// The code reader object used to scan the bar code.
public var codeReader: QRCodeReader {
return builder.reader
}
// MARK: - Managing the Callback Responders
@ -62,14 +58,7 @@ public class QRCodeReaderViewController: UIViewController {
- parameter builder: A QRCodeViewController builder object.
*/
required public init(builder: QRCodeReaderViewControllerBuilder) {
readerView = builder.readerView
startScanningAtLoad = builder.startScanningAtLoad
codeReader = builder.reader
showCancelButton = builder.showCancelButton
showSwitchCameraButton = builder.showSwitchCameraButton
showTorchButton = builder.showTorchButton
showOverlayView = builder.showOverlayView
customPreferredStatusBarStyle = builder.preferredStatusBarStyle
self.builder = builder
super.init(nibName: nil, bundle: nil)
@ -77,7 +66,7 @@ public class QRCodeReaderViewController: UIViewController {
codeReader.didFindCode = { [weak self] resultAsObject in
if let weakSelf = self {
if let qrv = weakSelf.readerView.displayable as? QRCodeReaderView {
if let qrv = builder.readerView.displayable as? QRCodeReaderView {
qrv.addGreenBorder()
}
weakSelf.completionBlock?(resultAsObject)
@ -85,26 +74,17 @@ public class QRCodeReaderViewController: UIViewController {
}
}
codeReader.didFailDecoding = { [weak self] in
if let weakSelf = self {
if let qrv = weakSelf.readerView.displayable as? QRCodeReaderView {
codeReader.didFailDecoding = {
if let qrv = builder.readerView.displayable as? QRCodeReaderView {
qrv.addRedBorder()
}
}
}
setupUIComponentsWithCancelButtonTitle(builder.cancelButtonTitle)
}
required public init?(coder aDecoder: NSCoder) {
codeReader = QRCodeReader()
readerView = QRCodeReaderContainer(displayable: QRCodeReaderView())
startScanningAtLoad = false
showCancelButton = false
showTorchButton = false
showSwitchCameraButton = false
showOverlayView = false
customPreferredStatusBarStyle = nil
self.builder = QRCodeReaderViewControllerBuilder()
super.init(coder: aDecoder)
}
@ -114,8 +94,8 @@ public class QRCodeReaderViewController: UIViewController {
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if startScanningAtLoad {
readerView.displayable.setNeedsUpdateOrientation()
if builder.startScanningAtLoad {
builder.readerView.displayable.setNeedsUpdateOrientation()
startScanning()
}
@ -134,38 +114,35 @@ public class QRCodeReaderViewController: UIViewController {
}
public override var preferredStatusBarStyle: UIStatusBarStyle {
return customPreferredStatusBarStyle ?? super.preferredStatusBarStyle
return builder.preferredStatusBarStyle ?? super.preferredStatusBarStyle
}
// MARK: - Initializing the AV Components
private func setupUIComponentsWithCancelButtonTitle(_ cancelButtonTitle: String) {
view.addSubview(readerView.view)
view.addSubview(builder.readerView.view)
let sscb = showSwitchCameraButton && codeReader.hasFrontDevice
let stb = showTorchButton && codeReader.isTorchAvailable
readerView.view.translatesAutoresizingMaskIntoConstraints = false
readerView.setupComponents(showCancelButton: showCancelButton, showSwitchCameraButton: sscb, showTorchButton: stb, showOverlayView: showOverlayView, reader: codeReader)
builder.readerView.view.translatesAutoresizingMaskIntoConstraints = false
builder.readerView.setupComponents(with: builder)
// Setup action methods
readerView.displayable.switchCameraButton?.addTarget(self, action: #selector(switchCameraAction), for: .touchUpInside)
readerView.displayable.toggleTorchButton?.addTarget(self, action: #selector(toggleTorchAction), for: .touchUpInside)
readerView.displayable.cancelButton?.setTitle(cancelButtonTitle, for: .normal)
readerView.displayable.cancelButton?.addTarget(self, action: #selector(cancelAction), for: .touchUpInside)
builder.readerView.displayable.switchCameraButton?.addTarget(self, action: #selector(switchCameraAction), for: .touchUpInside)
builder.readerView.displayable.toggleTorchButton?.addTarget(self, action: #selector(toggleTorchAction), for: .touchUpInside)
builder.readerView.displayable.cancelButton?.setTitle(cancelButtonTitle, for: .normal)
builder.readerView.displayable.cancelButton?.addTarget(self, action: #selector(cancelAction), for: .touchUpInside)
// Setup constraints
for attribute in [.left, .top, .right] as [NSLayoutConstraint.Attribute] {
NSLayoutConstraint(item: readerView.view, attribute: attribute, relatedBy: .equal, toItem: view, attribute: attribute, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: builder.readerView.view, attribute: attribute, relatedBy: .equal, toItem: view, attribute: attribute, multiplier: 1, constant: 0).isActive = true
}
if #available(iOS 11.0, *) {
view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: readerView.view.bottomAnchor).isActive = true
view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: builder.readerView.view.bottomAnchor).isActive = true
}
else {
NSLayoutConstraint(item: readerView.view, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: builder.readerView.view, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
}
}

View File

@ -68,12 +68,28 @@ public final class QRCodeReaderViewControllerBuilder {
/**
Flag to display the switch camera button.
*/
public var showSwitchCameraButton = true
public var showSwitchCameraButton: Bool {
get {
return _showSwitchCameraButton && reader.hasFrontDevice
}
set {
_showSwitchCameraButton = newValue
}
}
private var _showSwitchCameraButton: Bool = true
/**
Flag to display the toggle torch button. If the value is true and there is no torch the button will not be displayed.
*/
public var showTorchButton = false
public var showTorchButton: Bool {
get {
return _showTorchButton && reader.isTorchAvailable
}
set {
_showTorchButton = newValue
}
}
private var _showTorchButton = true
/**
Flag to display the guide view.
@ -91,6 +107,22 @@ public final class QRCodeReaderViewControllerBuilder {
*/
public var preferredStatusBarStyle: UIStatusBarStyle? = nil
/**
Specifies a rectangle of interest for limiting the search area for visual metadata.
The value of this property is a CGRect that determines the receiver's rectangle of interest for each frame of video. The rectangle's origin is top left and is relative to the coordinate space of the device providing the metadata. Specifying a rectOfInterest may improve detection performance for certain types of metadata. The default value of this property is the value CGRectMake(0, 0, 1, 1). Metadata objects whose bounds do not intersect with the rectOfInterest will not be returned.
*/
public var rectOfInterest: CGRect = CGRect(x: 0, y: 0, width: 1, height: 1) {
didSet {
reader.metadataOutput.rectOfInterest = CGRect(
x: min(max(rectOfInterest.origin.x, 0), 1),
y: min(max(rectOfInterest.origin.y, 0), 1),
width: min(max(rectOfInterest.width, 0), 1),
height: min(max(rectOfInterest.height, 0), 1)
)
}
}
// MARK: - Initializing a Flap View
/**

View File

@ -58,24 +58,25 @@ public final class ReaderOverlayView: UIView {
var overlayColor: UIColor = UIColor.white {
didSet {
self.overlay.strokeColor = overlayColor.cgColor
overlay.strokeColor = overlayColor.cgColor
self.setNeedsDisplay()
setNeedsDisplay()
}
}
var rectOfInterest: CGRect = CGRect(x: 0, y: 0, width: 1, height: 1) {
didSet {
setNeedsDisplay()
}
}
public override func draw(_ rect: CGRect) {
var innerRect = rect.insetBy(dx: 50, dy: 50)
let minSize = min(innerRect.width, innerRect.height)
if innerRect.width != minSize {
innerRect.origin.x += (innerRect.width - minSize) / 2
innerRect.size.width = minSize
}
else if innerRect.height != minSize {
innerRect.origin.y += (innerRect.height - minSize) / 2
innerRect.size.height = minSize
}
let innerRect = CGRect(
x: rect.width * rectOfInterest.minX,
y: rect.height * rectOfInterest.minY,
width: rect.width * rectOfInterest.width,
height: rect.height * rectOfInterest.height
)
overlay.path = UIBezierPath(roundedRect: innerRect, cornerRadius: 5).cgPath
}