Skip to content

Commit a8b4576

Browse files
committed
Allowing the scan area configuration #157 #106
1 parent dff4839 commit a8b4576

File tree

8 files changed

+104
-85
lines changed

8 files changed

+104
-85
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Change log
22

3+
## Version 10.0.0
4+
5+
- [ADD] Allowing the scan area configuration (#157 #106)
6+
37
## [Version 9.0.0](https://github.com/yannickl/QRCodeReader.swift/releases/tag/9.0.0)
48
Release on 2018-09-19
59

Example/QRCodeReader.swift/ViewController.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ import UIKit
3030
class ViewController: UIViewController, QRCodeReaderViewControllerDelegate {
3131
@IBOutlet weak var previewView: QRCodeReaderView! {
3232
didSet {
33-
previewView.setupComponents(showCancelButton: false, showSwitchCameraButton: false, showTorchButton: false, showOverlayView: true, reader: reader)
33+
previewView.setupComponents(with: QRCodeReaderViewControllerBuilder {
34+
$0.reader = reader
35+
$0.showTorchButton = false
36+
$0.showSwitchCameraButton = false
37+
})
3438
}
3539
}
3640
lazy var reader: QRCodeReader = QRCodeReader()
@@ -39,6 +43,7 @@ class ViewController: UIViewController, QRCodeReaderViewControllerDelegate {
3943
$0.reader = QRCodeReader(metadataObjectTypes: [.qr], captureDevicePosition: .back)
4044
$0.showTorchButton = true
4145
$0.preferredStatusBarStyle = .lightContent
46+
$0.rectOfInterest = CGRect(x: 0.15, y: 0.15, width: 0.7, height: 0.7)
4247

4348
$0.reader.stopScanningWhenCodeIsFound = false
4449
}

Sources/QRCodeReader.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ public final class QRCodeReader: NSObject, AVCaptureMetadataOutputObjectsDelegat
3737
private let sessionQueue = DispatchQueue(label: "session queue")
3838
private let metadataObjectsQueue = DispatchQueue(label: "com.yannickloriot.qr", attributes: [], target: nil)
3939

40-
var defaultDevice: AVCaptureDevice? = AVCaptureDevice.default(for: .video)
41-
var frontDevice: AVCaptureDevice? = {
40+
let defaultDevice: AVCaptureDevice? = AVCaptureDevice.default(for: .video)
41+
let frontDevice: AVCaptureDevice? = {
4242
if #available(iOS 10, *) {
4343
return AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .front)
4444
}
@@ -67,8 +67,8 @@ public final class QRCodeReader: NSObject, AVCaptureMetadataOutputObjectsDelegat
6767
return nil
6868
}()
6969

70-
public var metadataOutput = AVCaptureMetadataOutput()
71-
var session = AVCaptureSession()
70+
let session = AVCaptureSession()
71+
let metadataOutput = AVCaptureMetadataOutput()
7272

7373
weak var lifeCycleDelegate: QRCodeReaderLifeCycleDelegate?
7474

Sources/QRCodeReaderView.swift

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,44 +73,48 @@ final public class QRCodeReaderView: UIView, QRCodeReaderDisplayable {
7373

7474
private weak var reader: QRCodeReader?
7575

76-
public func setupComponents(showCancelButton: Bool, showSwitchCameraButton: Bool, showTorchButton: Bool, showOverlayView: Bool, reader: QRCodeReader?) {
77-
self.reader = reader
76+
public func setupComponents(with builder: QRCodeReaderViewControllerBuilder) {
77+
self.reader = builder.reader
7878
reader?.lifeCycleDelegate = self
7979

8080
addComponents()
8181

82-
cancelButton?.isHidden = !showCancelButton
83-
switchCameraButton?.isHidden = !showSwitchCameraButton
84-
toggleTorchButton?.isHidden = !showTorchButton
85-
overlayView?.isHidden = !showOverlayView
82+
cancelButton?.isHidden = !builder.showCancelButton
83+
switchCameraButton?.isHidden = !builder.showSwitchCameraButton
84+
toggleTorchButton?.isHidden = !builder.showTorchButton
85+
overlayView?.isHidden = !builder.showOverlayView
8686

8787
guard let cb = cancelButton, let scb = switchCameraButton, let ttb = toggleTorchButton, let ov = overlayView else { return }
8888

8989
let views = ["cv": cameraView, "ov": ov, "cb": cb, "scb": scb, "ttb": ttb]
9090

9191
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[cv]|", options: [], metrics: nil, views: views))
9292

93-
if showCancelButton {
93+
if builder.showCancelButton {
9494
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[cv][cb(40)]|", options: [], metrics: nil, views: views))
9595
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[cb]-|", options: [], metrics: nil, views: views))
9696
}
9797
else {
9898
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[cv]|", options: [], metrics: nil, views: views))
9999
}
100100

101-
if showSwitchCameraButton {
101+
if builder.showSwitchCameraButton {
102102
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[scb(50)]", options: [], metrics: nil, views: views))
103103
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[scb(70)]|", options: [], metrics: nil, views: views))
104104
}
105105

106-
if showTorchButton {
106+
if builder.showTorchButton {
107107
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[ttb(50)]", options: [], metrics: nil, views: views))
108108
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[ttb(70)]", options: [], metrics: nil, views: views))
109109
}
110110

111111
for attribute in Array<NSLayoutConstraint.Attribute>([.left, .top, .right, .bottom]) {
112112
addConstraint(NSLayoutConstraint(item: ov, attribute: attribute, relatedBy: .equal, toItem: cameraView, attribute: attribute, multiplier: 1, constant: 0))
113113
}
114+
115+
if let readerOverlayView = overlayView as? ReaderOverlayView {
116+
readerOverlayView.rectOfInterest = builder.rectOfInterest
117+
}
114118
}
115119

116120
public override func layoutSubviews() {

Sources/QRCodeReaderViewContainer.swift

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,11 @@ public protocol QRCodeReaderDisplayable {
4747
func setNeedsUpdateOrientation()
4848

4949
/**
50-
Method called by the container to allows you to layout your view properly using the given flags.
50+
Method called by the container to allows you to layout your view properly using the QR code reader builder.
5151

52-
- Parameter showCancelButton: Flag to know whether you should display the cancel button.
53-
- Parameter showSwitchCameraButton: Flag to know whether you should display the switch camera button.
54-
- Parameter showTorchButton: Flag to know whether you should display the toggle torch button.
55-
- Parameter showOverlayView: Flag to know whether you should display the overlay.
56-
- Parameter reader: A reference to the code reader.
52+
- Parameter builder: A QR code reader builder.
5753
*/
58-
func setupComponents(showCancelButton: Bool, showSwitchCameraButton: Bool, showTorchButton: Bool, showOverlayView: Bool, reader: QRCodeReader?)
54+
func setupComponents(with builder: QRCodeReaderViewControllerBuilder)
5955
}
6056

6157
/// 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 {
7571

7672
// MARK: - Convenience Methods
7773

78-
func setupComponents(showCancelButton: Bool, showSwitchCameraButton: Bool, showTorchButton: Bool, showOverlayView: Bool, reader: QRCodeReader? = nil) {
79-
displayable.setupComponents(showCancelButton: showCancelButton, showSwitchCameraButton: showSwitchCameraButton, showTorchButton: showTorchButton, showOverlayView: showOverlayView, reader: reader)
74+
func setupComponents(with builder: QRCodeReaderViewControllerBuilder) {
75+
displayable.setupComponents(with: builder)
8076
}
8177
}

Sources/QRCodeReaderViewController.swift

Lines changed: 24 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,12 @@ import AVFoundation
2929

3030
/// 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.
3131
public class QRCodeReaderViewController: UIViewController {
32-
/// The code reader object used to scan the bar code.
33-
public let codeReader: QRCodeReader
32+
private let builder: QRCodeReaderViewControllerBuilder
3433

35-
let readerView: QRCodeReaderContainer
36-
let startScanningAtLoad: Bool
37-
let showCancelButton: Bool
38-
let showSwitchCameraButton: Bool
39-
let showTorchButton: Bool
40-
let showOverlayView: Bool
41-
let customPreferredStatusBarStyle: UIStatusBarStyle?
34+
/// The code reader object used to scan the bar code.
35+
public var codeReader: QRCodeReader {
36+
return builder.reader
37+
}
4238

4339
// MARK: - Managing the Callback Responders
4440

@@ -62,49 +58,33 @@ public class QRCodeReaderViewController: UIViewController {
6258
- parameter builder: A QRCodeViewController builder object.
6359
*/
6460
required public init(builder: QRCodeReaderViewControllerBuilder) {
65-
readerView = builder.readerView
66-
startScanningAtLoad = builder.startScanningAtLoad
67-
codeReader = builder.reader
68-
showCancelButton = builder.showCancelButton
69-
showSwitchCameraButton = builder.showSwitchCameraButton
70-
showTorchButton = builder.showTorchButton
71-
showOverlayView = builder.showOverlayView
72-
customPreferredStatusBarStyle = builder.preferredStatusBarStyle
61+
self.builder = builder
7362

7463
super.init(nibName: nil, bundle: nil)
7564

7665
view.backgroundColor = .black
7766

7867
codeReader.didFindCode = { [weak self] resultAsObject in
7968
if let weakSelf = self {
80-
if let qrv = weakSelf.readerView.displayable as? QRCodeReaderView {
69+
if let qrv = builder.readerView.displayable as? QRCodeReaderView {
8170
qrv.addGreenBorder()
8271
}
8372
weakSelf.completionBlock?(resultAsObject)
8473
weakSelf.delegate?.reader(weakSelf, didScanResult: resultAsObject)
8574
}
8675
}
8776

88-
codeReader.didFailDecoding = { [weak self] in
89-
if let weakSelf = self {
90-
if let qrv = weakSelf.readerView.displayable as? QRCodeReaderView {
91-
qrv.addRedBorder()
92-
}
77+
codeReader.didFailDecoding = {
78+
if let qrv = builder.readerView.displayable as? QRCodeReaderView {
79+
qrv.addRedBorder()
9380
}
9481
}
9582

9683
setupUIComponentsWithCancelButtonTitle(builder.cancelButtonTitle)
9784
}
9885

9986
required public init?(coder aDecoder: NSCoder) {
100-
codeReader = QRCodeReader()
101-
readerView = QRCodeReaderContainer(displayable: QRCodeReaderView())
102-
startScanningAtLoad = false
103-
showCancelButton = false
104-
showTorchButton = false
105-
showSwitchCameraButton = false
106-
showOverlayView = false
107-
customPreferredStatusBarStyle = nil
87+
self.builder = QRCodeReaderViewControllerBuilder()
10888

10989
super.init(coder: aDecoder)
11090
}
@@ -114,8 +94,8 @@ public class QRCodeReaderViewController: UIViewController {
11494
override public func viewWillAppear(_ animated: Bool) {
11595
super.viewWillAppear(animated)
11696

117-
if startScanningAtLoad {
118-
readerView.displayable.setNeedsUpdateOrientation()
97+
if builder.startScanningAtLoad {
98+
builder.readerView.displayable.setNeedsUpdateOrientation()
11999

120100
startScanning()
121101
}
@@ -134,38 +114,35 @@ public class QRCodeReaderViewController: UIViewController {
134114
}
135115

136116
public override var preferredStatusBarStyle: UIStatusBarStyle {
137-
return customPreferredStatusBarStyle ?? super.preferredStatusBarStyle
117+
return builder.preferredStatusBarStyle ?? super.preferredStatusBarStyle
138118
}
139119

140120
// MARK: - Initializing the AV Components
141121

142122
private func setupUIComponentsWithCancelButtonTitle(_ cancelButtonTitle: String) {
143-
view.addSubview(readerView.view)
144-
145-
let sscb = showSwitchCameraButton && codeReader.hasFrontDevice
146-
let stb = showTorchButton && codeReader.isTorchAvailable
123+
view.addSubview(builder.readerView.view)
147124

148-
readerView.view.translatesAutoresizingMaskIntoConstraints = false
149-
readerView.setupComponents(showCancelButton: showCancelButton, showSwitchCameraButton: sscb, showTorchButton: stb, showOverlayView: showOverlayView, reader: codeReader)
125+
builder.readerView.view.translatesAutoresizingMaskIntoConstraints = false
126+
builder.readerView.setupComponents(with: builder)
150127

151128
// Setup action methods
152129

153-
readerView.displayable.switchCameraButton?.addTarget(self, action: #selector(switchCameraAction), for: .touchUpInside)
154-
readerView.displayable.toggleTorchButton?.addTarget(self, action: #selector(toggleTorchAction), for: .touchUpInside)
155-
readerView.displayable.cancelButton?.setTitle(cancelButtonTitle, for: .normal)
156-
readerView.displayable.cancelButton?.addTarget(self, action: #selector(cancelAction), for: .touchUpInside)
130+
builder.readerView.displayable.switchCameraButton?.addTarget(self, action: #selector(switchCameraAction), for: .touchUpInside)
131+
builder.readerView.displayable.toggleTorchButton?.addTarget(self, action: #selector(toggleTorchAction), for: .touchUpInside)
132+
builder.readerView.displayable.cancelButton?.setTitle(cancelButtonTitle, for: .normal)
133+
builder.readerView.displayable.cancelButton?.addTarget(self, action: #selector(cancelAction), for: .touchUpInside)
157134

158135
// Setup constraints
159136

160137
for attribute in [.left, .top, .right] as [NSLayoutConstraint.Attribute] {
161-
NSLayoutConstraint(item: readerView.view, attribute: attribute, relatedBy: .equal, toItem: view, attribute: attribute, multiplier: 1, constant: 0).isActive = true
138+
NSLayoutConstraint(item: builder.readerView.view, attribute: attribute, relatedBy: .equal, toItem: view, attribute: attribute, multiplier: 1, constant: 0).isActive = true
162139
}
163140

164141
if #available(iOS 11.0, *) {
165-
view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: readerView.view.bottomAnchor).isActive = true
142+
view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: builder.readerView.view.bottomAnchor).isActive = true
166143
}
167144
else {
168-
NSLayoutConstraint(item: readerView.view, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
145+
NSLayoutConstraint(item: builder.readerView.view, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
169146
}
170147
}
171148

Sources/QRCodeReaderViewControllerBuilder.swift

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,28 @@ public final class QRCodeReaderViewControllerBuilder {
6868
/**
6969
Flag to display the switch camera button.
7070
*/
71-
public var showSwitchCameraButton = true
71+
public var showSwitchCameraButton: Bool {
72+
get {
73+
return _showSwitchCameraButton && reader.hasFrontDevice
74+
}
75+
set {
76+
_showSwitchCameraButton = newValue
77+
}
78+
}
79+
private var _showSwitchCameraButton: Bool = true
7280

7381
/**
7482
Flag to display the toggle torch button. If the value is true and there is no torch the button will not be displayed.
7583
*/
76-
public var showTorchButton = false
84+
public var showTorchButton: Bool {
85+
get {
86+
return _showTorchButton && reader.isTorchAvailable
87+
}
88+
set {
89+
_showTorchButton = newValue
90+
}
91+
}
92+
private var _showTorchButton = true
7793

7894
/**
7995
Flag to display the guide view.
@@ -91,6 +107,22 @@ public final class QRCodeReaderViewControllerBuilder {
91107
*/
92108
public var preferredStatusBarStyle: UIStatusBarStyle? = nil
93109

110+
/**
111+
Specifies a rectangle of interest for limiting the search area for visual metadata.
112+
113+
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.
114+
*/
115+
public var rectOfInterest: CGRect = CGRect(x: 0, y: 0, width: 1, height: 1) {
116+
didSet {
117+
reader.metadataOutput.rectOfInterest = CGRect(
118+
x: min(max(rectOfInterest.origin.x, 0), 1),
119+
y: min(max(rectOfInterest.origin.y, 0), 1),
120+
width: min(max(rectOfInterest.width, 0), 1),
121+
height: min(max(rectOfInterest.height, 0), 1)
122+
)
123+
}
124+
}
125+
94126
// MARK: - Initializing a Flap View
95127

96128
/**

Sources/ReaderOverlayView.swift

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,24 +58,25 @@ public final class ReaderOverlayView: UIView {
5858

5959
var overlayColor: UIColor = UIColor.white {
6060
didSet {
61-
self.overlay.strokeColor = overlayColor.cgColor
61+
overlay.strokeColor = overlayColor.cgColor
6262

63-
self.setNeedsDisplay()
63+
setNeedsDisplay()
6464
}
6565
}
6666

67-
public override func draw(_ rect: CGRect) {
68-
var innerRect = rect.insetBy(dx: 50, dy: 50)
69-
let minSize = min(innerRect.width, innerRect.height)
70-
71-
if innerRect.width != minSize {
72-
innerRect.origin.x += (innerRect.width - minSize) / 2
73-
innerRect.size.width = minSize
74-
}
75-
else if innerRect.height != minSize {
76-
innerRect.origin.y += (innerRect.height - minSize) / 2
77-
innerRect.size.height = minSize
67+
var rectOfInterest: CGRect = CGRect(x: 0, y: 0, width: 1, height: 1) {
68+
didSet {
69+
setNeedsDisplay()
7870
}
71+
}
72+
73+
public override func draw(_ rect: CGRect) {
74+
let innerRect = CGRect(
75+
x: rect.width * rectOfInterest.minX,
76+
y: rect.height * rectOfInterest.minY,
77+
width: rect.width * rectOfInterest.width,
78+
height: rect.height * rectOfInterest.height
79+
)
7980

8081
overlay.path = UIBezierPath(roundedRect: innerRect, cornerRadius: 5).cgPath
8182
}

0 commit comments

Comments
 (0)