266 lines
9.3 KiB
Swift
266 lines
9.3 KiB
Swift
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
|
||
}
|
||
}
|