@@ -19,6 +19,11 @@ public final class CameraViewController: UIViewController {
1919
2020 /// Focus view type.
2121 public var barCodeFocusViewType : FocusViewType = . animated
22+ public var showsCameraButton : Bool = false {
23+ didSet {
24+ cameraButton. isHidden = showsCameraButton
25+ }
26+ }
2227 /// `AVCaptureMetadataOutput` metadata object types.
2328 var metadata = [ AVMetadataObject . ObjectType] ( )
2429
@@ -30,6 +35,8 @@ public final class CameraViewController: UIViewController {
3035 public private( set) lazy var flashButton : UIButton = . init( type: . custom)
3136 /// Button that opens settings to allow camera usage.
3237 public private( set) lazy var settingsButton : UIButton = self . makeSettingsButton ( )
38+ // Button to switch between front and back camera.
39+ public private( set) lazy var cameraButton : UIButton = self . makeCameraButton ( )
3340
3441 // Constraints for the focus view when it gets smaller in size.
3542 private var regularFocusViewConstraints = [ NSLayoutConstraint] ( )
@@ -41,7 +48,7 @@ public final class CameraViewController: UIViewController {
4148 /// Video preview layer.
4249 private var videoPreviewLayer : AVCaptureVideoPreviewLayer ?
4350 /// Video capture device. This may be nil when running in Simulator.
44- private lazy var captureDevice : AVCaptureDevice ! = AVCaptureDevice . default ( for : . video )
51+ private var captureDevice : AVCaptureDevice ?
4552 /// Capture session.
4653 private lazy var captureSession : AVCaptureSession = AVCaptureSession ( )
4754 // Service used to check authorization status of the capture device
@@ -62,6 +69,14 @@ public final class CameraViewController: UIViewController {
6269 }
6370 }
6471
72+ private var frontCameraDevice : AVCaptureDevice ? {
73+ return AVCaptureDevice . devices ( for: . video) . first ( where: { $0. position == . front } )
74+ }
75+
76+ private var backCameraDevice : AVCaptureDevice ? {
77+ return AVCaptureDevice . default ( for: . video)
78+ }
79+
6580 // MARK: - Initialization
6681
6782 deinit {
@@ -73,31 +88,21 @@ public final class CameraViewController: UIViewController {
7388 public override func viewDidLoad( ) {
7489 super. viewDidLoad ( )
7590 view. backgroundColor = . black
76-
7791 videoPreviewLayer = AVCaptureVideoPreviewLayer ( session: self . captureSession)
78- videoPreviewLayer? . videoGravity = AVLayerVideoGravity . resizeAspectFill
92+ videoPreviewLayer? . videoGravity = . resizeAspectFill
7993
8094 guard let videoPreviewLayer = videoPreviewLayer else {
8195 return
8296 }
8397
8498 view. layer. addSublayer ( videoPreviewLayer)
85- view. addSubviews ( settingsButton, flashButton, focusView)
99+ view. addSubviews ( settingsButton, flashButton, focusView, cameraButton )
86100
87101 torchMode = . off
88102 focusView. isHidden = true
89103 setupCamera ( )
90104 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- )
105+ setupActions ( )
101106 }
102107
103108 public override func viewDidAppear( _ animated: Bool ) {
@@ -149,19 +154,49 @@ public final class CameraViewController: UIViewController {
149154
150155 // MARK: - Actions
151156
157+ private func setupActions( ) {
158+ flashButton. addTarget (
159+ self ,
160+ action: #selector( handleFlashButtonTap) ,
161+ for: . touchUpInside
162+ )
163+ settingsButton. addTarget (
164+ self ,
165+ action: #selector( handleSettingsButtonTap) ,
166+ for: . touchUpInside
167+ )
168+ cameraButton. addTarget (
169+ self ,
170+ action: #selector( handleCameraButtonTap) ,
171+ for: . touchUpInside
172+ )
173+
174+ NotificationCenter . default. addObserver (
175+ self ,
176+ selector: #selector( appWillEnterForeground) ,
177+ name: NSNotification . Name. UIApplicationWillEnterForeground,
178+ object: nil
179+ )
180+ }
181+
152182 /// `UIApplicationWillEnterForegroundNotification` action.
153183 @objc private func appWillEnterForeground( ) {
154184 torchMode = . off
155185 animateFocusView ( )
156186 }
157187
158188 /// Opens setting to allow camera usage.
159- @objc private func settingsButtonDidPress ( ) {
189+ @objc private func handleSettingsButtonTap ( ) {
160190 delegate? . cameraViewControllerDidTapSettingsButton ( self )
161191 }
162192
193+ /// Swaps camera position.
194+ @objc private func handleCameraButtonTap( ) {
195+ swapCamera ( )
196+ }
197+
163198 /// Sets the next torch mode.
164- @objc private func flashButtonDidPress ( ) {
199+ @objc private func handleFlashButtonTap ( ) {
165200 torchMode = torchMode. next
166201 }
167202
@@ -179,7 +214,8 @@ public final class CameraViewController: UIViewController {
179214 }
180215
181216 if error == nil {
182- strongSelf. setupSession ( )
217+ strongSelf. setupSessionInput ( for: . back)
218+ strongSelf. setupSessionOutput ( )
183219 strongSelf. delegate? . cameraViewControllerDidSetupCaptureSession ( strongSelf)
184220 } else {
185221 strongSelf. delegate? . cameraViewControllerDidFailToSetupCaptureSession ( strongSelf)
@@ -188,16 +224,34 @@ public final class CameraViewController: UIViewController {
188224 }
189225
190226 /// Sets up capture input, output and session.
191- private func setupSession( ) {
192- guard let captureDevice = captureDevice, !isSimulatorRunning else {
227+ private func setupSessionInput( for position: AVCaptureDevice . Position ) {
228+ guard !isSimulatorRunning else {
229+ return
230+ }
231+
232+ guard let device = position == . front ? frontCameraDevice : backCameraDevice else {
193233 return
194234 }
195235
196236 do {
197- let input = try AVCaptureDeviceInput ( device: captureDevice)
198- captureSession. addInput ( input)
237+ let newInput = try AVCaptureDeviceInput ( device: device)
238+ captureDevice = device
239+ // Swap capture device inputs
240+ captureSession. beginConfiguration ( )
241+ if let currentInput = captureSession. inputs. first as? AVCaptureDeviceInput {
242+ captureSession. removeInput ( currentInput)
243+ }
244+ captureSession. addInput ( newInput)
245+ captureSession. commitConfiguration ( )
199246 } catch {
200247 delegate? . cameraViewController ( self , didReceiveError: error)
248+ return
249+ }
250+ }
251+
252+ private func setupSessionOutput( ) {
253+ guard !isSimulatorRunning else {
254+ return
201255 }
202256
203257 let output = AVCaptureMetadataOutput ( )
@@ -209,6 +263,14 @@ public final class CameraViewController: UIViewController {
209263 view. setNeedsLayout ( )
210264 }
211265
266+ /// Switch front/back camera.
267+ private func swapCamera( ) {
268+ guard let input = captureSession. inputs. first as? AVCaptureDeviceInput else {
269+ return
270+ }
271+ setupSessionInput ( for: input. device. position == . back ? . front : . back)
272+ }
273+
212274 // MARK: - Animations
213275
214276 /// Performs focus view animation.
@@ -248,25 +310,34 @@ private extension CameraViewController {
248310 flashButton. trailingAnchor. constraint (
249311 equalTo: view. safeAreaLayoutGuide. trailingAnchor,
250312 constant: - 13
313+ ) ,
314+ cameraButton. bottomAnchor. constraint (
315+ equalTo: view. safeAreaLayoutGuide. bottomAnchor,
316+ constant: - 30
251317 )
252318 )
253319 } else {
254320 NSLayoutConstraint . activate (
255321 flashButton. topAnchor. constraint ( equalTo: view. topAnchor, constant: 30 ) ,
256- flashButton. trailingAnchor. constraint ( equalTo: view. trailingAnchor, constant: - 13 )
322+ flashButton. trailingAnchor. constraint ( equalTo: view. trailingAnchor, constant: - 13 ) ,
323+ cameraButton. bottomAnchor. constraint ( equalTo: view. bottomAnchor, constant: - 30 )
257324 )
258325 }
259326
260- let flashButtonSize : CGFloat = 37
327+ let imageButtonSize : CGFloat = 37
261328
262329 NSLayoutConstraint . activate (
263- flashButton. widthAnchor. constraint ( equalToConstant: flashButtonSize ) ,
264- flashButton. heightAnchor. constraint ( equalToConstant: flashButtonSize ) ,
330+ flashButton. widthAnchor. constraint ( equalToConstant: imageButtonSize ) ,
331+ flashButton. heightAnchor. constraint ( equalToConstant: imageButtonSize ) ,
265332
266333 settingsButton. centerXAnchor. constraint ( equalTo: view. centerXAnchor) ,
267334 settingsButton. centerYAnchor. constraint ( equalTo: view. centerYAnchor) ,
268335 settingsButton. widthAnchor. constraint ( equalToConstant: 150 ) ,
269- settingsButton. heightAnchor. constraint ( equalToConstant: 50 )
336+ settingsButton. heightAnchor. constraint ( equalToConstant: 50 ) ,
337+
338+ cameraButton. widthAnchor. constraint ( equalToConstant: 48 ) ,
339+ cameraButton. heightAnchor. constraint ( equalToConstant: 48 ) ,
340+ cameraButton. trailingAnchor. constraint ( equalTo: flashButton. trailingAnchor)
270341 )
271342
272343 setupFocusViewConstraints ( )
@@ -345,6 +416,13 @@ private extension CameraViewController {
345416 button. sizeToFit ( )
346417 return button
347418 }
419+
420+ func makeCameraButton( ) -> UIButton {
421+ let button = UIButton ( type: . custom)
422+ button. setImage ( imageNamed ( " cameraRotate " ) , for: UIControlState ( ) )
423+ button. isHidden = showsCameraButton
424+ return button
425+ }
348426}
349427
350428// MARK: - AVCaptureMetadataOutputObjectsDelegate
0 commit comments