SwiftUI 基础
首先,如果你是刚刚开始学习SwiftUI的,那么你可以先学习一下基础的SwiftUI。
这方面的教程网上有很多,这里就不介绍了,我们直接从实现相机功能开始入手。
相机拍照
拍照protocol和Events
定义相机拍照协议(protocol),所有相机相关的View需要实现该协议来处理结果。
定义一个ObservableObject类型,用来控制相机拍照和翻转。
// 拍照协议
public protocol CameraFxImage {
// 拍照完成方法
// image: 照片 UIImage 对象
mutating func didImageOk(_ image: UIImage)
// 执行特效方法
// fx:特效名称
mutating func doFx(_ fx: String)
}
// 拍照Events
public class UserEvents: ObservableObject {
// 触发相机拍照
@Published public var didAskToCapturePhoto = false
// 触发相机翻转
@Published public var didAskToRotateCamera = false
public init() {
}
}
BBMetalImage
需要先使用 CocosPods 安装 BBMetalImage 具体方法加不在这里赘述了。
通过 BBMetalImage 库的 BBMetalCamera 来实现相机和基础滤镜功能。
相机预览 CameraPhotoFilterView
通过实现 UIViewController 和 UIViewControllerRepresentable 协议,创建相机预览SwiftUI View
详细代码可以参考: MagicCamera/SwiftUICam/CameraPhotoFilterVC.swift
CameraPhotoFilterVC 实现 UIViewController
// 引入依赖的库
import AVFoundation
import BBMetalImage
class CameraPhotoFilterVC: UIViewController {
// 相机
private var camera: BBMetalCamera!
// 预览View
private var metalView: BBMetalView!
// 相机拍照协议
public var delegateFx: CameraFxImage?
// 相机方向
public var position: AVCaptureDevice.Position = .front
override func viewDidLoad() {
super.viewDidLoad()
// view.backgroundColor = .white
var smooth = Float(0.5)
var brightness = Float(0.01)
var preset = AVCaptureSession.Preset.photo
...
// 创建相机
camera = BBMetalCamera(sessionPreset: preset, position: position)
if camera == nil {
camera = BBMetalCamera(sessionPreset: .high, position: position)
}
let x: CGFloat = 0
let width: CGFloat = view.bounds.width
let height: CGFloat = min(width*4/3, view.bounds.height*2/3)
// 创建预览
metalView = BBMetalView(frame: CGRect(x: x, y: 0, width: width, height: height))
metalView.setContentHuggingPriority(.defaultLow, for: .horizontal)
metalView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
view.addSubview(metalView)
camera.canTakePhoto = true
camera.photoDelegate = self
// 将滤镜添加到相机预览
camera
.add(consumer: BBMetalBeautyFilter(distanceNormalizationFactor: 4, stepOffset: 4, edgeStrength: 1, smoothDegree: smooth))
.add(consumer: BBMetalBrightnessFilter(brightness: brightness))
.add(consumer: metalView)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// 启动相机预览
camera.start()
metalView.isHidden = false
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// 停止相机预览
camera.stop()
}
public func switchCameraPosition() {
// 翻转相机
camera.switchCameraPosition()
}
public func takePhoto() {
// 拍照
metalView.isHidden = true
camera.takePhoto()
}
}
// 实现 BBMetalCameraPhotoDelegate,处理拍照结果
extension CameraPhotoFilterVC: BBMetalCameraPhotoDelegate {
func camera(_ camera: BBMetalCamera, didOutput texture: MTLTexture) {
// In main thread
...
// 添加滤镜
let brightness = BBMetalBrightnessFilter(brightness: bright)
let imageSource = BBMetalStaticImageSource(image: texture.bb_image!)
// 设置滤镜链,并设置滤镜同步执行
imageSource.add(consumer: BBMetalBeautyFilter(distanceNormalizationFactor: 4, stepOffset: 4, edgeStrength: 1, smoothDegree: smooth))
.add(consumer: brightness)
.runSynchronously = true
// 开始处理
imageSource.transmitTexture()
// 返回处理后结果
guard let filteredImage = brightness.outputTexture?.bb_image else { return }
metalView.isHidden = false
DispatchQueue.main.async {
// 完成后调用代理方法,返回照片
self.delegateFx?.didImageOk(filteredImage)
}
}
}
CameraPhotoFilterView 实现 UIViewControllerRepresentable
struct CameraPhotoFilterView: UIViewControllerRepresentable {
@ObservedObject var events: UserEvents
private var level: Int
private var position: AVCaptureDevice.Position = .front
private var delegateFx: CameraFxImage?
typealias UIViewControllerType = CameraPhotoFilterVC
func makeUIViewController(context: Context) -> CameraPhotoFilterVC {
// 创建 CameraPhotoFilterVC 实例
let vc = CameraPhotoFilterVC()
vc.delegateFx = self.delegateFx
vc.level = self.level
vc.position = self.position
return vc
}
// 更新时调用
func updateUIViewController(_ cameraController: CameraPhotoFilterVC, context: Context) {
if events.didAskToCapturePhoto {
// 开始拍照
events.didAskToCapturePhoto = false
cameraController.takePhoto()
}
if events.didAskToRotateCamera {
// 开始翻转相机
events.didAskToRotateCamera = false
cameraController.switchCameraPosition()
}
}
public init(events: UserEvents, delegateFx: CameraFxImage?, level: Int = 1, position: AVCaptureDevice.Position = .front) {
...
}
}
拍照界面
拍照界面代码如下:需要定义拍照Events,拍照协议,相机方向等参数
使用 ZStack 把 CameraPhotoFilterView 放在底层,在预览界面上方放置拍照按钮等
详细代码可以参考: MagicCamera/SwiftUICam/LiveCameraFilterView.swift
struct LiveCameraFilterView: View {
// 拍照Events
@ObservedObject var events: UserEvents
// 拍照协议
var delegateFx: CameraFxImage?
// 相机方向
var position: AVCaptureDevice.Position = .front
// 初始化参数
public init(events: UserEvents, delegateFx: CameraFxImage?, position: AVCaptureDevice.Position = .front) {
...
}
var body: some View {
ZStack {
// 相机预览 CameraPhotoFilterView
CameraPhotoFilterView(events:events, delegateFx: delegateFx, level: level, position: position)
// 在预览上方显示其他界面,如拍照按钮等
VStack {
HStack {
Spacer()
// 拍照按钮
Button(action: {
weakself.events.didAskToCapturePhoto = true
}, label: {
...
}).disabled(takeing)
Spacer()
// 翻转按钮
Button(action: {
weakself.events.didAskToRotateCamera = true
}, label: {
...
}).disabled(takeing)
Spacer()
}
...
}
}
}
}
使用拍照
在需要拍照的页面上使用 LiveCameraFilterView, 并实现 CameraFxImage 协议
详细代码可以参考: MagicCamera/Cameras/BeautyCameraView.swift