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,6 +32,7 @@ public struct AvatarPicker: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
|
VStack() {
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 20) {
|
||||||
// Avatar Image Button
|
// Avatar Image Button
|
||||||
Button(action: {
|
Button(action: {
|
||||||
@ -162,7 +163,7 @@ public struct AvatarPicker: View {
|
|||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.sheet(isPresented: $showImageCapture) {
|
.sheet(isPresented: $showImageCapture) {
|
||||||
CameraView(isPresented: $showImageCapture) { image in
|
CustomCameraView(isPresented: $showImageCapture) { image in
|
||||||
selectedImage = image
|
selectedImage = image
|
||||||
uploadManager.selectedMedia = [.image(image)]
|
uploadManager.selectedMedia = [.image(image)]
|
||||||
withAnimation {
|
withAnimation {
|
||||||
@ -174,6 +175,7 @@ public struct AvatarPicker: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Button style for scale effect
|
// Button style for scale effect
|
||||||
private struct ScaleButtonStyle: ButtonStyle {
|
private struct ScaleButtonStyle: ButtonStyle {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user