@@ -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