引言
在上一篇文章中,我們初步完成了使用 AVFoundation 采集視頻數據的流程,掌握了?AVCaptureSession?的搭建與視頻流的預覽顯示。
本篇將繼續深入 AVFoundation,聚焦于靜態圖片采集的實現。通過?AVCapturePhotoOutput,我們可以快速搭建起拍照功能,并獲取高質量的照片數據。
本文將詳細介紹拍照的基本流程,包括如何創建輸出、配置拍照參數、處理拍照回調,以及如何將拍攝到的照片保存到系統相冊。結合實際示例,帶你完成一套完整、穩定的拍照功能搭建。
在早期的 iOS 開發中,我們常常使用?AVCaptureStillImageOutput?來實現拍照功能。但從 iOS 10 開始,Apple 推薦使用更強大、功能更全面的?AVCapturePhotoOutput?進行靜態圖片采集。AVCapturePhotoOutput?不僅統一了拍照接口,還支持 HEIF 格式、Live Photo、深度數據、RAW 拍攝等高級特性,能夠更好地滿足高質量拍攝的需求。
接下來,我們將基于?AVCapturePhotoOutput,搭建一個基礎的拍照流程。
搭建拍照功能
為了更好地管理拍照流程,我們將功能封裝到一個自定義的PHCaptureController類中,負責完成會話的搭建、控制與拍照等操作。
整個拍照流程主要包括以下幾個步驟:
- 配置?AVCaptureSession。
- 添加攝像頭輸入。
- 添加照片輸出。
- 啟動與停止會話。
- 處理拍照請求與回調。
下面我們來逐一實現。
1.?類的基本結構
我們先來創建一個PHCaptureController類,大致結構如下:
//
// PHCaptureController.swift
// PHCaptureExample
//
// Created by Louis on 2025/4/23.
// 負責會話控制及拍照操作import UIKit
import AVFoundationclass PHCaptureController:NSObject {/// 會話private let session = AVCaptureSession()/// 輸出private let photoOutput = AVCapturePhotoOutput()/// 輸入private var captureDeviceInput: AVCaptureDeviceInput?/// 隊列private let sessionQueue = DispatchQueue(label: "com.example.captureSession")/// 代理weak var delegate: PHCaptureProtocol?init() {}/// 配置會話func setupConfigureSession() { }/// 啟動會話func startSession() { }/// 停止會話func stopSession() { }/// 拍照func takePhoto() { }}
可以看到我們在類的內部維護了:
- session:采集會話。
- photoOutput:用于拍照的會話輸出。
- captureDeviceInput:當前使用的會話輸入。
- sessionQueue:串行隊列,保證采集相關操作的線程安全。
- delegate:代理,負責錯誤和結果的回調。
PHCaptureProtocol 實現如下:
//
// PHCaptureProtocol.swift
// PHCaptureExample
//
// Created by Louis on 2025/4/23.
//import Foundation
import UIKitprotocol PHCaptureProtocol:NSObjectProtocol {/// 發生錯誤func captureError(_ error: Error)/// 拍照回調func capturePhoto(_ image: UIImage)}
包含了:
- captureError:發生錯誤的回調方法。
- capturePhoto:拍照結果的回調方法。
2.?配置采集會話
我們在?setupConfigureSession 方法中需要完成三個操作:
- 設置會話預設。
- 添加攝像頭輸入。
- 添加照片輸出。
并且保證這些操作需要在會話?beginConfiguration 與?commitConfiguration 方法之間執行。
/// 配置會話func setupConfigureSession() {session.beginConfiguration()// 1.設置會話預設setupSessionPreset()// 2.設置會話輸入if !setupSessionInput() {delegate?.captureError(NSError(domain: "PHCaptureController", code: 1001, userInfo: [NSLocalizedDescriptionKey: "Failed to add input"]))return}// 3.設置會話輸出if !setupSessionOutput() {delegate?.captureError(NSError(domain: "PHCaptureController", code: 1002, userInfo: [NSLocalizedDescriptionKey: "Failed to add output"]))return}session.commitConfiguration()}
2.1 設置會話預設
就是設置分辨率,比如高質量照片。
/// 設置會話話預設private func setupSessionPreset() {session.sessionPreset = .photo}
2.2 設置會話輸入
添加攝像頭設備,并包裝一層 AVCaptureDeviceInput 添加到會話中。
/// 設置會話輸入private func setupSessionInput() -> Bool {guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video,position: .back) else { return false }do {captureDeviceInput = try AVCaptureDeviceInput(device: device)if session.canAddInput(captureDeviceInput!) {session.addInput(captureDeviceInput!)return true} else {return false}} catch {delegate?.captureError(error)return false}}
- 首先要確保獲取到了攝像頭設備。
- 檢測輸入是否可以被添加到會話。
- 如果出現錯誤則直接回調。
2.3 設置會話輸出
將圖片輸出添加到會話。
/// 設置會話輸出private func setupSessionOutput() -> Bool {if session.canAddOutput(photoOutput) {session.addOutput(photoOutput)return true} else {return false}}
添加輸出時也要檢測是否可以被添加到會話。
3. 會話的啟動與停止
我們使用單獨的串行隊列來管理會話的啟動與停止,避免出現線程問題。
/// 啟動會話func startSession() {sessionQueue.async {if !self.session.isRunning {self.session.startRunning()}}}/// 停止會話func stopSession() {sessionQueue.async {if self.session.isRunning {self.session.stopRunning()}}}
4. 拍照
在拍照的方法里設置拍照參數和代理,并實現?AVCapturePhotoCaptureDelegate 的代理方法。
/// 拍照func takePhoto() {let settings = AVCapturePhotoSettings()settings.flashMode = .autosettings.isHighResolutionPhotoEnabled = truesessionQueue.async {self.photoOutput.capturePhoto(with: settings, delegate: self)}}//MARK: - AVCapturePhotoCaptureDelegatefunc photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: (any Error)?) {if let error = error {delegate?.captureError(error)return}guard let imageData = photo.fileDataRepresentation() else {delegate?.captureError(NSError(domain: "PHCaptureController", code: 1003, userInfo: [NSLocalizedDescriptionKey: "Failed to get image data"]))return}guard let image = UIImage(data: imageData) else {delegate?.captureError(NSError(domain: "PHCaptureController", code: 1004, userInfo: [NSLocalizedDescriptionKey: "Failed to create image"]))return}delegate?.capturePhoto(image)}
整個拍照的核心功能就已經搭建完成了,接下來我們只需要添加畫面預覽,就可以調用這個類來實現完整的拍照功能了。
5. 畫面預覽
為了能夠實時顯示攝像頭畫面,我們需要一個專門的預覽視圖。
在 AVFoundation 中,通常通過?AVCaptureVideoPreviewLayer?來實現攝像頭畫面渲染,因此我們可以自定義一個簡單的?PHPreviewView。
import UIKit
import AVFoundationclass PHPreviewView: UIView {override class var layerClass: AnyClass {return AVCaptureVideoPreviewLayer.self}private var previewLayer: AVCaptureVideoPreviewLayer {return layer as! AVCaptureVideoPreviewLayer}func setSession(_ session: AVCaptureSession) {previewLayer.session = sessionpreviewLayer.videoGravity = .resizeAspectFill}
}
使用拍照功能
接下來我們只需要在視圖控制器中,非常簡單的接入一下拍照的控制器和預覽視圖就可以使用拍照功能咯。
import UIKit
import AVFoundationclass ViewController: UIViewController,PHCaptureProtocol {/// 拍照控制器let captureController = PHCaptureController()/// 預覽視圖let previewView = PHPreviewView()override func viewDidLoad() {super.viewDidLoad()captureController.setupConfigureSession()captureController.delegate = selfcaptureController.startSession()previewView.setSession(captureController.session)view.addSubview(previewView)previewView.frame = view.bounds}//MARK: - PHCaptureProtocolfunc captureError(_ error: any Error) {}func capturePhoto(_ image: UIImage) {}}
結語
在本篇中,我們基于 AVFoundation 框架,搭建了一個基本的拍照功能實現流程:
包括配置?AVCaptureSession、添加?AVCaptureDeviceInput?和?AVCapturePhotoOutput、設置預覽視圖?PHPreviewView,并通過?AVCapturePhotoCaptureDelegate?拿到照片數據,為后續保存、展示、處理照片打下了基礎。
需要特別注意的是:在正式使用相機功能之前,務必進行權限申請和檢測。
如果未申請或未獲得相機訪問權限,直接啟動?AVCaptureSession?會導致應用崩潰或黑屏。
通常,我們會在 App 啟動或功能入口時,通過?AVCaptureDevice.requestAccess(for: .video)?請求權限,并根據權限結果決定是否繼續初始化采集相關流程。
到這里,我們已經完成了拍照功能的基本搭建,感謝大家的閱讀。
?
?
?
?