Создание кастомной камеры на Swift

В один прекрасный день передо мной встала задача сделать кастомную камеру, скажу вам так это задача не из легких, пришлось познакмиться с разными интересными и не очень вещами. Поэтому если хотите сделать свое приложение уникальным с помошью кастомной камеры, то эта статья для вас. Here we go!!

Начнем с того, какой финальный результат мы получим.

Создание кастомной камеры на Swift

Как видно на скриншоте мы будем иметь кастомную камеру, возможность переходить в галерею и фанарик которым мы будем освещать при необходимости пространство перед нами. Прекрасно помоему)

Пример будет на архитектуре MVC. Начнем с добавления всех вьюшек который нам понадобятся для нашего экрана:

// MARK: - Views private let cameraView = UIView() private let photoImageView = UIImageView() private let takePhotoButton = UIButton() private let cameraFlashButton = UIButton() private let dismissButton = UIButton()

Давайте по порядку:

cameraView — это как раз вьюшка через которую мы будем видеть окно камеры.

photoImageView — это вьюшка отвечает за фото сделанное последний раз в нижнем левом углу на скриншоте, по нажатию на нее будем открывть список всех фото.

takePhotoButton — кнопка которая делает фотографию.

cameraFlashButton — наша кастомная вспышка)

dismissButton — скрывает экран, так у нас пример модального экрана.

Далее сделаем настройки наших вьюшек:

// MARK: - Private functions private func setupCameraView() { guard let captureSession = viewModel?.captureSession else { return } videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) videoPreviewLayer?.videoGravity = .resizeAspectFill videoPreviewLayer?.connection?.videoOrientation = .portrait guard let videoPreviewLayer = videoPreviewLayer else { return } cameraView.layer.addSublayer(videoPreviewLayer) view.addSubview(cameraView) } private func setupTakePhotoButton() { takePhotoButton.backgroundColor = .clear takePhotoButton.setBackgroundImage(#imageLiteral(resourceName: "ic_make_photo_button"), for: .normal) takePhotoButton.addTarget(self, action: #selector(didTapTakePhoto), for: .touchUpInside) view.addSubview(takePhotoButton) } private func setupPhotoImageView() { photoImageView.layer.cornerRadius = Constants.photoImageViewCornerRadius photoImageView.layer.borderColor = UIColor.white.cgColor photoImageView.layer.masksToBounds = true photoImageView.layer.borderWidth = 1 let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapPhotoImageView)) photoImageView.isUserInteractionEnabled = true photoImageView.addGestureRecognizer(tapGestureRecognizer) view.addSubview(photoImageView) } private func setupCameraFlashButton() { cameraFlashButton.backgroundColor = .clear cameraFlashButton.setBackgroundImage(#imageLiteral(resourceName: "ic_camera_flash_button"), for: .normal) cameraFlashButton.addTarget(self, action: #selector(didTapFlashPhoto), for: .touchUpInside) view.addSubview(cameraFlashButton) } private func setupDismissButton() { dismissButton.backgroundColor = .clear dismissButton.setBackgroundImage(#imageLiteral(resourceName: "ic_close_photo_button"), for: .normal) view.addSubview(dismissButton) }

И настроим расположение:

private enum Constants { static let takePhotoButtonSize: CGSize = CGSize(width: 75, height: 75) static let cameraFlashButtonSize: CGSize = CGSize(width: 40, height: 40) static let cameraFlashButtonInsets: UIEdgeInsets = .init(top: 60, left: 20, bottom: 0, right: 20) static let photoImageViewSize: CGSize = CGSize(width: 45, height: 60) static let photoImageViewInsets: UIEdgeInsets = .init(top: 42, left: 20, bottom: 42, right: 20) static let takePhotoButtonYOffset: CGFloat = 80 static let photoImageViewCornerRadius: CGFloat = 6 } private func layoutCameraView() { cameraView.frame = view.bounds } private func layoutTakePhotoButton() { takePhotoButton.frame.size = Constants.takePhotoButtonSize takePhotoButton.center = CGPoint(x: view.center.x, y: view.bounds.height - view.safeAreaInsets.bottom - Constants.takePhotoButtonYOffset - Constants.takePhotoButtonSize.height / 2) } private func layoutCameraFlashButton() { cameraFlashButton.frame.size = Constants.cameraFlashButtonSize cameraFlashButton.center = CGPoint(x: view.bounds.width - Constants.cameraFlashButtonInsets.right - Constants.cameraFlashButtonSize.width / 2, y: Constants.cameraFlashButtonInsets.top + Constants.cameraFlashButtonSize.height / 2) } private func layoutDismissButton() { dismissButton.frame.size = Constants.cameraFlashButtonSize dismissButton.center = CGPoint(x: Constants.cameraFlashButtonInsets.right + Constants.cameraFlashButtonSize.width / 2, y: Constants.cameraFlashButtonInsets.top + Constants.cameraFlashButtonSize.height / 2) } private func layoutPhotoImageView() { photoImageView.frame.size = Constants.photoImageViewSize photoImageView.center = CGPoint(x: Constants.photoImageViewInsets.left + Constants.cameraFlashButtonSize.width / 2, y: view.bounds.height - view.safeAreaInsets.bottom - Constants.photoImageViewInsets.bottom - Constants.photoImageViewSize.height / 2) }

Переходим непросредственно, к созданию главных объектов и переменных которые нам будут необходимы:

// MARK: - Private properties private var videoPreviewLayer: AVCaptureVideoPreviewLayer? private var captureSession: AVCaptureSession = AVCaptureSession() private var stillImageOutput: AVCapturePhotoOutput = AVCapturePhotoOutput() private let cameraSessionQueue = DispatchQueue(label: "Camera.Session") private var isCameraFlashEnabled = false

AVCaptureVideoPreviewLayer - Слой Core Animation, отображающий видео с камеры.

AVCaptureSession — Объект, который настраивает поведение захвата и координирует поток данных от устройств.

AVCapturePhotoOutput — предоставляет интерфейс для рабочих процессов захвата, связанных с фотосъемкой. В дополнение к базовому захвату неподвижных изображений, вывод фотографий, поддерживает захват в формате RAW, захват нескольких изображений, Live Photos и широкий цветовой охват. Вы можете выводить захваченные фотографии в различных форматах и кодеках, включая файлы DNG в формате RAW, файлы HEIF в формате HEVC и файлы JPEG.

cameraessionQueue — это очередь которую мы создаем для того чтобы в ней выполнять операции которые требуют определенного времени например AVCaptureSession.startRunning()

Метод startRunning() является блокирующим вызовом, который может занять некоторое время, поэтому начните сеанс в очереди последовательной отправки, чтобы не блокировать основную очередь (которая сохраняет отзывчивость пользовательского интерфейса).

isCameraFlashEnabled — просто переключатель для работы с камерой

Также нам будет необходимо сделать импорт нужных библиотек, а то пока Xcode не понимает что происходит)

import AVFoundation import Photos import PhotosUI

Давайте сделаем базовые настройки для наших объектов:

override func viewDidLoad() { super.viewDidLoad() //Boolean указывающий, следует ли настраивать конвейер захвата для захвата неподвижных изображений с высоким разрешением. stillImageOutput.isHighResolutionCaptureEnabled = true // настройка которая устанавливается для объекта, указываем что для нас в приоритете во время работы с объектом stillImageOutput.maxPhotoQualityPrioritization = .quality // Предустановленное значение, указывающее уровень качества или битрейт вывода для сеанса. captureSession.sessionPreset = .high // Тут мы проверяем поддерживает ли девайс возможность снимать видео and the device supports the specified focus mode. if let captureDevice = AVCaptureDevice.default(for: .video), captureDevice.isFocusModeSupported(.continuousAutoFocus) { // Запрашивает эксклюзивный доступ для настройки свойств оборудования устройства. try? captureDevice.lockForConfiguration() // И мы настраиваем режим, который постоянно следит за фокусировкой и при необходимости выполняет автофокусировку. captureDevice.focusMode = .continuousAutoFocus captureDevice.unlockForConfiguration() }

maxPhotoQualityPrioritization имеет несколько вариантов, вам нужно выбрать то, что вам особенно нужно:

Из документации Apple

case speed

Скорость доставки фотографий важнее всего, даже в ущерб качеству.

case quality

Качество фотографий важнее всего, даже в ущерб времени от снимка к снимку.

case balanced

Приоритет сбалансирован между качеством фото и скоростью доставки.


Cсылки:

11
1 комментарий

Комментарий недоступен

1
Ответить