Skip to content

Commit 06c198d

Browse files
Merge pull request #6 from OutSystems/feat/RMET-2771/filter-scanning-zone
RMET-2771, 2911 ::: Filter Scanning View
2 parents 3a3f297 + 678400a commit 06c198d

11 files changed

+188
-124
lines changed

OSBarcodeLib.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
7513C48E2B03E8DF005E81C4 /* UIInterfaceOrientationMask+OSBARCModelMappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7513C48C2B03E8DE005E81C4 /* UIInterfaceOrientationMask+OSBARCModelMappable.swift */; };
1919
7513C4902B03E922005E81C4 /* AVCaptureVideoOrientation+CustomInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7513C48F2B03E922005E81C4 /* AVCaptureVideoOrientation+CustomInit.swift */; };
2020
7554D70B2AFA3A0E00D4261C /* OSBARCTorchButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7554D70A2AFA3A0E00D4261C /* OSBARCTorchButton.swift */; };
21+
75562B3D2B17676D00F31AF6 /* Notification+ScanFrameChanged.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75562B3C2B17676D00F31AF6 /* Notification+ScanFrameChanged.swift */; };
22+
75562B3F2B1767C100F31AF6 /* UIApplication+Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75562B3E2B1767C100F31AF6 /* UIApplication+Window.swift */; };
2123
758E6C162B0238FF00FC16D9 /* OSBARCCameraModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758E6C152B0238FF00FC16D9 /* OSBARCCameraModel.swift */; };
2224
758E6C192B0239E700FC16D9 /* AVCaptureDevice+OSBARCModelMappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758E6C182B0239E700FC16D9 /* AVCaptureDevice+OSBARCModelMappable.swift */; };
2325
75A1FC632AFA40D200AA775F /* OSBARCScannerView.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 75A1FC622AFA40D200AA775F /* OSBARCScannerView.xcassets */; };
@@ -71,6 +73,8 @@
7173
7513C48C2B03E8DE005E81C4 /* UIInterfaceOrientationMask+OSBARCModelMappable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIInterfaceOrientationMask+OSBARCModelMappable.swift"; sourceTree = "<group>"; };
7274
7513C48F2B03E922005E81C4 /* AVCaptureVideoOrientation+CustomInit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVCaptureVideoOrientation+CustomInit.swift"; sourceTree = "<group>"; };
7375
7554D70A2AFA3A0E00D4261C /* OSBARCTorchButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCTorchButton.swift; sourceTree = "<group>"; };
76+
75562B3C2B17676D00F31AF6 /* Notification+ScanFrameChanged.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+ScanFrameChanged.swift"; sourceTree = "<group>"; };
77+
75562B3E2B1767C100F31AF6 /* UIApplication+Window.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Window.swift"; sourceTree = "<group>"; };
7478
758E6C152B0238FF00FC16D9 /* OSBARCCameraModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCCameraModel.swift; sourceTree = "<group>"; };
7579
758E6C182B0239E700FC16D9 /* AVCaptureDevice+OSBARCModelMappable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVCaptureDevice+OSBARCModelMappable.swift"; sourceTree = "<group>"; };
7680
75A1FC622AFA40D200AA775F /* OSBARCScannerView.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = OSBARCScannerView.xcassets; sourceTree = "<group>"; };
@@ -186,6 +190,8 @@
186190
children = (
187191
758E6C182B0239E700FC16D9 /* AVCaptureDevice+OSBARCModelMappable.swift */,
188192
7513C48F2B03E922005E81C4 /* AVCaptureVideoOrientation+CustomInit.swift */,
193+
75562B3C2B17676D00F31AF6 /* Notification+ScanFrameChanged.swift */,
194+
75562B3E2B1767C100F31AF6 /* UIApplication+Window.swift */,
189195
7513C48C2B03E8DE005E81C4 /* UIInterfaceOrientationMask+OSBARCModelMappable.swift */,
190196
7513C48B2B03E8DE005E81C4 /* UIUserInterfaceIdiom+OSBARCModelMappable.swift */,
191197
75E2B20E2AF41B5000DB689E /* View+CustomModifiers.swift */,
@@ -404,6 +410,7 @@
404410
758E6C192B0239E700FC16D9 /* AVCaptureDevice+OSBARCModelMappable.swift in Sources */,
405411
75D20FE92AF17C1C009AD84D /* OSBARCPermissionsProtocol.swift in Sources */,
406412
75D20FE52AF17A87009AD84D /* OSBARCCoordinator.swift in Sources */,
413+
75562B3F2B1767C100F31AF6 /* UIApplication+Window.swift in Sources */,
407414
75D20FE72AF17AFC009AD84D /* OSBARCCoordinatorProtocol.swift in Sources */,
408415
75D20FE22AF16B0A009AD84D /* OSBARCManagerFactory.swift in Sources */,
409416
75EF59A42B0E4A410084F144 /* OSBARCScanButton.swift in Sources */,
@@ -420,6 +427,7 @@
420427
7513C48E2B03E8DF005E81C4 /* UIInterfaceOrientationMask+OSBARCModelMappable.swift in Sources */,
421428
75D20FDF2AF16AE4009AD84D /* OSBARCManagerProtocol.swift in Sources */,
422429
75EF599E2B0E31620084F144 /* OSBARCCancelButton.swift in Sources */,
430+
75562B3D2B17676D00F31AF6 /* Notification+ScanFrameChanged.swift in Sources */,
423431
7513C4872B03E86B005E81C4 /* OSBARCDeviceTypeModelMappable.swift in Sources */,
424432
75EF59A02B0E44660084F144 /* OSBARCInstructionsText.swift in Sources */,
425433
7513C4882B03E86B005E81C4 /* OSBARCModelMappable.swift in Sources */,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Foundation
2+
3+
extension Notification.Name {
4+
/// Notification triggered when barcode's scan frame gets changed.
5+
static let scanFrameChanged = Notification.Name("scanFrameChanged")
6+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import UIKit
2+
3+
extension UIApplication {
4+
/// The application's main window.
5+
static var firstKeyWindowForConnectedScenes: UIWindow? {
6+
UIApplication
7+
.shared
8+
.connectedScenes
9+
.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
10+
.last { $0.isKeyWindow }
11+
}
12+
}

OSBarcodeLib/Scanner/Extensions/View+CustomModifiers.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Combine
12
import SwiftUI
23

34
extension View {
@@ -37,4 +38,18 @@ extension View {
3738
self.edgesIgnoringSafeArea(.all)
3839
}
3940
}
41+
42+
/// Adds a modifier for this view that fires an action when a specific value changes.
43+
/// - Parameters:
44+
/// - value: The value to check against when determining whether to run the closure.
45+
/// - onChange: A closure to run when the value changes.
46+
/// - Returns: A view that fires an action when the specified value changes.
47+
@ViewBuilder
48+
func valueChanged<T: Equatable>(value: T, _ onChange: @escaping (T) -> Void) -> some View {
49+
if #available(iOS 14.0, *) {
50+
self.onChange(of: value, perform: onChange)
51+
} else {
52+
self.onReceive(Just(value)) { onChange($0) }
53+
}
54+
}
4055
}

OSBarcodeLib/Scanner/Interface Elements/OSBARCBackgroundView.swift

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,22 @@ import SwiftUI
33
/// Background view that is displayed behind the interface elements (a black view with a little transparency).
44
/// Since the view has a "hole" in the middle, in reality is composed by 4 rectangles, one for each side of the hole.
55
struct OSBARCBackgroundView: View {
6-
/// The smallest size (height/width) to be used to draw the side rectangle
7-
let padding: CGFloat
8-
/// The biggest size (height/width) to be used to draw the side rectangle.
9-
let size: CGSize
6+
/// Frame of portion of the screen used for scanning.
7+
let scanFrame: CGRect
108

119
/// Colour to be displayed as the background.
1210
private let colour: Color = OSBARCScannerViewConfigurationValues.backgroundColour
11+
/// Radius to apply on the vertices.
12+
private let radius: CGFloat = OSBARCScannerViewConfigurationValues.defaultRadius
1313

14-
var body: some View {
15-
// left side
16-
colour
17-
.frame(width: padding, height: size.height)
18-
// right side
19-
colour
20-
.frame(width: padding, height: size.height)
21-
.offset(x: size.width - padding)
22-
// top side
23-
colour
24-
.frame(width: size.width - padding * 2.0, height: padding)
25-
.offset(x: padding)
26-
// bottom side
27-
colour
28-
.frame(width: size.width - padding * 2.0, height: padding)
29-
.offset(x: padding, y: size.height - padding)
14+
var body: some View {
15+
ZStack(alignment: .topLeading) {
16+
colour
17+
18+
RoundedRectangle(cornerRadius: radius)
19+
.blendMode(.destinationOut)
20+
.offset(x: scanFrame.minX, y: scanFrame.minY)
21+
.frame(width: scanFrame.width, height: scanFrame.height)
22+
}.compositingGroup()
3023
}
3124
}

OSBarcodeLib/Scanner/Interface Elements/OSBARCInstructionsText.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,11 @@ struct OSBARCInstructionsText: View {
77

88
/// The colour to be used for the font.
99
private let foregroundColour: Color = OSBARCScannerViewConfigurationValues.mainColour
10-
/// The colour to be used as the text's background.
11-
private let backgroundColour: Color = OSBARCScannerViewConfigurationValues.backgroundColour
1210

1311
var body: some View {
1412
Text(instructionsText)
1513
.foregroundStyle(forColour: foregroundColour)
1614
.fixedSize(horizontal: false, vertical: true) // allows the text to grow vertically in case of multiline text.
1715
.frame(maxWidth: .infinity, alignment: .center) // allows the text to grow horizontally, centering it on the view it's inserted on.
18-
.background(backgroundColour)
1916
}
2017
}

OSBarcodeLib/Scanner/Interface Elements/OSBARCScanningZone.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ struct OSBARCScanningZone: View {
66
/// The size of the scanning zone.
77
let size: CGSize
88

9-
/// Considering the aim outside is a Rounded Rectangle, this is the radius its vertices.
9+
/// Considering the aim outside is a Rounded Rectangle, this is the radius of its vertices.
1010
private let radius: CGFloat = OSBARCScannerViewConfigurationValues.defaultRadius
1111
/// The colour of the aim's line.
1212
private let color: Color = OSBARCScannerViewConfigurationValues.mainColour

OSBarcodeLib/Scanner/OSBARCScannerView.swift

Lines changed: 69 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ struct OSBARCScannerView: View {
3030
/// The type of device being used.
3131
let deviceType: OSBARCDeviceTypeModel
3232

33+
/// Frame of portion of the screen used for scanning.
34+
@State var scanFrame: CGRect = .zero
35+
3336
/// The horizontal visual size available.
3437
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
3538
/// The vertical visual size available.
@@ -46,20 +49,9 @@ struct OSBARCScannerView: View {
4649
/// The spacing between the buttons (used on iPads and iPhones on Landscape mode).
4750
private let buttonSpacing: CGFloat = OSBARCScannerViewConfigurationValues.buttonSpacing
4851

49-
/// Draws the background view (black with a little transparency).
50-
/// - Parameters:
51-
/// - height: The height to use, if any.
52-
/// - width: The width to use, if any.
53-
/// - Returns: Returns the view to display.
54-
@ViewBuilder
55-
private func backgroundView(height: CGFloat? = nil, width: CGFloat? = nil) -> some View {
56-
OSBARCScannerViewConfigurationValues.backgroundColour
57-
.if(height != nil) {
58-
$0.frame(height: height ?? 0.0) // the fallback value will never be executed
59-
}
60-
.if(width != nil) {
61-
$0.frame(width: width ?? 0.0) // the fallback value will never be executed
62-
}
52+
/// View filled with the background colour
53+
private var backgroundView: OSBARCBackgroundView {
54+
.init(scanFrame: scanFrame)
6355
}
6456

6557
/// Cancel button.
@@ -91,12 +83,22 @@ struct OSBARCScannerView: View {
9183
private func scanningZone(for size: CGSize) -> some View {
9284
let scannerSize: CGSize = calculateSize(for: size)
9385

94-
ZStack(alignment: .topLeading) {
95-
// inner background view
96-
OSBARCBackgroundView(padding: scannerPadding, size: scannerSize)
97-
98-
// Scanning Zone
86+
// Scanning Zone
87+
GeometryReader { scanningZoneProxy in
9988
OSBARCScanningZone(size: scannerSize)
89+
.onAppear(perform: {
90+
let scanningZoneFrame = scanningZoneProxy.frame(in: .global)
91+
scanFrame = .init(
92+
x: scanningZoneFrame.minX + scannerPadding,
93+
y: scanningZoneFrame.minY + scannerPadding,
94+
width: scanningZoneFrame.width - 2.0 * scannerPadding,
95+
height: scanningZoneFrame.height - 2.0 * scannerPadding
96+
)
97+
})
98+
.valueChanged(value: scanFrame) {
99+
// everytime `scanFrame` changes, the notification is triggered so that the barcode detection region gets updated.
100+
NotificationCenter.default.post(name: .scanFrameChanged, object: $0)
101+
}
100102
}
101103
.frame(width: scannerSize.width, height: scannerSize.height)
102104
}
@@ -108,8 +110,6 @@ struct OSBARCScannerView: View {
108110
// Scan Instructions
109111
instructionsTextField
110112

111-
backgroundView(height: scannerPadding)
112-
113113
GeometryReader {
114114
scanningZone(for: $0.size)
115115
}
@@ -126,115 +126,96 @@ struct OSBARCScannerView: View {
126126

127127
// MARK: - Main Element
128128
var body: some View {
129-
ZStack(alignment: .topLeading) {
129+
ZStack {
130130
// Camera Stream
131131
OSBARCScannerViewControllerRepresentable(captureDevice, $scanResult, shouldShowButton, $buttonScanEnabled, orientationModel)
132132

133-
// Background Black View
134-
GeometryReader { proxy in
135-
OSBARCBackgroundView(padding: screenPadding, size: proxy.size)
136-
}
133+
backgroundView
137134

138135
if isPhoneInPortrait {
139-
VStack(spacing: 0.0) {
136+
VStack(spacing: screenPadding) {
140137
// X View
141-
ZStack {
142-
backgroundView()
138+
HStack {
139+
Spacer()
143140

144-
HStack {
145-
Spacer()
146-
147-
// Cancel Button
148-
cancelButton
149-
}
141+
// Cancel Button
142+
cancelButton
150143
}
151144

152-
backgroundView(height: screenPadding)
153-
154145
GeometryReader { scannerProxy in
155-
VStack(spacing: 0.0) {
156-
backgroundView()
157-
146+
VStack(spacing: screenPadding) {
158147
// Scan Instructions
159148
instructionsTextField
160-
161-
backgroundView(height: screenPadding)
162-
149+
// Scanning Zone
163150
scanningZone(for: scannerProxy.size)
164-
165-
backgroundView()
166151
}
152+
.frame(maxHeight: .infinity, alignment: .center)
167153
}
168-
.layoutPriority(1)
169-
170-
backgroundView(height: screenPadding)
171154

172155
// Buttons View
173-
ZStack {
174-
backgroundView()
175-
176-
ZStack(alignment: .trailing) {
177-
// Scan Button
178-
scanButton
156+
ZStack(alignment: .trailing) {
157+
// Scan Button
158+
scanButton
179159
.opacity(!shouldShowButton ? 0.0 : 1.0)
180160
.disabled(!shouldShowButton)
181161
.frame(maxWidth: .infinity)
182-
183-
// Torch Button
184-
torchButton
162+
163+
// Torch Button
164+
torchButton
185165
.opacity(!cameraHasTorch ? 0.0 : 1.0)
186-
.disabled(!cameraHasTorch)
187-
}
166+
.disabled(!cameraHasTorch)
188167
}
189168
}
190169
.padding(screenPadding)
191170
} else {
192171
GeometryReader { mainProxy in
193-
HStack(spacing: 0.0) {
194-
backgroundView()
172+
HStack(spacing: scannerPadding) {
173+
Color.clear
195174

196-
VStack(spacing: 0.0) {
197-
backgroundView()
175+
VStack {
176+
Spacer()
198177

199178
if deviceType == .iphone {
200179
scanningZoneWithInstructions
180+
}
181+
// Despite the similarities between the following views,
182+
// this is required so that `scanFrame` gets correctly updated
183+
else if mainProxy.size.width < mainProxy.size.height {
184+
VStack(spacing: scannerPadding) {
185+
scanningZoneWithInstructions
186+
}
187+
.frame(height: mainProxy.size.width * 0.5)
201188
} else {
202-
VStack(spacing: 0.0) {
189+
VStack(spacing: scannerPadding) {
203190
scanningZoneWithInstructions
204191
}
205-
.frame(height: min(mainProxy.size.width, mainProxy.size.height) * 0.5)
192+
.frame(height: mainProxy.size.height * 0.5)
206193
}
207194

208-
backgroundView()
195+
Spacer()
209196
}
210197
.frame(width: mainProxy.size.width * 0.5 - scannerPadding * 2.0)
211198

212-
backgroundView(width: screenPadding)
213-
214199
// Buttons View
215-
ZStack(alignment: .trailing) {
216-
backgroundView()
200+
VStack(alignment: .trailing, spacing: buttonSpacing) {
201+
// Cancel Button
202+
cancelButton
217203

218-
VStack(alignment: .trailing, spacing: buttonSpacing) {
219-
// Cancel Button
220-
cancelButton
221-
222-
Spacer()
223-
224-
if cameraHasTorch {
225-
// Torch Button
226-
torchButton
227-
}
228-
229-
if shouldShowButton {
230-
// Scan Button
231-
scanButton
232-
}
233-
234-
Spacer()
204+
Spacer()
205+
206+
if cameraHasTorch {
207+
// Torch Button
208+
torchButton
235209
}
210+
211+
if shouldShowButton {
212+
// Scan Button
213+
scanButton
214+
}
215+
216+
Spacer()
236217
}
237-
.frame(maxWidth: .infinity)
218+
.frame(maxWidth: .infinity, alignment: .trailing)
238219
}
239220
}.padding(screenPadding)
240221
}

0 commit comments

Comments
 (0)