Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 0dbca34

Browse files
committed
Fix Orientation & Animation
- Fixes an issue where orientation may be incorrect on iOS - Fixes some documentation issues - Cleans up resize/rotate animation
1 parent 71b67e8 commit 0dbca34

File tree

4 files changed

+80
-35
lines changed

4 files changed

+80
-35
lines changed

Sources/ScanKit/NSCodeScanner.swift renamed to Sources/ScanKit/NSScanKitPreview.swift

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// ScanKit.swift
2+
// NSScanKitPreview.swift
33
// SwiftUI ScanKit
44
//
55
// Copyright © 2023 Shawn Davis
@@ -35,20 +35,20 @@ fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "dev.
3535
public struct ScanKitPreview: NSViewControllerRepresentable {
3636
var camera: ScanKitCamera
3737

38-
public init(camera: ScanKitCamera, startScanningOnLoad: Bool = true) {
38+
public init(camera: ScanKitCamera) {
3939
self.camera = camera
4040
}
4141

42-
public func makeNSViewController(context: Context) -> NSCodeScanner {
43-
return NSCodeScanner(camera: camera)
42+
public func makeNSViewController(context: Context) -> NSScanKitPreview {
43+
return NSScanKitPreview(camera: camera)
4444
}
4545

46-
public func updateNSViewController(_ nsViewController: NSCodeScanner, context: Context) {
47-
// do nothing
46+
public func updateNSViewController(_ nsViewController: NSScanKitPreview, context: Context) {
47+
// Do nothing
4848
}
4949
}
5050

51-
public class NSCodeScanner: NSViewController, AVCaptureVideoDataOutputSampleBufferDelegate {
51+
public class NSScanKitPreview: NSViewController, AVCaptureVideoDataOutputSampleBufferDelegate {
5252

5353
weak var camera: ScanKitCamera?
5454

@@ -86,7 +86,10 @@ public class NSCodeScanner: NSViewController, AVCaptureVideoDataOutputSampleBuff
8686

8787
public override func viewWillLayout() {
8888
super.viewWillLayout()
89+
CATransaction.begin()
90+
CATransaction.setDisableActions(true)
8991
self.camera?.previewLayer.frame = self.view.bounds
92+
CATransaction.commit()
9093
}
9194

9295
private func addPreviewLayer() {

Sources/ScanKit/ScanKit.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ public struct ScannerView: View {
5858
/// - isScanning: Whether or not to scan for the selected symbology.
5959
/// When `false` the preview will continue but frames will not be processed for
6060
/// instances of the selected symbology.
61-
/// - completion: A completion handler that returns a `Result` of either `String` when information has been found and can be decoded or `ScanKitError` when an error occurs.
61+
/// - completion: A completion handler that returns a `Result` of either `String` when information has been found
62+
/// and can be decoded or `ScanKitError` when an error occurs.
6263
public init(for symbology: VNBarcodeSymbology,
6364
showViewfinder: Bool = true,
6465
isScanning: Binding<Bool>,

Sources/ScanKit/ScanKitCamera.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,17 @@ public class ScanKitCamera: NSObject, ObservableObject, AVCaptureVideoDataOutput
414414
/// You may need to call this function again if you wish to restart the scanning process.
415415
///
416416
/// ```
417-
/// ADD EXAMPLE!!!
417+
/// @StateObject var camera = ScanKitCamera()
418+
/// ...
419+
/// .task {
420+
/// do {
421+
/// for try await result in camera.resultsStream {
422+
/// print(result)
423+
/// }
424+
/// } catch let error {
425+
/// print(error)
426+
/// }
427+
/// }
418428
/// ```
419429
///
420430
/// It is possible to recieve an empty `String` if the code does not contain any data. Be prepared to handle that.

Sources/ScanKit/UICodeScanner.swift renamed to Sources/ScanKit/UIScanKitPreview.swift

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// ScanKit.swift
2+
// UIScanKitPreview.swift
33
// SwiftUI ScanKit
44
//
55
// Copyright © 2023 Shawn Davis
@@ -41,26 +41,18 @@ public struct ScanKitPreview: UIViewControllerRepresentable {
4141
self.camera = camera
4242
}
4343

44-
public func makeUIViewController(context: Context) -> UICodeScanner {
45-
return UICodeScanner(camera: camera)
44+
public func makeUIViewController(context: Context) -> UIScanKitPreview {
45+
return UIScanKitPreview(camera: camera)
4646
}
4747

48-
public func updateUIViewController(_ uiViewController: UICodeScanner, context: Context) {
49-
// Do nothing!
48+
public func updateUIViewController(_ uiViewController: UIScanKitPreview, context: Context) {
49+
// Do nothing
5050
}
5151
}
5252

53-
public class UICodeScanner: UIViewController {
53+
public class UIScanKitPreview: UIViewController {
5454
weak var camera: ScanKitCamera?
5555

56-
private var deviceOrientation: UIDeviceOrientation {
57-
var orientation = UIDevice.current.orientation
58-
if orientation == UIDeviceOrientation.unknown {
59-
orientation = .portrait
60-
}
61-
return orientation
62-
}
63-
6456
init(camera: ScanKitCamera) {
6557
self.camera = camera
6658
super.init(nibName: nil, bundle: nil)
@@ -70,20 +62,28 @@ public class UICodeScanner: UIViewController {
7062
fatalError("init(coder:) has not been implemented")
7163
}
7264

73-
public override func viewDidLoad() {
74-
super.viewDidLoad()
65+
public override func viewWillAppear(_ animated: Bool) {
66+
super.viewWillAppear(animated)
67+
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
68+
69+
CATransaction.begin()
70+
CATransaction.setDisableActions(true)
7571
addPreviewLayer()
76-
}
77-
78-
public override func viewDidAppear(_ animated: Bool) {
72+
CATransaction.commit()
73+
7974
// Start the camera
80-
Task.detached(priority: .userInitiated) { [weak self] in
75+
Task(priority: .userInitiated) { [weak self] in
8176
await self?.camera?.start()
8277
}
8378
}
8479

80+
public override func viewDidAppear(_ animated: Bool) {
81+
super.viewDidAppear(animated)
82+
}
83+
8584
public override func viewWillDisappear(_ animated: Bool) {
8685
super.viewWillDisappear(animated)
86+
UIDevice.current.endGeneratingDeviceOrientationNotifications()
8787
camera?.stop()
8888
}
8989

@@ -93,25 +93,56 @@ public class UICodeScanner: UIViewController {
9393
// Update video orientation on rotate
9494
guard let connection = camera?.previewLayer.connection else { return }
9595
if connection.isVideoOrientationSupported {
96-
connection.videoOrientation = videoOrientation(for: deviceOrientation)
96+
print("updating orientation...")
97+
Task { @MainActor in
98+
connection.videoOrientation = appropriateVideoOrientation
99+
}
97100
}
98101

102+
CATransaction.begin()
103+
CATransaction.setDisableActions(true)
99104
self.camera?.previewLayer.frame = self.view.bounds
105+
CATransaction.commit()
100106
}
101107

102108
private func addPreviewLayer() {
103109
if let previewLayer = self.camera?.previewLayer {
110+
previewLayer.frame = self.view.bounds
104111
self.view.layer.addSublayer(previewLayer)
105112
}
106113
}
107114

115+
private var appropriateVideoOrientation: AVCaptureVideoOrientation {
116+
let orientation = UIDevice.current.orientation
117+
118+
// If the device orientation cannot be found, check the UI instead...
119+
if orientation == .unknown {
120+
return videOrientationFromInterfaceOrientation(self.preferredInterfaceOrientationForPresentation)
121+
}
122+
return videoOrientationFromDeviceOrientation(orientation)
123+
}
124+
125+
/// Converts a `UIInterfaceOrientation` to an appropriate `AVCaptureVideoOrientation` equivalent.
126+
private func videOrientationFromInterfaceOrientation(_ interfaceOrientation: UIInterfaceOrientation) -> AVCaptureVideoOrientation {
127+
switch interfaceOrientation {
128+
case .unknown: return .portrait
129+
case .portrait: return .portrait
130+
case .portraitUpsideDown: return .portrait
131+
case .landscapeLeft: return .landscapeLeft
132+
case .landscapeRight: return .landscapeRight
133+
default: return .portrait
134+
}
135+
}
136+
108137
/// Converts a `UIDeviceOrientation` to an appropriate `AVCaptureVideoOrientation` equivalent.
109-
private func videoOrientation(for deviceOrientation: UIDeviceOrientation) -> AVCaptureVideoOrientation {
138+
private func videoOrientationFromDeviceOrientation(_ deviceOrientation: UIDeviceOrientation) -> AVCaptureVideoOrientation {
110139
switch deviceOrientation {
111-
case .portrait: return AVCaptureVideoOrientation.portrait
112-
case .portraitUpsideDown: return AVCaptureVideoOrientation.portraitUpsideDown
113-
case .landscapeLeft: return AVCaptureVideoOrientation.landscapeRight
114-
case .landscapeRight: return AVCaptureVideoOrientation.landscapeLeft
140+
case .portrait: return .portrait
141+
case .portraitUpsideDown: return .portraitUpsideDown
142+
case .landscapeLeft: return .landscapeRight
143+
case .landscapeRight: return .landscapeLeft
144+
case .faceUp: return .landscapeRight
145+
case .faceDown: return .landscapeRight
115146
default: return .portrait
116147
}
117148
}

0 commit comments

Comments
 (0)