
引言
在上一篇文章中,我们初步完成了使用 AVFoundation 采集视频数据的流程,掌握了 AVCaptureSession 的搭建与视频流的预览显示。
本篇将继续深入 AVFoundation,聚焦于静态图片采集的实现。通过 AVCapturePhotoOutput,我们可以快速搭建起拍照功能,并获取高质量的照片数据。
本文将详细介绍拍照的基本流程,包括如何创建输出、配置拍照参数、处理拍照回调,以及如何将拍摄到的照片保存到系统相册。结合实际示例,带你完成一套完整、稳定的拍照功能搭建。
在早期的 iOS 开发中,我们常常使用 AVCaptureStillImageOutput 来实现拍照功能。但从 iOS 10 开始,Apple 推荐使用更强大、功能更全面的 AVCapturePhotoOutput 进行静态图片采集。AVCapturePhotoOutput 不仅统一了拍照接口,还支持 HEIF 格式、Live Photo、深度数据、RAW 拍摄等高级特性,能够更好地满足高质量拍摄的需求。
接下来,我们将基于 AVCapturePhotoOutput,搭建一个基础的拍照流程。
搭建拍照功能
为了更好地管理拍照流程,我们将功能封装到一个自定义的PHCaptureController类中,负责完成会话的搭建、控制与拍照等操作。
整个拍照流程主要包括以下几个步骤:
- 配置 AVCaptureSession。
- 添加摄像头输入。
- 添加照片输出。
- 启动与停止会话。
- 处理拍照请求与回调。
下面我们来逐一实现。
1. 类的基本结构
我们先来创建一个PHCaptureController类,大致结构如下:
Swift
//
// PHCaptureController.swift
// PHCaptureExample
//
// Created by Louis on 2025/4/23.
// 负责会话控制及拍照操作
import UIKit
import AVFoundation
class 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 实现如下:
Swift
//
// PHCaptureProtocol.swift
// PHCaptureExample
//
// Created by Louis on 2025/4/23.
//
import Foundation
import UIKit
protocol PHCaptureProtocol:NSObjectProtocol {
/// 发生错误
func captureError(_ error: Error)
/// 拍照回调
func capturePhoto(_ image: UIImage)
}
包含了:
- captureError:发生错误的回调方法。
- capturePhoto:拍照结果的回调方法。
2. 配置采集会话
我们在 setupConfigureSession 方法中需要完成三个操作:
- 设置会话预设。
- 添加摄像头输入。
- 添加照片输出。
并且保证这些操作需要在会话 beginConfiguration 与 commitConfiguration 方法之间执行。
Swift
/// 配置会话
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 设置会话预设
就是设置分辨率,比如高质量照片。
Swift
/// 设置会话话预设
private func setupSessionPreset() {
session.sessionPreset = .photo
}
2.2 设置会话输入
添加摄像头设备,并包装一层 AVCaptureDeviceInput 添加到会话中。
Swift
/// 设置会话输入
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 设置会话输出
将图片输出添加到会话。
Swift
/// 设置会话输出
private func setupSessionOutput() -> Bool {
if session.canAddOutput(photoOutput) {
session.addOutput(photoOutput)
return true
} else {
return false
}
}
添加输出时也要检测是否可以被添加到会话。
3. 会话的启动与停止
我们使用单独的串行队列来管理会话的启动与停止,避免出现线程问题。
Swift
/// 启动会话
func startSession() {
sessionQueue.async {
if !self.session.isRunning {
self.session.startRunning()
}
}
}
/// 停止会话
func stopSession() {
sessionQueue.async {
if self.session.isRunning {
self.session.stopRunning()
}
}
}
4. 拍照
在拍照的方法里设置拍照参数和代理,并实现 AVCapturePhotoCaptureDelegate 的代理方法。
Swift
/// 拍照
func takePhoto() {
let settings = AVCapturePhotoSettings()
settings.flashMode = .auto
settings.isHighResolutionPhotoEnabled = true
sessionQueue.async {
self.photoOutput.capturePhoto(with: settings, delegate: self)
}
}
//MARK: - AVCapturePhotoCaptureDelegate
func 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。
Swift
import UIKit
import AVFoundation
class PHPreviewView: UIView {
override class var layerClass: AnyClass {
return AVCaptureVideoPreviewLayer.self
}
private var previewLayer: AVCaptureVideoPreviewLayer {
return layer as! AVCaptureVideoPreviewLayer
}
func setSession(_ session: AVCaptureSession) {
previewLayer.session = session
previewLayer.videoGravity = .resizeAspectFill
}
}
使用拍照功能
接下来我们只需要在视图控制器中,非常简单的接入一下拍照的控制器和预览视图就可以使用拍照功能咯。
Swift
import UIKit
import AVFoundation
class ViewController: UIViewController,PHCaptureProtocol {
/// 拍照控制器
let captureController = PHCaptureController()
/// 预览视图
let previewView = PHPreviewView()
override func viewDidLoad() {
super.viewDidLoad()
captureController.setupConfigureSession()
captureController.delegate = self
captureController.startSession()
previewView.setSession(captureController.session)
view.addSubview(previewView)
previewView.frame = view.bounds
}
//MARK: - PHCaptureProtocol
func captureError(_ error: any Error) {
}
func capturePhoto(_ image: UIImage) {
}
}
结语
在本篇中,我们基于 AVFoundation 框架,搭建了一个基本的拍照功能实现流程:
包括配置 AVCaptureSession、添加 AVCaptureDeviceInput 和 AVCapturePhotoOutput、设置预览视图 PHPreviewView,并通过 AVCapturePhotoCaptureDelegate 拿到照片数据,为后续保存、展示、处理照片打下了基础。
需要特别注意的是:在正式使用相机功能之前,务必进行权限申请和检测。
如果未申请或未获得相机访问权限,直接启动 AVCaptureSession 会导致应用崩溃或黑屏。
通常,我们会在 App 启动或功能入口时,通过 AVCaptureDevice.requestAccess(for: .video) 请求权限,并根据权限结果决定是否继续初始化采集相关流程。
到这里,我们已经完成了拍照功能的基本搭建,感谢大家的阅读。