Неважно, будет ли главное внимание в вашем приложении запечатлеть удивительный пейзаж или просто невинную селфи. Что важно для разработчиков, так это позволить пользователям максимально использовать возможности камеры и делать это безболезненно. С этой целью Apple проделала замечательную работу, представив AVCapturePhotoOutput
в iOS 10 . В этой статье я покажу, как реализовать некоторые его функции. Полный проект можно скачать с GitHub .
Что нового?
Конечно, приложения iOS могли использовать камеру раньше. Тем не менее, фреймворк имеет новые функции и улучшил существующие.
- Live Photos смешивает картинку с коротким видео. Это заставляет фотографии «оживать» и лучше вспоминать воспоминания.
- Съемка RAW фото теперь возможна.
- Миниатюрные изображения доступны при захвате фотографии. Это полезно для предварительного просмотра изображения для пользователя.
- Также вы можете делать широкоформатные фотографии.
Давайте доберемся до этого.
Короткая настройка
Чтобы сохранить фокус, я не включил шаги для разработки UIViewController
. Его можно скопировать из раскадровки в полном проекте или настроить дизайн в соответствии со своими предпочтениями.
Начните с импорта правильных структур :
import UIKit import AVFoundation import Photos
Затем объявите следующие переменные и выходы:
var captureSession = AVCaptureSession() var cameraOutput = AVCapturePhotoOutput() var previewLayer = AVCaptureVideoPreviewLayer() var currentImage: (image: Data, imageName: String)? var previewing = false var highResolutionEnabled = true var rawEnabled = false var live = .Off var flashMode = AVCaptureFlashMode.off var cameraPosition = AVCaptureDevicePosition.back @IBOutlet weak var capturedButton: UIButton! @IBOutlet weak var previewView: UIView! @IBOutlet weak var menuView: UIView! @IBOutlet weak var toastLabel: UILabel! @IBOutlet weak var saveButton: UIButton!
LiveMode
— это пользовательское перечисление.
enum LiveMode { case On, Off, Unavailable }
Для отображения сообщения на экране отображается метка с использованием следующей функции.
func showToast(text: String) { toastLabel.text = text UIView.animate(withDuration: 1.0, animations: { self.toastLabel.alpha = 1.0 }) UIView.animate(withDuration: 1.0, delay: 2.0, options: .curveLinear, animations: { self.toastLabel.alpha = 0.0 }, completion: nil) }
Теперь мы готовы двигаться дальше.
права доступа
При запросе доступа к конфиденциальным данным в iOS 10 необходимо добавить строку описания в файл info.plist , который будет отображаться для пользователя, чтобы он знал, что вы будете делать с данными. Следующие ключи описывают использование библиотеки фотографий и камеры соответственно. Это должно помочь пользователю лучше понять, почему пытаются получить доступ к этим компонентам.
<key>NSPhotoLibraryUsageDescription</key> <string>to save photos and videos</string> <key>NSCameraUsageDescription</key> <string>to take photos</string>
В первый раз сообщение будет отображаться как на следующих скриншотах. Тогда выбор пользователя будет запомнен.
Загрузка камеры
Прежде чем пользователь сможет взаимодействовать с приложением, камера должна загрузиться. Это делается в методе viewDidLoad
override func viewDidLoad() { super.viewDidLoad() loadCamera() }
Мы должны убедиться, что камера доступна в первую очередь. AVCaptureDevice
инициализируется с использованием положения камеры, установленного пользователем. Это запускает переднюю или заднюю камеру.
func loadCamera() { let device = AVCaptureDevice.defaultDevice(withDeviceType: .builtInWideAngleCamera, mediaType: AVMediaTypeVideo, position: cameraPosition)
Перед использованием captureSession
любой предыдущий ввод удаляется. Это необходимо, когда камера переключается спереди назад и наоборот.
captureSession.removeInput(captureSession.inputs.first as! AVCaptureInput!)
Добавьте выход cameraOutput
к AVCaptureSession
. Это когда мы проверяем, доступен ли захват живого фото , и устанавливаем захват высокого разрешения на выбор пользователя.
if let input = try? AVCaptureDeviceInput(device: device) { if (captureSession.canAddInput(input)) { captureSession.addInput(input) if (captureSession.canAddOutput(cameraOutput)) { cameraOutput.isHighResolutionCaptureEnabled = self.highResolutionEnabled captureSession.addOutput(cameraOutput) if !cameraOutput.isLivePhotoCaptureSupported { self.live = .Unavailable } // Next, the previewLayer is setup to show the camera content with the size of the view. previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) previewLayer.frame = previewView.bounds previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill previewView.clipsToBounds = true previewView.layer.addSublayer(previewLayer) captureSession.startRunning() } } else { print("Cannot add output") } }
Хорошей практикой было бы остановить captureSession
когда представление больше не видно:
override func viewDidDisappear(_ animated: Bool) { if captureSession.isRunning { captureSession.stopRunning() } else { captureSession.startRunning() } }
Теперь у нас работает камера. Время сделать снимок.
Первый захват
Чтобы сделать снимок, просто добавьте следующий IBAction
. Это действие запускается при нажатии кнопки со значком камеры. За ним следует классический звуковой эффект затвора камеры.
@IBAction func didPressTakePhoto(_ sender: UIButton) { var settings = AVCapturePhotoSettings() cameraOutput.capturePhoto(with: settings, delegate: self) }
В параметрах запроса следует отметить две вещи. Настройки — это объект, который мы только что создали (свойства будут добавлены на следующем шаге). Делегатом является протокол AVCapturePhotoCaptureDelegate
, которому должен соответствовать класс. Это сделано в extension
класса.
extension ViewController : AVCapturePhotoCaptureDelegate { func capture(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhotoSampleBuffer photoSampleBuffer: CMSampleBuffer?, previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) { if let error = error { print("Capture failed: \(error.localizedDescription)") } } }
Этот метод вызывается, когда все процессы завершены и фотография готова.
Thumbnail
При съемке фотографии естественным следующим шагом будет предварительный просмотр. AVFoundation
делает это легко. Он предлагает возможность указать размеры изображения и получить предварительный просмотр, когда обработка будет завершена.
Нам просто нужно изменить функцию capture
выше, чтобы показать изображение в левом нижнем углу, как показано на фотографии.
if let error = error { print("Capture failed: \(error.localizedDescription)") } if let sampleBuffer = photoSampleBuffer, let previewBuffer = previewPhotoSampleBuffer, let dataImage = AVCapturePhotoOutput .jpegPhotoDataRepresentation(forJPEGSampleBuffer: sampleBuffer, previewPhotoSampleBuffer: previewBuffer) { self.currentImage = (dataImage, "\(resolvedSettings.uniqueID).jpg") showImage() }
Здесь буфер преобразуется в объект Data
и устанавливается в качестве текущего изображения. Функция showImage
просто берет этот объект и устанавливает его как изображение capturedButton
.
func showImage() { let dataProvider = CGDataProvider(data: self.currentImage!.image as CFData) let cgImageRef: CGImage! = CGImage(jpegDataProviderSource: dataProvider!, decode: nil, shouldInterpolate: true, intent: .defaultIntent) let image = UIImage(cgImage: cgImageRef, scale: 1.0, orientation: UIImageOrientation.right) self.capturedButton.imageView?.contentMode = .scaleAspectFill self.capturedButton.setImage(image, for: .normal) self.capturedButton.isHidden = false }
Чтобы установить размеры предварительного просмотра, нам нужно добавить формат предварительного просмотра в объект AVCapturePhotoSettings
.
var settings = AVCapturePhotoSettings() let previewPixelType = settings.availablePreviewPhotoPixelFormatTypes.first! let previewFormat = [ kCVPixelBufferPixelFormatTypeKey as String: previewPixelType, kCVPixelBufferWidthKey as String: self.capturedButton.frame.width, kCVPixelBufferHeightKey as String: self.capturedButton.frame.height ] as [String : Any] settings.previewPhotoFormat = previewFormat
Сохранение данных
В предварительном просмотре миниатюры в этом проекте доступна возможность сохранить захваченное фото в библиотеке. Для перехода от миниатюры к полноэкранному режиму и наоборот используется простая анимация. Это также показывает и скрывает кнопку Сохранить .
@IBAction func previewClicked() { UIView.animate(withDuration: 0.5, animations: { if !self.previewing { self.capturedButton.frame = self.view.frame self.saveButton.isHidden = false } else { let x: CGFloat = 20.0 let y: CGFloat = self.view.frame.height - 100.0 - 20.0 self.capturedButton.frame = CGRect(x: x, y: y, width: 75.0, height: 100.0) self.saveButton.isHidden = true } }) previewing = !previewing }
При нажатии кнопки « Save
у пользователя запрашивается разрешение на доступ к библиотеке фотографий. Мы продолжаем пытаться сохранить фотографию только тогда, когда это разрешение предоставлено. Функция showToast
вызывается в основном потоке, так как она изменяет пользовательский интерфейс.
@IBAction func saveClicked() { if let image = currentImage { PHPhotoLibrary.requestAuthorization({ (status) in if status == .authorized { do { try self.save(image: image.image, withName: image.imageName) } catch let error { print(error.localizedDescription) } } else { DispatchQueue.main.async { self.showToast(text: "Not authorized!") } } }) } }
Наконец, настоящая работа выполняется в функции save
. Сначала изображение сохраняется во временный файл.
func save(image: Data, withName: String) throws { let url = URL(fileURLWithPath: NSTemporaryDirectory().appending(withName)) try image.write(to: url, options: .atomicWrite)
Затем PHPhotoLibrary
используется для добавления изображения в качестве ресурса. Функция performChanges
также имеет completionHandler
для изменения пользовательского интерфейса или другой работы.
PHPhotoLibrary.shared().performChanges({ let request = PHAssetCreationRequest.forAsset() request.addResource(with: .photo, data: image, options: nil) let creationOptions = PHAssetResourceCreationOptions() creationOptions.shouldMoveFile = true request.addResource(with: .alternatePhoto, fileURL: url, options: nil) }, completionHandler: { (success, error) in if let error = error { print(error.localizedDescription) return }
Убедившись, что все в порядке, временный файл удаляется и отображается сообщение об успехе.
if FileManager.default.fileExists(atPath: url.absoluteString) { do { try FileManager.default.removeItem(at: url) } catch let err { print(err.localizedDescription) } } DispatchQueue.main.async { self.saveButton.isHidden = true self.showToast(text: "Image saved") } }) }
RAW
Запись изображений в формате RAW , а также Live Photos доступна на iPhone 6s, 6s +, SE и 9,7 ″ iPad Pro. RAW- фотографии несжаты и имеют больше битов на пиксель, что дает больше возможностей для редактирования.
Для захвата RAW фото AVCaptureSessionPresetPhoto
формат AVCaptureSessionPresetPhoto
и камера заднего вида. Код очень похож на то, что нормальный захват изображения. AVCapturePhotoSettings
в этом случае инициализируются с использованием необработанного пиксельного формата.
if rawEnabled { if let rawFormat = cameraOutput.availableRawPhotoPixelFormatTypes.first { settings = AVCapturePhotoSettings(rawPixelFormatType: OSType(rawFormat)) } }
Теперь следующий обратный вызов добавлен к расширению класса. Формат файла в этом случае — .dng
.
func capture(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingRawPhotoSampleBuffer rawSampleBuffer: CMSampleBuffer?, previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) { if let error = error { print("Capture failed: \(error.localizedDescription)") } if let sampleBuffer = rawSampleBuffer, let previewBuffer = previewPhotoSampleBuffer, let dataImage = AVCapturePhotoOutput .dngPhotoDataRepresentation(forRawSampleBuffer: sampleBuffer, previewPhotoSampleBuffer: previewBuffer) { self.currentImage = (dataImage, "\(resolvedSettings.uniqueID).dng") showImage() } }
Больше опций
Чтобы изменить некоторые настройки, такие как вспышка или положение камеры, я добавил меню в правом нижнем углу. Это делается видимым через эту функцию.
@IBAction func moreClicked() { UIView.animate(withDuration: 1.0, animations: { self.menuView.alpha = 1.0 - self.menuView.alpha }) }
Каждая из кнопок имеет свой IBAction
который срабатывает.
Высокое разрешение
Изменяет логическое значение и шрифт текста кнопки.
@IBAction func toggleHR(button: UIButton) { highResolutionEnabled = !highResolutionEnabled button.titleLabel?.font = highResolutionEnabled ? UIFont (name: "System-Heavy", size: 15) : UIFont (name: "System-Thin", size: 15) showToast(text: "High resolution: \(highResolutionEnabled)") }
Затем следующий параметр устанавливается в AVCapturePhotoSettings
.
settings.isHighResolutionPhotoEnabled = self.highResolutionEnabled
Положение камеры
Меняет положение и перезагружает камеру.
@IBAction func toggleCamera() { if cameraPosition == .back { cameraPosition = .front showToast(text: "Camera: front") } else { cameraPosition = .back showToast(text: "Camera: back") } loadCamera() }
вспышка
Поскольку режим вспышки имеет три возможных значения, для переключения между вариантами используется регистр переключателя. Изображение на кнопке настроено соответственно.
@IBAction func toggleFlash(button: UIButton) { switch self.flashMode { case .off: self.flashMode = .on showToast(text: "Flash mode: on") button.setImage(UIImage(named: "FlashOn"), for: .normal) break case .on: self.flashMode = .auto showToast(text: "Flash mode: auto") button.setImage(UIImage(named: "FlashAuto"), for: .normal) break case .auto: self.flashMode = .off showToast(text: "Flash mode: off") button.setImage(UIImage(named: "FlashOff"), for: .normal) break } }
Перед съемкой изображения настройки обновляются в режиме вспышки, если он доступен.
if cameraOutput.supportedFlashModes.contains(NSNumber(value: self.flashMode.rawValue)) { settings.flashMode = self.flashMode }
RAW
Ранее мы видели, как сделать снимок в формате RAW , но для переключения между RAW и обычным фото нам нужна следующая функция. Сначала мы проверяем, доступно ли это для захвата изображения RAW, а затем действуем аналогично функции переключения высокого разрешения.
@IBAction func toggleRAW(button: UIButton) { if cameraOutput.availableRawPhotoPixelFormatTypes.count == 0 { showToast(text: "RAW not available") return } rawEnabled = !rawEnabled button.titleLabel?.font = rawEnabled ? UIFont (name: "System-Heavy", size: 15) : UIFont (name: "System-Thin", size: 15) showToast(text: "RAW: \(rawEnabled)") }
Прямой эфир
Значение для переменной live
устанавливается при загрузке камеры. Это -1, когда он недоступен, и 0 или 1, когда он включен или выключен.
@IBAction func toggleLive(button: UIButton) { switch live { case .Unavailable: showToast(text: "Live photo not supported") break case .On: live = .Off showToast(text: "Live: off") button.titleLabel?.font = UIFont (name: "System-Thin", size: 15) break case .Off: live = .On showToast(text: "Live on") button.titleLabel?.font = UIFont (name: "System-Heavy", size: 15) break } }
Нам также нужно установить URL для видеофайла в настройках перед тем, как сделать снимок.
if self.live == .On { let path = "\(NSTemporaryDirectory())/Photoshot_\(settings.uniqueID)" settings.livePhotoMovieFileURL = URL(fileURLWithPath: path) }
Вывод
AVCapturePhotoOutput
реализацию функций камеры в вашем приложении. Эта статья была кратким введением в то, как использовать некоторые из этих функций.
Я надеюсь, что вы найдете это интересным и полезным.
Пусть Кодекс будет с вами.