最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

ios - Swift UI Camera Preview Black Screen - Stack Overflow

programmeradmin0浏览0评论

I am getting an issue for my swift ui app where I am trying to implement a video recording functionality inside of my app. I am not sure why its not working. I am getting a black preview screen whenever the camera opens up.

Just to be clear I have added the camera and microphone usage description on my plist file. I have added the view and controller code here. If anyone has dealt with this before, I would really appreciate the help.

CameraViewMode.swift

// MARK: - Camera View Model
class CameraViewModel: NSObject, ObservableObject {
    @Published var isCameraAuthorized = false
    @Published var isRecording = false
    @Published var previewLayer: AVCaptureVideoPreviewLayer?
    @Published var capturedImage: UIImage?
    @Published var recordedVideoURL: URL?
    @Published var isPreviewingMedia = false
    @Published var isFlashOn = false
    @Published var isFrontCamera = false
    @Published var recordingProgress: CGFloat = 0

private var captureSession: AVCaptureSession?
private var videoOutput: AVCaptureMovieFileOutput?
private var photoOutput: AVCapturePhotoOutput?
private var recordingTimer: Timer?
private let maxRecordingDuration: CGFloat = 10.0 // Maximum recording duration in seconds

override init() {
    super.init()
    checkPermissions()
}

private func checkPermissions() {
    switch AVCaptureDevice.authorizationStatus(for: .video) {
    case .authorized:
        setupCamera()
    case .notDetermined:
        AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
            if granted {
                DispatchQueue.main.async {
                    self?.setupCamera()
                }
            }
        }
    default:
        break
    }
}

private func setupCamera() {
    DispatchQueue.global(qos: .userInitiated).async { [weak self] in
        let session = AVCaptureSession()
        
        // Configure the session for high quality video
        if session.canSetSessionPreset(.high) {
            session.sessionPreset = .high
        }
        
        // Set up video input
        guard let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera,
                                                       for: .video,
                                                       position: .back),
              let videoInput = try? AVCaptureDeviceInput(device: videoDevice) else {
            return
        }
        
        if session.canAddInput(videoInput) {
            session.addInput(videoInput)
        }
        
        // Set up audio input
        if let audioDevice = AVCaptureDevice.default(for: .audio),
           let audioInput = try? AVCaptureDeviceInput(device: audioDevice),
           session.canAddInput(audioInput) {
            session.addInput(audioInput)
        }
        
        // Set up photo output
        let photo = AVCapturePhotoOutput()
        if session.canAddOutput(photo) {
            session.addOutput(photo)
            self?.photoOutput = photo
        }
        
        // Set up video output
        let video = AVCaptureMovieFileOutput()
        if session.canAddOutput(video) {
            session.addOutput(video)
            self?.videoOutput = video
        }
        
        // Create and setup preview layer
        let previewLayer = AVCaptureVideoPreviewLayer(session: session)
        previewLayer.videoGravity = .resizeAspectFill
        if #available(iOS 17.0, *) {
            previewLayer.connection?.videoRotationAngle = 90
        } else {
            previewLayer.connection?.videoOrientation = .portrait
        }
        
        // Start session
        session.startRunning()
        
        DispatchQueue.main.async {
            self?.captureSession = session
            self?.previewLayer = previewLayer
            self?.isCameraAuthorized = true
        }
    }
}

func toggleCamera() {
    guard let session = captureSession else { return }
    
    session.beginConfiguration()
    
    // Remove existing input
    for input in session.inputs {
        session.removeInput(input)
    }
    
    // Switch camera position
    let position: AVCaptureDevice.Position = isFrontCamera ? .back : .front
    guard let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera,
                                                   for: .video,
                                                   position: position),
          let videoInput = try? AVCaptureDeviceInput(device: videoDevice) else {
        return
    }
    
    if session.canAddInput(videoInput) {
        session.addInput(videoInput)
    }
    
    // Re-add audio input
    if let audioDevice = AVCaptureDevice.default(for: .audio),
       let audioInput = try? AVCaptureDeviceInput(device: audioDevice) {
        if session.canAddInput(audioInput) {
            session.addInput(audioInput)
        }
    }
    
    sessionmitConfiguration()
    isFrontCamera.toggle()
}

func toggleFlash() {
    guard let device = AVCaptureDevice.default(for: .video) else { return }
    
    try? device.lockForConfiguration()
    if device.hasTorch {
        if device.torchMode == .off {
            try? device.setTorchModeOn(level: 1.0)
            isFlashOn = true
        } else {
            device.torchMode = .off
            isFlashOn = false
        }
    }
    device.unlockForConfiguration()
}

func capturePhoto() {
    guard let photoOutput = photoOutput else { return }
    
    let settings = AVCapturePhotoSettings()
    if isFlashOn {
        settings.flashMode = .on
    }
    
    photoOutput.capturePhoto(with: settings, delegate: self)
}

func startRecording() {
    guard let videoOutput = videoOutput else { return }
    
    let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("\(UUID().uuidString).mov")
    videoOutput.startRecording(to: tempURL, recordingDelegate: self)
    isRecording = true
    
    // Start progress timer
    recordingProgress = 0
    recordingTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
        guard let self = self else { return }
        self.recordingProgress = min(self.recordingProgress + 0.1/self.maxRecordingDuration, 1.0)
        
        if self.recordingProgress >= 1.0 {
            self.stopRecording()
        }
    }
}

func stopRecording() {
    videoOutput?.stopRecording()
    recordingTimer?.invalidate()
    recordingTimer = nil
    isRecording = false
}

}

// MARK: - Photo Capture Delegate
extension CameraViewModel: AVCapturePhotoCaptureDelegate {
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        guard let imageData = photo.fileDataRepresentation(),
              let image = UIImage(data: imageData) else {
            return
        }
        
        DispatchQueue.main.async {
            self.capturedImage = image
            self.isPreviewingMedia = true
        }
    }
}

// MARK: - Video Recording Delegate
extension CameraViewModel: AVCaptureFileOutputRecordingDelegate {
    func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
        if error == nil {
            DispatchQueue.main.async {
                self.recordedVideoURL = outputFileURL
                self.isPreviewingMedia = true
            }
        }
    }
}

// MARK: - Camera Preview View
struct CameraPreviewView: UIViewRepresentable {
    let previewLayer: AVCaptureVideoPreviewLayer
    
    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        view.backgroundColor = .black
        view.layer.addSublayer(previewLayer)
        return view
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        previewLayer.frame = uiView.bounds
        previewLayer.videoGravity = .resizeAspectFill
        // Ensure layout happens on main thread
        DispatchQueue.main.async {
            uiView.layer.layoutIfNeeded()
        }
    }
}

// MARK: - Progress Bar View
struct RecordingProgressBar: View {
    let progress: CGFloat
    
    var body: some View {
        GeometryReader { geometry in
            ZStack(alignment: .leading) {
                Rectangle()
                    .fill(Color.white.opacity(0.3))
                    .frame(height: 4)
                
                Rectangle()
                    .fill(Color.red)
                    .frame(width: geometry.size.width * progress, height: 4)
            }
        }
        .frame(height: 4)
    }
}

// MARK: - Media Preview View
struct MediaPreviewView: View {
    let image: UIImage?
    let videoURL: URL?
    @Binding var isShowing: Bool
    var onSend: () -> Void
    
    var body: some View {
        ZStack {
            if let image = image {
                Image(uiImage: image)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
            } else if let url = videoURL {
                VideoPlayer(url: url)
            }
            
            VStack {
                HStack {
                    Button(action: { isShowing = false }) {
                        Image(systemName: "xmark")
                            .foregroundColor(.white)
                            .font(.title)
                            .padding()
                    }
                    Spacer()
                }
                Spacer()
                Button(action: onSend) {
                    Text("Send")
                        .font(.headline)
                        .foregroundColor(.white)
                        .padding(.horizontal, 40)
                        .padding(.vertical, 12)
                        .background(Color.blue)
                        .cornerRadius(25)
                }
                .padding(.bottom, 40)
            }
        }
        .edgesIgnoringSafeArea(.all)
        .background(Color.black)
    }
}

// MARK: - Video Player View
struct VideoPlayer: UIViewRepresentable {
    let url: URL
    
    func makeUIView(context: Context) -> UIView {
        let view = UIView(frame: .zero)
        let player = AVPlayer(url: url)
        let playerLayer = AVPlayerLayer(player: player)
        playerLayer.videoGravity = .resizeAspectFill
        view.layer.addSublayer(playerLayer)
        player.play()
        return view
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        if let playerLayer = uiView.layer.sublayers?.first as? AVPlayerLayer {
            playerLayer.frame = uiView.bounds
        }
    }
}

VideoRecordingView.swift

struct VideoRecordingView: View {
let coordinate: CLLocationCoordinate2D
@Environment(\.dismiss) var dismiss
//@StateObject private var videoRecorder = VideoRecorder()
@StateObject private var viewModel = CameraViewModel()
@State private var isRecording = false
@State private var showErrorAlert = false
@State private var errorMessage = ""
@State private var isUploading = false

//@StateObject private var viewModel = CameraViewModel()

var body: some View {
    GeometryReader { geometry in
        ZStack {
            // Camera Preview
            if let previewLayer = viewModel.previewLayer {
                CameraPreviewView(previewLayer: previewLayer)
                    .frame(width: geometry.size.width, height: geometry.size.height)
            } else {
                Color.black
            }
            
            // Recording Progress Bar
            if viewModel.isRecording {
                RecordingProgressBar(progress: viewModel.recordingProgress)
                    .padding(.top, 5)
                    .frame(maxHeight: .infinity, alignment: .top)
            }
            
            // Controls
            VStack {
                // Top controls
                HStack {
                    Button(action: { viewModel.toggleFlash() }) {
                        Image(systemName: viewModel.isFlashOn ? "bolt.fill" : "bolt.slash")
                            .foregroundColor(.white)
                            .font(.system(size: 20))
                            .padding()
                    }
                    Spacer()
                    Button(action: { viewModel.toggleCamera() }) {
                        Image(systemName: "camera.rotate")
                            .foregroundColor(.white)
                            .font(.system(size: 20))
                            .padding()
                    }
                }
                .padding(.top, 44)
                
                Spacer()
                
                // Bottom controls
                HStack {
                    Spacer()
                    
                    // Camera button
                    Button(action: {
                        if viewModel.isRecording {
                            viewModel.stopRecording()
                        } else {
                            viewModel.capturePhoto()
                        }
                    }) {
                        Circle()
                            .strokeBorder(Color.white, lineWidth: 4)
                            .frame(width: 80, height: 80)
                    }
                    .simultaneousGesture(
                        LongPressGesture(minimumDuration: 0.5)
                            .onEnded { _ in
                                viewModel.startRecording()
                            }
                    )
                    
                    Spacer()
                }
                .padding(.bottom, 40)
            }
            
            // Media Preview
            if viewModel.isPreviewingMedia {
                MediaPreviewView(
                    image: viewModel.capturedImage,
                    videoURL: viewModel.recordedVideoURL,
                    isShowing: $viewModel.isPreviewingMedia
                ) {
                    // Handle sending media
                    print("Send media")
                }
            }
        }
发布评论

评论列表(0)

  1. 暂无评论