Skip to content

Commit e34635e

Browse files
committed
Support front and back camera positions
1 parent 515d0cd commit e34635e

File tree

4 files changed

+99
-27
lines changed

4 files changed

+99
-27
lines changed

BarcodeScanner.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
D50BE3E91C9FE7A80000A34C /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = D50BE3E51C9FE7A80000A34C /* [email protected] */; };
1313
D50BE3EA1C9FE7A80000A34C /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = D50BE3E61C9FE7A80000A34C /* [email protected] */; };
1414
D50BE3EB1C9FE7A80000A34C /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = D50BE3E71C9FE7A80000A34C /* [email protected] */; };
15+
D5349DF8201E42D900CD53EA /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = D5349DF7201E42D900CD53EA /* [email protected] */; };
1516
D55281B62016758F00FF3CDD /* HeaderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55281B52016758F00FF3CDD /* HeaderViewController.swift */; };
1617
D55281B8201675D500FF3CDD /* MessageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55281B7201675D500FF3CDD /* MessageViewController.swift */; };
1718
D55281BA2016770800FF3CDD /* CameraViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55281B92016770800FF3CDD /* CameraViewController.swift */; };
@@ -31,6 +32,7 @@
3132
D50BE3E51C9FE7A80000A34C /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
3233
D50BE3E61C9FE7A80000A34C /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
3334
D50BE3E71C9FE7A80000A34C /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
35+
D5349DF7201E42D900CD53EA /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
3436
D55281B52016758F00FF3CDD /* HeaderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderViewController.swift; sourceTree = "<group>"; };
3537
D55281B7201675D500FF3CDD /* MessageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageViewController.swift; sourceTree = "<group>"; };
3638
D55281B92016770800FF3CDD /* CameraViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraViewController.swift; sourceTree = "<group>"; };
@@ -60,6 +62,7 @@
6062
D50BE3E31C9FE7A80000A34C /* Images */ = {
6163
isa = PBXGroup;
6264
children = (
65+
D5349DF7201E42D900CD53EA /* [email protected] */,
6366
D50BE3E51C9FE7A80000A34C /* [email protected] */,
6467
D50BE3E61C9FE7A80000A34C /* [email protected] */,
6568
D50BE3E71C9FE7A80000A34C /* [email protected] */,
@@ -215,6 +218,7 @@
215218
buildActionMask = 2147483647;
216219
files = (
217220
D50BE3E91C9FE7A80000A34C /* [email protected] in Resources */,
221+
D5349DF8201E42D900CD53EA /* [email protected] in Resources */,
218222
D50BE3EB1C9FE7A80000A34C /* [email protected] in Resources */,
219223
D50BE3EA1C9FE7A80000A34C /* [email protected] in Resources */,
220224
);

Images/[email protected]

9.13 KB
Loading

Sources/Controllers/BarcodeScannerViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ private extension BarcodeScannerViewController {
220220
NSLayoutConstraint.activate(
221221
cameraView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
222222
cameraView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
223-
cameraView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
223+
cameraView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -75)
224224
)
225225

226226
if navigationController != nil {

Sources/Controllers/CameraViewController.swift

Lines changed: 94 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public final class CameraViewController: UIViewController {
3030
public private(set) lazy var flashButton: UIButton = .init(type: .custom)
3131
/// Button that opens settings to allow camera usage.
3232
public private(set) lazy var settingsButton: UIButton = self.makeSettingsButton()
33+
// Button to switch between front and back camera.
34+
public private(set) lazy var cameraButton: UIButton = self.makeCameraButton()
3335

3436
// Constraints for the focus view when it gets smaller in size.
3537
private var regularFocusViewConstraints = [NSLayoutConstraint]()
@@ -41,7 +43,7 @@ public final class CameraViewController: UIViewController {
4143
/// Video preview layer.
4244
private var videoPreviewLayer: AVCaptureVideoPreviewLayer?
4345
/// Video capture device. This may be nil when running in Simulator.
44-
private lazy var captureDevice: AVCaptureDevice! = AVCaptureDevice.default(for: .video)
46+
private var captureDevice: AVCaptureDevice?
4547
/// Capture session.
4648
private lazy var captureSession: AVCaptureSession = AVCaptureSession()
4749
// Service used to check authorization status of the capture device
@@ -62,6 +64,14 @@ public final class CameraViewController: UIViewController {
6264
}
6365
}
6466

67+
private var frontCameraDevice: AVCaptureDevice? {
68+
return AVCaptureDevice.devices(for: .video).first(where: { $0.position == .front })
69+
}
70+
71+
private var backCameraDevice: AVCaptureDevice? {
72+
return AVCaptureDevice.default(for: .video)
73+
}
74+
6575
// MARK: - Initialization
6676

6777
deinit {
@@ -73,31 +83,21 @@ public final class CameraViewController: UIViewController {
7383
public override func viewDidLoad() {
7484
super.viewDidLoad()
7585
view.backgroundColor = .black
76-
7786
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession)
78-
videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
87+
videoPreviewLayer?.videoGravity = .resizeAspectFill
7988

8089
guard let videoPreviewLayer = videoPreviewLayer else {
8190
return
8291
}
8392

8493
view.layer.addSublayer(videoPreviewLayer)
85-
view.addSubviews(settingsButton, flashButton, focusView)
94+
view.addSubviews(settingsButton, flashButton, focusView, cameraButton)
8695

8796
torchMode = .off
8897
focusView.isHidden = true
8998
setupCamera()
9099
setupConstraints()
91-
92-
flashButton.addTarget(self, action: #selector(flashButtonDidPress), for: .touchUpInside)
93-
settingsButton.addTarget(self, action: #selector(settingsButtonDidPress), for: .touchUpInside)
94-
95-
NotificationCenter.default.addObserver(
96-
self,
97-
selector: #selector(appWillEnterForeground),
98-
name: NSNotification.Name.UIApplicationWillEnterForeground,
99-
object: nil
100-
)
100+
setupActions()
101101
}
102102

103103
public override func viewDidAppear(_ animated: Bool) {
@@ -149,19 +149,49 @@ public final class CameraViewController: UIViewController {
149149

150150
// MARK: - Actions
151151

152+
private func setupActions() {
153+
flashButton.addTarget(
154+
self,
155+
action: #selector(handleFlashButtonTap),
156+
for: .touchUpInside
157+
)
158+
settingsButton.addTarget(
159+
self,
160+
action: #selector(handleSettingsButtonTap),
161+
for: .touchUpInside
162+
)
163+
cameraButton.addTarget(
164+
self,
165+
action: #selector(handleCameraButtonTap),
166+
for: .touchUpInside
167+
)
168+
169+
NotificationCenter.default.addObserver(
170+
self,
171+
selector: #selector(appWillEnterForeground),
172+
name: NSNotification.Name.UIApplicationWillEnterForeground,
173+
object: nil
174+
)
175+
}
176+
152177
/// `UIApplicationWillEnterForegroundNotification` action.
153178
@objc private func appWillEnterForeground() {
154179
torchMode = .off
155180
animateFocusView()
156181
}
157182

158183
/// Opens setting to allow camera usage.
159-
@objc private func settingsButtonDidPress() {
184+
@objc private func handleSettingsButtonTap() {
160185
delegate?.cameraViewControllerDidTapSettingsButton(self)
161186
}
162187

188+
/// Swaps camera position.
189+
@objc private func handleCameraButtonTap() {
190+
swapCamera()
191+
}
192+
163193
/// Sets the next torch mode.
164-
@objc private func flashButtonDidPress() {
194+
@objc private func handleFlashButtonTap() {
165195
torchMode = torchMode.next
166196
}
167197

@@ -179,7 +209,8 @@ public final class CameraViewController: UIViewController {
179209
}
180210

181211
if error == nil {
182-
strongSelf.setupSession()
212+
strongSelf.setupSessionInput(for: .back)
213+
strongSelf.setupSessionOutput()
183214
strongSelf.delegate?.cameraViewControllerDidSetupCaptureSession(strongSelf)
184215
} else {
185216
strongSelf.delegate?.cameraViewControllerDidFailToSetupCaptureSession(strongSelf)
@@ -188,18 +219,32 @@ public final class CameraViewController: UIViewController {
188219
}
189220

190221
/// Sets up capture input, output and session.
191-
private func setupSession() {
192-
guard let captureDevice = captureDevice, !isSimulatorRunning else {
222+
private func setupSessionInput(for position: AVCaptureDevice.Position) {
223+
guard !isSimulatorRunning else {
224+
return
225+
}
226+
227+
guard let device = position == .front ? frontCameraDevice : backCameraDevice else {
193228
return
194229
}
195230

196231
do {
197-
let input = try AVCaptureDeviceInput(device: captureDevice)
198-
captureSession.addInput(input)
232+
let newInput = try AVCaptureDeviceInput(device: device)
233+
captureDevice = device
234+
// Swap capture device inputs
235+
captureSession.beginConfiguration()
236+
if let currentInput = captureSession.inputs.first as? AVCaptureDeviceInput {
237+
captureSession.removeInput(currentInput)
238+
}
239+
captureSession.addInput(newInput)
240+
captureSession.commitConfiguration()
199241
} catch {
200242
delegate?.cameraViewController(self, didReceiveError: error)
243+
return
201244
}
245+
}
202246

247+
private func setupSessionOutput() {
203248
let output = AVCaptureMetadataOutput()
204249
captureSession.addOutput(output)
205250
output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
@@ -209,6 +254,14 @@ public final class CameraViewController: UIViewController {
209254
view.setNeedsLayout()
210255
}
211256

257+
/// Switch front/back camera.
258+
private func swapCamera() {
259+
guard let input = captureSession.inputs.first as? AVCaptureDeviceInput else {
260+
return
261+
}
262+
setupSessionInput(for: input.device.position == .back ? .front : .back)
263+
}
264+
212265
// MARK: - Animations
213266

214267
/// Performs focus view animation.
@@ -248,25 +301,34 @@ private extension CameraViewController {
248301
flashButton.trailingAnchor.constraint(
249302
equalTo: view.safeAreaLayoutGuide.trailingAnchor,
250303
constant: -13
304+
),
305+
cameraButton.bottomAnchor.constraint(
306+
equalTo: view.safeAreaLayoutGuide.bottomAnchor,
307+
constant: -30
251308
)
252309
)
253310
} else {
254311
NSLayoutConstraint.activate(
255312
flashButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 30),
256-
flashButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -13)
313+
flashButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -13),
314+
cameraButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -30)
257315
)
258316
}
259317

260-
let flashButtonSize: CGFloat = 37
318+
let imageButtonSize: CGFloat = 37
261319

262320
NSLayoutConstraint.activate(
263-
flashButton.widthAnchor.constraint(equalToConstant: flashButtonSize),
264-
flashButton.heightAnchor.constraint(equalToConstant: flashButtonSize),
321+
flashButton.widthAnchor.constraint(equalToConstant: imageButtonSize),
322+
flashButton.heightAnchor.constraint(equalToConstant: imageButtonSize),
265323

266324
settingsButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
267325
settingsButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
268326
settingsButton.widthAnchor.constraint(equalToConstant: 150),
269-
settingsButton.heightAnchor.constraint(equalToConstant: 50)
327+
settingsButton.heightAnchor.constraint(equalToConstant: 50),
328+
329+
cameraButton.widthAnchor.constraint(equalToConstant: 48),
330+
cameraButton.heightAnchor.constraint(equalToConstant: 48),
331+
cameraButton.trailingAnchor.constraint(equalTo: flashButton.trailingAnchor)
270332
)
271333

272334
setupFocusViewConstraints()
@@ -345,6 +407,12 @@ private extension CameraViewController {
345407
button.sizeToFit()
346408
return button
347409
}
410+
411+
func makeCameraButton() -> UIButton {
412+
let button = UIButton(type: .custom)
413+
button.setImage(imageNamed("cameraRotate"), for: UIControlState())
414+
return button
415+
}
348416
}
349417

350418
// MARK: - AVCaptureMetadataOutputObjectsDelegate

0 commit comments

Comments
 (0)