Skip to content

Commit 3a3f297

Browse files
Merge pull request #5 from OutSystems/feat/RMET-2771/phone-interface
RMET-2771, 2911 ::: Implement Scanner View User Interface
2 parents cd74417 + 10af6c8 commit 3a3f297

15 files changed

+524
-120
lines changed

OSBarcodeLib.xcodeproj/project.pbxproj

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
7513C48D2B03E8DF005E81C4 /* UIUserInterfaceIdiom+OSBARCModelMappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7513C48B2B03E8DE005E81C4 /* UIUserInterfaceIdiom+OSBARCModelMappable.swift */; };
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 */; };
20-
7554D70B2AFA3A0E00D4261C /* ToggleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7554D70A2AFA3A0E00D4261C /* ToggleButton.swift */; };
20+
7554D70B2AFA3A0E00D4261C /* OSBARCTorchButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7554D70A2AFA3A0E00D4261C /* OSBARCTorchButton.swift */; };
2121
758E6C162B0238FF00FC16D9 /* OSBARCCameraModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758E6C152B0238FF00FC16D9 /* OSBARCCameraModel.swift */; };
2222
758E6C192B0239E700FC16D9 /* AVCaptureDevice+OSBARCModelMappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758E6C182B0239E700FC16D9 /* AVCaptureDevice+OSBARCModelMappable.swift */; };
2323
75A1FC632AFA40D200AA775F /* OSBARCScannerView.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 75A1FC622AFA40D200AA775F /* OSBARCScannerView.xcassets */; };
@@ -40,7 +40,12 @@
4040
75E2B2062AF4153900DB689E /* OSBARCManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75E2B2052AF4153900DB689E /* OSBARCManagerTests.swift */; };
4141
75E2B2092AF4156F00DB689E /* OSBARCPermissionsStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75E2B2082AF4156F00DB689E /* OSBARCPermissionsStub.swift */; };
4242
75E2B20C2AF415C000DB689E /* OSBARCScannerStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75E2B20B2AF415C000DB689E /* OSBARCScannerStub.swift */; };
43-
75E2B20F2AF41B5000DB689E /* View+StyleForColour.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75E2B20E2AF41B5000DB689E /* View+StyleForColour.swift */; };
43+
75E2B20F2AF41B5000DB689E /* View+CustomModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75E2B20E2AF41B5000DB689E /* View+CustomModifiers.swift */; };
44+
75EF599A2B0E2F220084F144 /* OSBARCBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75EF59992B0E2F220084F144 /* OSBARCBackgroundView.swift */; };
45+
75EF599E2B0E31620084F144 /* OSBARCCancelButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75EF599D2B0E31620084F144 /* OSBARCCancelButton.swift */; };
46+
75EF59A02B0E44660084F144 /* OSBARCInstructionsText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75EF599F2B0E44660084F144 /* OSBARCInstructionsText.swift */; };
47+
75EF59A22B0E462B0084F144 /* OSBARCScanningZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75EF59A12B0E462B0084F144 /* OSBARCScanningZone.swift */; };
48+
75EF59A42B0E4A410084F144 /* OSBARCScanButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75EF59A32B0E4A410084F144 /* OSBARCScanButton.swift */; };
4449
/* End PBXBuildFile section */
4550

4651
/* Begin PBXContainerItemProxy section */
@@ -65,7 +70,7 @@
6570
7513C48B2B03E8DE005E81C4 /* UIUserInterfaceIdiom+OSBARCModelMappable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIUserInterfaceIdiom+OSBARCModelMappable.swift"; sourceTree = "<group>"; };
6671
7513C48C2B03E8DE005E81C4 /* UIInterfaceOrientationMask+OSBARCModelMappable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIInterfaceOrientationMask+OSBARCModelMappable.swift"; sourceTree = "<group>"; };
6772
7513C48F2B03E922005E81C4 /* AVCaptureVideoOrientation+CustomInit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVCaptureVideoOrientation+CustomInit.swift"; sourceTree = "<group>"; };
68-
7554D70A2AFA3A0E00D4261C /* ToggleButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleButton.swift; sourceTree = "<group>"; };
73+
7554D70A2AFA3A0E00D4261C /* OSBARCTorchButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCTorchButton.swift; sourceTree = "<group>"; };
6974
758E6C152B0238FF00FC16D9 /* OSBARCCameraModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCCameraModel.swift; sourceTree = "<group>"; };
7075
758E6C182B0239E700FC16D9 /* AVCaptureDevice+OSBARCModelMappable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVCaptureDevice+OSBARCModelMappable.swift"; sourceTree = "<group>"; };
7176
75A1FC622AFA40D200AA775F /* OSBARCScannerView.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = OSBARCScannerView.xcassets; sourceTree = "<group>"; };
@@ -88,7 +93,12 @@
8893
75E2B2052AF4153900DB689E /* OSBARCManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCManagerTests.swift; sourceTree = "<group>"; };
8994
75E2B2082AF4156F00DB689E /* OSBARCPermissionsStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCPermissionsStub.swift; sourceTree = "<group>"; };
9095
75E2B20B2AF415C000DB689E /* OSBARCScannerStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCScannerStub.swift; sourceTree = "<group>"; };
91-
75E2B20E2AF41B5000DB689E /* View+StyleForColour.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+StyleForColour.swift"; sourceTree = "<group>"; };
96+
75E2B20E2AF41B5000DB689E /* View+CustomModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+CustomModifiers.swift"; sourceTree = "<group>"; };
97+
75EF59992B0E2F220084F144 /* OSBARCBackgroundView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSBARCBackgroundView.swift; sourceTree = "<group>"; };
98+
75EF599D2B0E31620084F144 /* OSBARCCancelButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCCancelButton.swift; sourceTree = "<group>"; };
99+
75EF599F2B0E44660084F144 /* OSBARCInstructionsText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCInstructionsText.swift; sourceTree = "<group>"; };
100+
75EF59A12B0E462B0084F144 /* OSBARCScanningZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCScanningZone.swift; sourceTree = "<group>"; };
101+
75EF59A32B0E4A410084F144 /* OSBARCScanButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCScanButton.swift; sourceTree = "<group>"; };
92102
/* End PBXFileReference section */
93103

94104
/* Begin PBXFrameworksBuildPhase section */
@@ -132,7 +142,6 @@
132142
isa = PBXGroup;
133143
children = (
134144
758E6C142B0238F100FC16D9 /* Models */,
135-
75E2B20D2AF41B3F00DB689E /* ViewModifiers */,
136145
75E2B1D02AF3ADE500DB689E /* Coordinator */,
137146
75D20FE02AF16AF1009AD84D /* Manager */,
138147
75E2B2042AF414DD00DB689E /* Permissions */,
@@ -179,6 +188,7 @@
179188
7513C48F2B03E922005E81C4 /* AVCaptureVideoOrientation+CustomInit.swift */,
180189
7513C48C2B03E8DE005E81C4 /* UIInterfaceOrientationMask+OSBARCModelMappable.swift */,
181190
7513C48B2B03E8DE005E81C4 /* UIUserInterfaceIdiom+OSBARCModelMappable.swift */,
191+
75E2B20E2AF41B5000DB689E /* View+CustomModifiers.swift */,
182192
);
183193
path = Extensions;
184194
sourceTree = "<group>";
@@ -196,6 +206,7 @@
196206
75E2B1C32AF3A97400DB689E /* Scanner */ = {
197207
isa = PBXGroup;
198208
children = (
209+
75EF59982B0E2EE30084F144 /* Interface Elements */,
199210
758E6C172B0239C100FC16D9 /* Extensions */,
200211
75A1FC622AFA40D200AA775F /* OSBARCScannerView.xcassets */,
201212
75E2B1CC2AF3AC5500DB689E /* OSBARCScannerBehaviour.swift */,
@@ -238,13 +249,17 @@
238249
path = Stubs;
239250
sourceTree = "<group>";
240251
};
241-
75E2B20D2AF41B3F00DB689E /* ViewModifiers */ = {
252+
75EF59982B0E2EE30084F144 /* Interface Elements */ = {
242253
isa = PBXGroup;
243254
children = (
244-
7554D70A2AFA3A0E00D4261C /* ToggleButton.swift */,
245-
75E2B20E2AF41B5000DB689E /* View+StyleForColour.swift */,
246-
);
247-
path = ViewModifiers;
255+
75EF59992B0E2F220084F144 /* OSBARCBackgroundView.swift */,
256+
75EF599D2B0E31620084F144 /* OSBARCCancelButton.swift */,
257+
75EF599F2B0E44660084F144 /* OSBARCInstructionsText.swift */,
258+
75EF59A32B0E4A410084F144 /* OSBARCScanButton.swift */,
259+
75EF59A12B0E462B0084F144 /* OSBARCScanningZone.swift */,
260+
7554D70A2AFA3A0E00D4261C /* OSBARCTorchButton.swift */,
261+
);
262+
path = "Interface Elements";
248263
sourceTree = "<group>";
249264
};
250265
/* End PBXGroup section */
@@ -391,20 +406,25 @@
391406
75D20FE52AF17A87009AD84D /* OSBARCCoordinator.swift in Sources */,
392407
75D20FE72AF17AFC009AD84D /* OSBARCCoordinatorProtocol.swift in Sources */,
393408
75D20FE22AF16B0A009AD84D /* OSBARCManagerFactory.swift in Sources */,
409+
75EF59A42B0E4A410084F144 /* OSBARCScanButton.swift in Sources */,
394410
75D20FDC2AF16AC9009AD84D /* OSBARCManager.swift in Sources */,
395411
75E2B1D42AF3C80C00DB689E /* OSBARCScannerViewController.swift in Sources */,
396412
75E2B1D62AF3C93200DB689E /* OSBARCScannerViewControllerRepresentable.swift in Sources */,
397-
7554D70B2AFA3A0E00D4261C /* ToggleButton.swift in Sources */,
413+
7554D70B2AFA3A0E00D4261C /* OSBARCTorchButton.swift in Sources */,
414+
75EF59A22B0E462B0084F144 /* OSBARCScanningZone.swift in Sources */,
398415
7513C48D2B03E8DF005E81C4 /* UIUserInterfaceIdiom+OSBARCModelMappable.swift in Sources */,
399-
75E2B20F2AF41B5000DB689E /* View+StyleForColour.swift in Sources */,
416+
75E2B20F2AF41B5000DB689E /* View+CustomModifiers.swift in Sources */,
400417
75E2B1D22AF3AE1200DB689E /* OSBARCCoordinatable.swift in Sources */,
401418
75E2B1D82AF3C95800DB689E /* OSBARCScannerViewControllerCoordinator.swift in Sources */,
402419
758E6C162B0238FF00FC16D9 /* OSBARCCameraModel.swift in Sources */,
403420
7513C48E2B03E8DF005E81C4 /* UIInterfaceOrientationMask+OSBARCModelMappable.swift in Sources */,
404421
75D20FDF2AF16AE4009AD84D /* OSBARCManagerProtocol.swift in Sources */,
422+
75EF599E2B0E31620084F144 /* OSBARCCancelButton.swift in Sources */,
405423
7513C4872B03E86B005E81C4 /* OSBARCDeviceTypeModelMappable.swift in Sources */,
424+
75EF59A02B0E44660084F144 /* OSBARCInstructionsText.swift in Sources */,
406425
7513C4882B03E86B005E81C4 /* OSBARCModelMappable.swift in Sources */,
407426
7513C4862B03E86B005E81C4 /* OSBARCOrientationModel.swift in Sources */,
427+
75EF599A2B0E2F220084F144 /* OSBARCBackgroundView.swift in Sources */,
408428
7513C4852B03E86B005E81C4 /* OSBARCDeviceTypeModel.swift in Sources */,
409429
);
410430
runOnlyForDeploymentPostprocessing = 0;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import SwiftUI
2+
3+
extension View {
4+
/// Applies a colour to the View's foreground.
5+
/// - Parameter colour: Colour to be applied.
6+
/// - Returns: The resulting view.
7+
@ViewBuilder
8+
func foregroundStyle(forColour colour: Color) -> some View {
9+
if #available(iOS 15.0, *) {
10+
self.foregroundStyle(colour)
11+
} else {
12+
self.foregroundColor(colour)
13+
}
14+
}
15+
16+
/// Applies the given transform if the given condition evaluates to `true`.
17+
/// - Parameters:
18+
/// - condition: The condition to evaluate.
19+
/// - transform: The transform to apply to the source `View`.
20+
/// - Returns: Either the original `View` or the modified `View` if the condition is `true`.
21+
@ViewBuilder
22+
func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
23+
if condition {
24+
transform(self)
25+
} else {
26+
self
27+
}
28+
}
29+
30+
/// Ignores safe area for different versions of iOS.
31+
/// - Returns: the View ignoring all safe areas.
32+
@ViewBuilder
33+
func customIgnoreSafeArea() -> some View {
34+
if #available(iOS 14.0, *) {
35+
self.ignoresSafeArea()
36+
} else {
37+
self.edgesIgnoringSafeArea(.all)
38+
}
39+
}
40+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import SwiftUI
2+
3+
/// Background view that is displayed behind the interface elements (a black view with a little transparency).
4+
/// Since the view has a "hole" in the middle, in reality is composed by 4 rectangles, one for each side of the hole.
5+
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
10+
11+
/// Colour to be displayed as the background.
12+
private let colour: Color = OSBARCScannerViewConfigurationValues.backgroundColour
13+
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)
30+
}
31+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import SwiftUI
2+
3+
/// User Interface element to draw the button that allows cancelling the operation
4+
/// It's shown as a white X on the screen.
5+
struct OSBARCCancelButton: View {
6+
/// The action performed when the button is clicked.
7+
let action: () -> Void
8+
9+
/// The icon to display..
10+
private let cancelIcon: String = "xmark"
11+
/// The scale to apply to the icon to display.
12+
private let cancelIconScale: Image.Scale = .large
13+
/// The color of the icon.
14+
private let foregroundColour: Color = OSBARCScannerViewConfigurationValues.mainColour
15+
/// The size of the button. It's applied to both width and height.
16+
private let buttonSize: CGFloat = 32.0
17+
18+
var body: some View {
19+
Button(action: action) {
20+
Image(systemName: cancelIcon) // SF Symbols value.
21+
.imageScale(cancelIconScale)
22+
.foregroundStyle(forColour: foregroundColour)
23+
}
24+
.frame(width: buttonSize, height: buttonSize)
25+
}
26+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import SwiftUI
2+
3+
/// The scanning instructions to be displayed on the screen, next to the scanning zone.
4+
struct OSBARCInstructionsText: View {
5+
/// The text to be displayed.
6+
let instructionsText: String
7+
8+
/// The colour to be used for the font.
9+
private let foregroundColour: Color = OSBARCScannerViewConfigurationValues.mainColour
10+
/// The colour to be used as the text's background.
11+
private let backgroundColour: Color = OSBARCScannerViewConfigurationValues.backgroundColour
12+
13+
var body: some View {
14+
Text(instructionsText)
15+
.foregroundStyle(forColour: foregroundColour)
16+
.fixedSize(horizontal: false, vertical: true) // allows the text to grow vertically in case of multiline text.
17+
.frame(maxWidth: .infinity, alignment: .center) // allows the text to grow horizontally, centering it on the view it's inserted on.
18+
.background(backgroundColour)
19+
}
20+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import SwiftUI
2+
3+
/// Button that, when enabled, allows the user to start the scan processing
4+
struct OSBARCScanButton: View {
5+
/// The action performed when the button is clicked.
6+
let action: () -> Void
7+
/// Text to be shown on the button.
8+
let text: String
9+
/// Indicates if the feature is enabled or not.
10+
let isOn: Bool
11+
12+
/// Text colour to use when the button's feature is set to `on`
13+
private let selectedForegroundColour: Color = OSBARCScannerViewConfigurationValues.tertiaryColour
14+
/// Text colour to use when the button's feature is set to `off`
15+
private let notSelectedForegroundColour: Color = OSBARCScannerViewConfigurationValues.mainColour
16+
/// Background colour to use when the button's feature is set to `on`.
17+
private let selectedBackgroundColour: Color = OSBARCScannerViewConfigurationValues.mainColour
18+
/// Background colour to use when the button's feature is set to `off`.
19+
private let notSelectedBackgroundColour: Color = OSBARCScannerViewConfigurationValues.secondaryColour
20+
/// The colour of the button's outer line.
21+
private let overlayColour: Color = OSBARCScannerViewConfigurationValues.tertiaryColour
22+
/// Considering the button is displayed as a Rounded Rectangle, this is the radius of the button's vertices.
23+
private let cornerRadius: CGFloat = OSBARCScannerViewConfigurationValues.defaultRadius
24+
/// Width of the button's outer line.
25+
private let stroke: CGFloat = OSBARCScannerViewConfigurationValues.defaultLineStroke
26+
27+
/// Calculates the text colour to be used based on the `isOn` value.
28+
private var foregroundColour: Color { isOn ? selectedForegroundColour : notSelectedForegroundColour }
29+
/// Calculates the background colour to be used based on the `isOn` value.
30+
private var backgroundColour: Color { isOn ? selectedBackgroundColour : notSelectedBackgroundColour }
31+
32+
var body: some View {
33+
Button(action: action) {
34+
Text(text)
35+
.padding()
36+
.foregroundStyle(forColour: foregroundColour)
37+
.background(
38+
RoundedRectangle(cornerRadius: cornerRadius)
39+
.foregroundColor(backgroundColour)
40+
)
41+
.if(!isOn) {
42+
$0.overlay(
43+
RoundedRectangle(cornerRadius: cornerRadius)
44+
.stroke(overlayColour, style: .init(lineWidth: stroke))
45+
)
46+
}
47+
}
48+
}
49+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import SwiftUI
2+
3+
/// The scanner's scanning zone.
4+
/// It's composed by the "hole" and the aiming square outside it.
5+
struct OSBARCScanningZone: View {
6+
/// The size of the scanning zone.
7+
let size: CGSize
8+
9+
/// Considering the aim outside is a Rounded Rectangle, this is the radius its vertices.
10+
private let radius: CGFloat = OSBARCScannerViewConfigurationValues.defaultRadius
11+
/// The colour of the aim's line.
12+
private let color: Color = OSBARCScannerViewConfigurationValues.mainColour
13+
/// Width of the aim's line.
14+
private let stroke: CGFloat = OSBARCScannerViewConfigurationValues.defaultLineStroke
15+
/// Length of the aim's line.
16+
private let lineSize: CGFloat = OSBARCScannerViewConfigurationValues.scannerLineSize
17+
18+
var body: some View {
19+
RoundedRectangle(cornerRadius: radius)
20+
.strokeBorder(color, style: .init(
21+
lineWidth: stroke, dash: self.calculateDashes(for: size), dashPhase: self.calculateDashPhase(for: size.height)
22+
))
23+
}
24+
}
25+
26+
// MARK: - Methods to calculate the Aim's Dash values.
27+
private extension OSBARCScanningZone {
28+
/// Calculates the dashes to use the aim. It's a combination of line -> no line -> line -> no line (recursely).
29+
/// - Parameter scannerSize: Size to consider for the dashes.
30+
/// - Returns: The list of dashes to consider for the aim.
31+
func calculateDashes(for scannerSize: CGSize) -> [CGFloat] {
32+
let lineDash = lineSize * 2.0
33+
let heightDash = scannerSize.height - lineSize * 2.0 // squares have equal height and width dashes.
34+
let widthDash = scannerSize.width - lineSize * 2.0
35+
36+
return [lineDash, heightDash, lineDash, widthDash]
37+
}
38+
39+
/// Calculates where the dashes should start.
40+
/// - Parameter scannerHeight: The aim's height value.
41+
/// - Returns: The offset to apply when drawing the dashes.
42+
func calculateDashPhase(for scannerHeight: CGFloat) -> CGFloat { scannerHeight / 2.0 + lineSize + radius }
43+
}

0 commit comments

Comments
 (0)