CameraViewController.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. import UIKit
  2. import AVFoundation
  3. /// Delegate to handle camera setup and video capturing.
  4. protocol CameraViewControllerDelegate: class {
  5. func cameraViewControllerDidSetupCaptureSession(_ controller: CameraViewController)
  6. func cameraViewControllerDidFailToSetupCaptureSession(_ controller: CameraViewController)
  7. func cameraViewController(_ controller: CameraViewController, didReceiveError error: Error)
  8. func cameraViewControllerDidTapSettingsButton(_ controller: CameraViewController)
  9. func cameraViewController(
  10. _ controller: CameraViewController,
  11. didOutput metadataObjects: [AVMetadataObject]
  12. )
  13. }
  14. /// View controller responsible for camera controls and video capturing.
  15. public final class CameraViewController: UIViewController {
  16. weak var delegate: CameraViewControllerDelegate?
  17. /// Focus view type.
  18. public var barCodeFocusViewType: FocusViewType = .animated
  19. public var showsCameraButton: Bool = false {
  20. didSet {
  21. cameraButton.isHidden = showsCameraButton
  22. }
  23. }
  24. /// `AVCaptureMetadataOutput` metadata object types.
  25. var metadata = [AVMetadataObject.ObjectType]()
  26. // MARK: - UI proterties
  27. /// Animated focus view.
  28. public private(set) lazy var focusView: UIView = self.makeFocusView()
  29. /// Button to change torch mode.
  30. public private(set) lazy var flashButton: UIButton = .init(type: .custom)
  31. /// Button that opens settings to allow camera usage.
  32. 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()
  35. // Constraints for the focus view when it gets smaller in size.
  36. private var regularFocusViewConstraints = [NSLayoutConstraint]()
  37. // Constraints for the focus view when it gets bigger in size.
  38. private var animatedFocusViewConstraints = [NSLayoutConstraint]()
  39. // MARK: - Video
  40. /// Video preview layer.
  41. private var videoPreviewLayer: AVCaptureVideoPreviewLayer?
  42. /// Video capture device. This may be nil when running in Simulator.
  43. private var captureDevice: AVCaptureDevice?
  44. /// Capture session.
  45. private lazy var captureSession: AVCaptureSession = AVCaptureSession()
  46. // Service used to check authorization status of the capture device
  47. private let permissionService = VideoPermissionService()
  48. /// The current torch mode on the capture device.
  49. private var torchMode: TorchMode = .off {
  50. didSet {
  51. guard let captureDevice = captureDevice, captureDevice.hasFlash else { return }
  52. guard captureDevice.isTorchModeSupported(torchMode.captureTorchMode) else { return }
  53. do {
  54. try captureDevice.lockForConfiguration()
  55. captureDevice.torchMode = torchMode.captureTorchMode
  56. captureDevice.unlockForConfiguration()
  57. } catch {}
  58. flashButton.setImage(torchMode.image, for: UIControlState())
  59. }
  60. }
  61. private var frontCameraDevice: AVCaptureDevice? {
  62. return AVCaptureDevice.devices(for: .video).first(where: { $0.position == .front })
  63. }
  64. private var backCameraDevice: AVCaptureDevice? {
  65. return AVCaptureDevice.default(for: .video)
  66. }
  67. // MARK: - Initialization
  68. deinit {
  69. stopCapturing()
  70. NotificationCenter.default.removeObserver(self)
  71. }
  72. // MARK: - View lifecycle
  73. public override func viewDidLoad() {
  74. super.viewDidLoad()
  75. view.backgroundColor = .black
  76. videoPreviewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession)
  77. videoPreviewLayer?.videoGravity = .resizeAspectFill
  78. guard let videoPreviewLayer = videoPreviewLayer else {
  79. return
  80. }
  81. view.layer.addSublayer(videoPreviewLayer)
  82. view.addSubviews(settingsButton, flashButton, focusView, cameraButton)
  83. torchMode = .off
  84. focusView.isHidden = true
  85. setupCamera()
  86. setupConstraints()
  87. setupActions()
  88. }
  89. public override func viewDidAppear(_ animated: Bool) {
  90. super.viewDidAppear(animated)
  91. setupVideoPreviewLayerOrientation()
  92. animateFocusView()
  93. }
  94. public override func viewWillTransition(to size: CGSize,
  95. with coordinator: UIViewControllerTransitionCoordinator) {
  96. super.viewWillTransition(to: size, with: coordinator)
  97. coordinator.animate(
  98. alongsideTransition: { [weak self] _ in
  99. self?.setupVideoPreviewLayerOrientation()
  100. },
  101. completion: ({ [weak self] _ in
  102. self?.animateFocusView()
  103. }))
  104. }
  105. // MARK: - Video capturing
  106. func startCapturing() {
  107. guard !isSimulatorRunning else {
  108. return
  109. }
  110. torchMode = .off
  111. captureSession.startRunning()
  112. focusView.isHidden = false
  113. flashButton.isHidden = captureDevice?.position == .front
  114. cameraButton.isHidden = !showsCameraButton
  115. }
  116. func stopCapturing() {
  117. guard !isSimulatorRunning else {
  118. return
  119. }
  120. torchMode = .off
  121. captureSession.stopRunning()
  122. focusView.isHidden = true
  123. flashButton.isHidden = true
  124. cameraButton.isHidden = true
  125. }
  126. // MARK: - Actions
  127. private func setupActions() {
  128. flashButton.addTarget(
  129. self,
  130. action: #selector(handleFlashButtonTap),
  131. for: .touchUpInside
  132. )
  133. settingsButton.addTarget(
  134. self,
  135. action: #selector(handleSettingsButtonTap),
  136. for: .touchUpInside
  137. )
  138. cameraButton.addTarget(
  139. self,
  140. action: #selector(handleCameraButtonTap),
  141. for: .touchUpInside
  142. )
  143. NotificationCenter.default.addObserver(
  144. self,
  145. selector: #selector(appWillEnterForeground),
  146. name: NSNotification.Name.UIApplicationWillEnterForeground,
  147. object: nil
  148. )
  149. }
  150. /// `UIApplicationWillEnterForegroundNotification` action.
  151. @objc private func appWillEnterForeground() {
  152. torchMode = .off
  153. animateFocusView()
  154. }
  155. /// Opens setting to allow camera usage.
  156. @objc private func handleSettingsButtonTap() {
  157. delegate?.cameraViewControllerDidTapSettingsButton(self)
  158. }
  159. /// Swaps camera position.
  160. @objc private func handleCameraButtonTap() {
  161. swapCamera()
  162. }
  163. /// Sets the next torch mode.
  164. @objc private func handleFlashButtonTap() {
  165. torchMode = torchMode.next
  166. }
  167. // MARK: - Camera setup
  168. /// Sets up camera and checks for camera permissions.
  169. private func setupCamera() {
  170. permissionService.checkPersmission { [weak self] error in
  171. guard let strongSelf = self else {
  172. return
  173. }
  174. DispatchQueue.main.async { [weak self] in
  175. self?.settingsButton.isHidden = error == nil
  176. }
  177. if error == nil {
  178. strongSelf.setupSessionInput(for: .back)
  179. strongSelf.setupSessionOutput()
  180. strongSelf.delegate?.cameraViewControllerDidSetupCaptureSession(strongSelf)
  181. } else {
  182. strongSelf.delegate?.cameraViewControllerDidFailToSetupCaptureSession(strongSelf)
  183. }
  184. }
  185. }
  186. /// Sets up capture input, output and session.
  187. private func setupSessionInput(for position: AVCaptureDevice.Position) {
  188. guard !isSimulatorRunning else {
  189. return
  190. }
  191. guard let device = position == .front ? frontCameraDevice : backCameraDevice else {
  192. return
  193. }
  194. do {
  195. let newInput = try AVCaptureDeviceInput(device: device)
  196. captureDevice = device
  197. // Swap capture device inputs
  198. captureSession.beginConfiguration()
  199. if let currentInput = captureSession.inputs.first as? AVCaptureDeviceInput {
  200. captureSession.removeInput(currentInput)
  201. }
  202. captureSession.addInput(newInput)
  203. captureSession.commitConfiguration()
  204. flashButton.isHidden = position == .front
  205. } catch {
  206. delegate?.cameraViewController(self, didReceiveError: error)
  207. return
  208. }
  209. }
  210. private func setupSessionOutput() {
  211. guard !isSimulatorRunning else {
  212. return
  213. }
  214. let output = AVCaptureMetadataOutput()
  215. captureSession.addOutput(output)
  216. output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
  217. output.metadataObjectTypes = metadata
  218. videoPreviewLayer?.session = captureSession
  219. view.setNeedsLayout()
  220. }
  221. /// Switch front/back camera.
  222. private func swapCamera() {
  223. guard let input = captureSession.inputs.first as? AVCaptureDeviceInput else {
  224. return
  225. }
  226. setupSessionInput(for: input.device.position == .back ? .front : .back)
  227. }
  228. // MARK: - Animations
  229. /// Performs focus view animation.
  230. private func animateFocusView() {
  231. // Restore to initial state
  232. focusView.layer.removeAllAnimations()
  233. animatedFocusViewConstraints.deactivate()
  234. regularFocusViewConstraints.activate()
  235. view.layoutIfNeeded()
  236. guard barCodeFocusViewType == .animated else {
  237. return
  238. }
  239. regularFocusViewConstraints.deactivate()
  240. animatedFocusViewConstraints.activate()
  241. UIView.animate(
  242. withDuration: 1.0,
  243. delay: 0,
  244. options: [.repeat, .autoreverse, .beginFromCurrentState],
  245. animations: ({ [weak self] in
  246. self?.view.layoutIfNeeded()
  247. }),
  248. completion: nil
  249. )
  250. }
  251. }
  252. // MARK: - Layout
  253. private extension CameraViewController {
  254. func setupConstraints() {
  255. if #available(iOS 11.0, *) {
  256. NSLayoutConstraint.activate(
  257. flashButton.topAnchor.constraint(
  258. equalTo: view.safeAreaLayoutGuide.topAnchor,
  259. constant: 30
  260. ),
  261. flashButton.trailingAnchor.constraint(
  262. equalTo: view.safeAreaLayoutGuide.trailingAnchor,
  263. constant: -13
  264. ),
  265. cameraButton.bottomAnchor.constraint(
  266. equalTo: view.safeAreaLayoutGuide.bottomAnchor,
  267. constant: -30
  268. )
  269. )
  270. } else {
  271. NSLayoutConstraint.activate(
  272. flashButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 30),
  273. flashButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -13),
  274. cameraButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -30)
  275. )
  276. }
  277. let imageButtonSize: CGFloat = 37
  278. NSLayoutConstraint.activate(
  279. flashButton.widthAnchor.constraint(equalToConstant: imageButtonSize),
  280. flashButton.heightAnchor.constraint(equalToConstant: imageButtonSize),
  281. settingsButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
  282. settingsButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
  283. settingsButton.widthAnchor.constraint(equalToConstant: 150),
  284. settingsButton.heightAnchor.constraint(equalToConstant: 50),
  285. cameraButton.widthAnchor.constraint(equalToConstant: 48),
  286. cameraButton.heightAnchor.constraint(equalToConstant: 48),
  287. cameraButton.trailingAnchor.constraint(equalTo: flashButton.trailingAnchor)
  288. )
  289. setupFocusViewConstraints()
  290. }
  291. func setupFocusViewConstraints() {
  292. NSLayoutConstraint.activate(
  293. focusView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
  294. focusView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
  295. )
  296. let focusViewSize = barCodeFocusViewType == .oneDimension
  297. ? CGSize(width: 280, height: 80)
  298. : CGSize(width: 218, height: 150)
  299. regularFocusViewConstraints = [
  300. focusView.widthAnchor.constraint(equalToConstant: focusViewSize.width),
  301. focusView.heightAnchor.constraint(equalToConstant: focusViewSize.height)
  302. ]
  303. animatedFocusViewConstraints = [
  304. focusView.widthAnchor.constraint(equalToConstant: 280),
  305. focusView.heightAnchor.constraint(equalToConstant: 80)
  306. ]
  307. NSLayoutConstraint.activate(regularFocusViewConstraints)
  308. }
  309. func setupVideoPreviewLayerOrientation() {
  310. guard let videoPreviewLayer = videoPreviewLayer else {
  311. return
  312. }
  313. videoPreviewLayer.frame = view.layer.bounds
  314. if let connection = videoPreviewLayer.connection, connection.isVideoOrientationSupported {
  315. switch UIApplication.shared.statusBarOrientation {
  316. case .portrait:
  317. connection.videoOrientation = .portrait
  318. case .landscapeRight:
  319. connection.videoOrientation = .landscapeRight
  320. case .landscapeLeft:
  321. connection.videoOrientation = .landscapeLeft
  322. case .portraitUpsideDown:
  323. connection.videoOrientation = .portraitUpsideDown
  324. default:
  325. connection.videoOrientation = .portrait
  326. }
  327. }
  328. }
  329. }
  330. // MARK: - Subviews factory
  331. private extension CameraViewController {
  332. func makeFocusView() -> UIView {
  333. let view = UIView()
  334. view.layer.borderColor = UIColor.white.cgColor
  335. view.layer.borderWidth = 2
  336. view.layer.cornerRadius = 5
  337. view.layer.shadowColor = UIColor.white.cgColor
  338. view.layer.shadowRadius = 10.0
  339. view.layer.shadowOpacity = 0.9
  340. view.layer.shadowOffset = CGSize.zero
  341. view.layer.masksToBounds = false
  342. return view
  343. }
  344. func makeSettingsButton() -> UIButton {
  345. let button = UIButton(type: .system)
  346. let title = NSAttributedString(
  347. string: localizedString("BUTTON_SETTINGS"),
  348. attributes: [.font: UIFont.boldSystemFont(ofSize: 17), .foregroundColor: UIColor.white]
  349. )
  350. button.setAttributedTitle(title, for: UIControlState())
  351. button.sizeToFit()
  352. return button
  353. }
  354. func makeCameraButton() -> UIButton {
  355. let button = UIButton(type: .custom)
  356. button.setImage(imageNamed("cameraRotate"), for: UIControlState())
  357. button.isHidden = !showsCameraButton
  358. return button
  359. }
  360. }
  361. // MARK: - AVCaptureMetadataOutputObjectsDelegate
  362. extension CameraViewController: AVCaptureMetadataOutputObjectsDelegate {
  363. public func metadataOutput(_ output: AVCaptureMetadataOutput,
  364. didOutput metadataObjects: [AVMetadataObject],
  365. from connection: AVCaptureConnection) {
  366. delegate?.cameraViewController(self, didOutput: metadataObjects)
  367. }
  368. }