-3.1 C
New York
Thursday, January 30, 2025

ios – Swift UI Digital camera Preview Black Display


I get a problem for my swift ui app the place I’m attempting to implement a video recording performance inside my app. I’m not positive why its not working. I get a black preview display screen every time the digicam opens up.

Simply to be clear I’ve added the digicam and microphone utilization description on my plist file. I’ve added the view and controller code right here. If anybody has handled this earlier than, I might actually admire the assistance.

CameraViewMode.swift

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

non-public var captureSession: AVCaptureSession?
non-public var videoOutput: AVCaptureMovieFileOutput?
non-public var photoOutput: AVCapturePhotoOutput?
non-public var recordingTimer: Timer?
non-public let maxRecordingDuration: CGFloat = 10.0 // Most recording length in seconds

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

non-public func checkPermissions() {
    swap AVCaptureDevice.authorizationStatus(for: .video) {
    case .approved:
        setupCamera()
    case .notDetermined:
        AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
            if granted {
                DispatchQueue.important.async {
                    self?.setupCamera()
                }
            }
        }
    default:
        break
    }
}

non-public func setupCamera() {
    DispatchQueue.world(qos: .userInitiated).async { [weak self] in
        let session = AVCaptureSession()
        
        // Configure the session for top of the range video
        if session.canSetSessionPreset(.excessive) {
            session.sessionPreset = .excessive
        }
        
        // Arrange video enter
        guard let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera,
                                                       for: .video,
                                                       place: .again),
              let videoInput = attempt? AVCaptureDeviceInput(machine: videoDevice) else {
            return
        }
        
        if session.canAddInput(videoInput) {
            session.addInput(videoInput)
        }
        
        // Arrange audio enter
        if let audioDevice = AVCaptureDevice.default(for: .audio),
           let audioInput = attempt? AVCaptureDeviceInput(machine: audioDevice),
           session.canAddInput(audioInput) {
            session.addInput(audioInput)
        }
        
        // Arrange picture output
        let picture = AVCapturePhotoOutput()
        if session.canAddOutput(picture) {
            session.addOutput(picture)
            self?.photoOutput = picture
        }
        
        // Arrange 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 #obtainable(iOS 17.0, *) {
            previewLayer.connection?.videoRotationAngle = 90
        } else {
            previewLayer.connection?.videoOrientation = .portrait
        }
        
        // Begin session
        session.startRunning()
        
        DispatchQueue.important.async {
            self?.captureSession = session
            self?.previewLayer = previewLayer
            self?.isCameraAuthorized = true
        }
    }
}

func toggleCamera() {
    guard let session = captureSession else { return }
    
    session.beginConfiguration()
    
    // Take away current enter
    for enter in session.inputs {
        session.removeInput(enter)
    }
    
    // Swap digicam place
    let place: AVCaptureDevice.Place = isFrontCamera ? .again : .entrance
    guard let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera,
                                                   for: .video,
                                                   place: place),
          let videoInput = attempt? AVCaptureDeviceInput(machine: videoDevice) else {
        return
    }
    
    if session.canAddInput(videoInput) {
        session.addInput(videoInput)
    }
    
    // Re-add audio enter
    if let audioDevice = AVCaptureDevice.default(for: .audio),
       let audioInput = attempt? AVCaptureDeviceInput(machine: audioDevice) {
        if session.canAddInput(audioInput) {
            session.addInput(audioInput)
        }
    }
    
    session.commitConfiguration()
    isFrontCamera.toggle()
}

func toggleFlash() {
    guard let machine = AVCaptureDevice.default(for: .video) else { return }
    
    attempt? machine.lockForConfiguration()
    if machine.hasTorch {
        if machine.torchMode == .off {
            attempt? machine.setTorchModeOn(degree: 1.0)
            isFlashOn = true
        } else {
            machine.torchMode = .off
            isFlashOn = false
        }
    }
    machine.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
    
    // Begin 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: - Photograph Seize Delegate
extension CameraViewModel: AVCapturePhotoCaptureDelegate {
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto picture: AVCapturePhoto, error: Error?) {
        guard let imageData = picture.fileDataRepresentation(),
              let picture = UIImage(knowledge: imageData) else {
            return
        }
        
        DispatchQueue.important.async {
            self.capturedImage = picture
            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.important.async {
                self.recordedVideoURL = outputFileURL
                self.isPreviewingMedia = true
            }
        }
    }
}

// MARK: - Digital 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.body = uiView.bounds
        previewLayer.videoGravity = .resizeAspectFill
        // Guarantee format occurs on important thread
        DispatchQueue.important.async {
            uiView.layer.layoutIfNeeded()
        }
    }
}

// MARK: - Progress Bar View
struct RecordingProgressBar: View {
    let progress: CGFloat
    
    var physique: some View {
        GeometryReader { geometry in
            ZStack(alignment: .main) {
                Rectangle()
                    .fill(Colour.white.opacity(0.3))
                    .body(top: 4)
                
                Rectangle()
                    .fill(Colour.pink)
                    .body(width: geometry.dimension.width * progress, top: 4)
            }
        }
        .body(top: 4)
    }
}

// MARK: - Media Preview View
struct MediaPreviewView: View {
    let picture: UIImage?
    let videoURL: URL?
    @Binding var isShowing: Bool
    var onSend: () -> Void
    
    var physique: some View {
        ZStack {
            if let picture = picture {
                Picture(uiImage: picture)
                    .resizable()
                    .aspectRatio(contentMode: .match)
            } else if let url = videoURL {
                VideoPlayer(url: url)
            }
            
            VStack {
                HStack {
                    Button(motion: { isShowing = false }) {
                        Picture(systemName: "xmark")
                            .foregroundColor(.white)
                            .font(.title)
                            .padding()
                    }
                    Spacer()
                }
                Spacer()
                Button(motion: onSend) {
                    Textual content("Ship")
                        .font(.headline)
                        .foregroundColor(.white)
                        .padding(.horizontal, 40)
                        .padding(.vertical, 12)
                        .background(Colour.blue)
                        .cornerRadius(25)
                }
                .padding(.backside, 40)
            }
        }
        .edgesIgnoringSafeArea(.all)
        .background(Colour.black)
    }
}

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

VideoRecordingView.swift

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

//@StateObject non-public var viewModel = CameraViewModel()

var physique: some View {
    GeometryReader { geometry in
        ZStack {
            // Digital camera Preview
            if let previewLayer = viewModel.previewLayer {
                CameraPreviewView(previewLayer: previewLayer)
                    .body(width: geometry.dimension.width, top: geometry.dimension.top)
            } else {
                Colour.black
            }
            
            // Recording Progress Bar
            if viewModel.isRecording {
                RecordingProgressBar(progress: viewModel.recordingProgress)
                    .padding(.prime, 5)
                    .body(maxHeight: .infinity, alignment: .prime)
            }
            
            // Controls
            VStack {
                // High controls
                HStack {
                    Button(motion: { viewModel.toggleFlash() }) {
                        Picture(systemName: viewModel.isFlashOn ? "bolt.fill" : "bolt.slash")
                            .foregroundColor(.white)
                            .font(.system(dimension: 20))
                            .padding()
                    }
                    Spacer()
                    Button(motion: { viewModel.toggleCamera() }) {
                        Picture(systemName: "digicam.rotate")
                            .foregroundColor(.white)
                            .font(.system(dimension: 20))
                            .padding()
                    }
                }
                .padding(.prime, 44)
                
                Spacer()
                
                // Backside controls
                HStack {
                    Spacer()
                    
                    // Digital camera button
                    Button(motion: {
                        if viewModel.isRecording {
                            viewModel.stopRecording()
                        } else {
                            viewModel.capturePhoto()
                        }
                    }) {
                        Circle()
                            .strokeBorder(Colour.white, lineWidth: 4)
                            .body(width: 80, top: 80)
                    }
                    .simultaneousGesture(
                        LongPressGesture(minimumDuration: 0.5)
                            .onEnded { _ in
                                viewModel.startRecording()
                            }
                    )
                    
                    Spacer()
                }
                .padding(.backside, 40)
            }
            
            // Media Preview
            if viewModel.isPreviewingMedia {
                MediaPreviewView(
                    picture: viewModel.capturedImage,
                    videoURL: viewModel.recordedVideoURL,
                    isShowing: $viewModel.isPreviewingMedia
                ) {
                    // Deal with sending media
                    print("Ship media")
                }
            }
        }

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles