diff --git a/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate b/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate index 29cefd4..2aa7ba7 100644 Binary files a/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate and b/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/wake/Components/Media/CustomCameraView.swift b/wake/Components/Media/CustomCameraView.swift new file mode 100644 index 0000000..f63763e --- /dev/null +++ b/wake/Components/Media/CustomCameraView.swift @@ -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 + } +} diff --git a/wake/View/Owner/UserInfo/AvatarPicker.swift b/wake/View/Owner/UserInfo/AvatarPicker.swift index 03fe974..63153b7 100644 --- a/wake/View/Owner/UserInfo/AvatarPicker.swift +++ b/wake/View/Owner/UserInfo/AvatarPicker.swift @@ -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 { } } } + } } }