wake-ios/wake/Components/Media/CustomCameraView.swift
2025-08-22 18:58:08 +08:00

266 lines
9.3 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
import AVFoundation
struct CustomCameraView: UIViewControllerRepresentable {
@Binding var isPresented: Bool
let onImageCaptured: (UIImage) -> Void
@Environment(\.presentationMode) private var presentationMode
func makeUIViewController(context: Context) -> CustomCameraViewController {
let viewController = CustomCameraViewController()
viewController.delegate = context.coordinator
return viewController
}
func updateUIViewController(_ uiViewController: CustomCameraViewController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, CustomCameraViewControllerDelegate {
let parent: CustomCameraView
init(_ parent: CustomCameraView) {
self.parent = parent
}
func didCaptureImage(_ image: UIImage) {
parent.onImageCaptured(image)
parent.presentationMode.wrappedValue.dismiss()
}
func didCancel() {
parent.presentationMode.wrappedValue.dismiss()
}
}
}
protocol CustomCameraViewControllerDelegate: AnyObject {
func didCaptureImage(_ image: UIImage)
func didCancel()
}
class CustomCameraViewController: UIViewController {
private var captureSession: AVCaptureSession?
private var photoOutput: AVCapturePhotoOutput?
private var previewLayer: AVCaptureVideoPreviewLayer?
private var captureDevice: AVCaptureDevice?
weak var delegate: CustomCameraViewControllerDelegate?
private lazy var captureButton: UIButton = {
let button = UIButton(type: .system)
button.backgroundColor = .white
button.tintColor = .black
button.layer.cornerRadius = 35
button.layer.borderWidth = 5
button.layer.borderColor = UIColor.lightGray.cgColor
button.addTarget(self, action: #selector(capturePhoto), for: .touchUpInside)
return button
}()
private lazy var closeButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(systemName: "xmark"), for: .normal)
button.tintColor = .white
button.addTarget(self, action: #selector(closeCamera), for: .touchUpInside)
return button
}()
private lazy var flipButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(systemName: "arrow.triangle.2.circlepath.camera"), for: .normal)
button.tintColor = .white
button.addTarget(self, action: #selector(switchCamera), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
checkCameraPermissions()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
//
previewLayer?.frame = view.bounds
//
if let connection = previewLayer?.connection, connection.isVideoOrientationSupported {
connection.videoOrientation = .portrait
}
}
private func checkCameraPermissions() {
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
setupCamera()
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
DispatchQueue.main.async {
if granted {
self?.setupCamera()
} else {
self?.delegate?.didCancel()
}
}
}
default:
delegate?.didCancel()
}
}
private func setupCamera() {
let session = AVCaptureSession()
session.sessionPreset = .high
// 使
guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {
delegate?.didCancel()
return
}
captureDevice = device
do {
let input = try AVCaptureDeviceInput(device: device)
if session.canAddInput(input) {
session.addInput(input)
}
let output = AVCapturePhotoOutput()
if session.canAddOutput(output) {
session.addOutput(output)
photoOutput = output
}
//
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.videoGravity = .resizeAspectFill
previewLayer.frame = view.bounds
previewLayer.connection?.videoOrientation = .portrait
//
view.layer.insertSublayer(previewLayer, at: 0)
self.previewLayer = previewLayer
DispatchQueue.global(qos: .userInitiated).async {
session.startRunning()
DispatchQueue.main.async {
self.setupUI()
}
}
captureSession = session
} catch {
print("Error setting up camera: \(error)")
delegate?.didCancel()
}
}
private func setupUI() {
view.bringSubviewToFront(closeButton)
view.bringSubviewToFront(flipButton)
view.bringSubviewToFront(captureButton)
view.addSubview(closeButton)
closeButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
closeButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20),
closeButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
closeButton.widthAnchor.constraint(equalToConstant: 44),
closeButton.heightAnchor.constraint(equalToConstant: 44)
])
view.addSubview(flipButton)
flipButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
flipButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20),
flipButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
flipButton.widthAnchor.constraint(equalToConstant: 44),
flipButton.heightAnchor.constraint(equalToConstant: 44)
])
view.addSubview(captureButton)
captureButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
captureButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
captureButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -30),
captureButton.widthAnchor.constraint(equalToConstant: 70),
captureButton.heightAnchor.constraint(equalToConstant: 70)
])
}
@objc private func capturePhoto() {
let settings = AVCapturePhotoSettings()
photoOutput?.capturePhoto(with: settings, delegate: self)
}
@objc private func closeCamera() {
delegate?.didCancel()
}
@objc private func switchCamera() {
guard let currentInput = captureSession?.inputs.first as? AVCaptureDeviceInput else { return }
let newPosition: AVCaptureDevice.Position = currentInput.device.position == .front ? .back : .front
guard let newDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: newPosition) else { return }
do {
let newInput = try AVCaptureDeviceInput(device: newDevice)
captureSession?.beginConfiguration()
captureSession?.removeInput(currentInput)
if captureSession?.canAddInput(newInput) == true {
captureSession?.addInput(newInput)
captureDevice = newDevice
} else {
captureSession?.addInput(currentInput)
}
captureSession?.commitConfiguration()
} catch {
print("Error switching camera: \(error)")
}
}
}
extension CustomCameraViewController: AVCapturePhotoCaptureDelegate {
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
if let error = error {
print("Error capturing photo: \(error)")
return
}
guard let imageData = photo.fileDataRepresentation(),
let image = UIImage(data: imageData) else {
return
}
let fixedImage = image.fixedOrientation()
DispatchQueue.main.async {
self.delegate?.didCaptureImage(fixedImage)
}
}
}
extension UIImage {
func fixedOrientation() -> UIImage {
if imageOrientation == .up {
return self
}
UIGraphicsBeginImageContextWithOptions(size, false, scale)
draw(in: CGRect(origin: .zero, size: size))
let normalizedImage = UIGraphicsGetImageFromCurrentImageContext() ?? self
UIGraphicsEndImageContext()
return normalizedImage
}
}