feat: 相机
This commit is contained in:
parent
534c38cd8b
commit
5a784e7ae7
Binary file not shown.
265
wake/Components/Media/CustomCameraView.swift
Normal file
265
wake/Components/Media/CustomCameraView.swift
Normal file
@ -0,0 +1,265 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -32,7 +32,8 @@ public struct AvatarPicker: View {
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack(spacing: 20) {
|
||||
VStack() {
|
||||
VStack(spacing: 20) {
|
||||
// Avatar Image Button
|
||||
Button(action: {
|
||||
withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
|
||||
@ -162,7 +163,7 @@ public struct AvatarPicker: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.sheet(isPresented: $showImageCapture) {
|
||||
CameraView(isPresented: $showImageCapture) { image in
|
||||
CustomCameraView(isPresented: $showImageCapture) { image in
|
||||
selectedImage = image
|
||||
uploadManager.selectedMedia = [.image(image)]
|
||||
withAnimation {
|
||||
@ -172,6 +173,7 @@ public struct AvatarPicker: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user