UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Camera activated on views without camera

Forums > Swift

Hi all, I am currently build an app that uses the camera and when I am on views where there is no camera view object, and I update the view via either a toggle or a button press, the green camera indicator randomly turns on. It happens in multiple views and I have tried serveral options to get it to stop happening.

Has anyone experienced this before? Any ideas?

This is the camera struct I am using if this helps:

struct CameraView: UIViewControllerRepresentable {
    let captureSession = AVCaptureSession()

    func startCamera() {
        DispatchQueue.global(qos: .userInitiated).async {
            self.captureSession.startRunning()
        }
    }

    func stopCamera() {
        DispatchQueue.global(qos: .userInitiated).async {
            self.captureSession.stopRunning()
        }
    }

    func makeUIViewController(context: Context) -> UIViewController {
        let viewController = UIViewController()

        guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return viewController }

        let videoInput: AVCaptureDeviceInput
        do {
            videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
        } catch {
            return viewController
        }

        if captureSession.canAddInput(videoInput) {
            captureSession.addInput(videoInput)
        } else {
            return viewController
        }

        let metadataOutput = AVCaptureMetadataOutput()
        if captureSession.canAddOutput(metadataOutput) {
            captureSession.addOutput(metadataOutput)

            metadataOutput.setMetadataObjectsDelegate(context.coordinator, queue: DispatchQueue.main)
            metadataOutput.metadataObjectTypes = [.qr]
        } else {
            return viewController
        }

        let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        previewLayer.frame = viewController.view.layer.bounds
        previewLayer.videoGravity = .resizeAspectFill
        viewController.view.layer.addSublayer(previewLayer)

        DispatchQueue.global(qos: .userInitiated).async {
            self.captureSession.startRunning()
        }

        return viewController
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate {
        var parent: CameraView
        private var isReadingQRCode = false

        init(_ parent: CameraView) {
            self.parent = parent
        }

        func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {

            if isReadingQRCode {
                return
            }

            if let metadataObject = metadataObjects.first {
                guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }
                guard let stringValue = readableObject.stringValue else { return }
                print("QR Code: \(stringValue)") // TODO: THIS IS WHERE I HANDLE WHAT TO DO WITH QR READ
                performHapticFeedback()

                isReadingQRCode = true
                DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                    self.isReadingQRCode = false
                }
            }
        }

        func performHapticFeedback() {
            let generator = UIImpactFeedbackGenerator(style: .medium)
            generator.prepare()
            generator.impactOccurred()
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                generator.impactOccurred()
            }
        }
    }
}

3      

I found the problem and a solution:

.onAppear is and has been bugged for some time and it was being called for views that DID have the camera randomly when I would open other views.

SOLUTION: I used .task as a replacement to .onAppear and asyncronously called .startCamera() there. .onDisappear works as intended so I left it as a solution to calling .stopCamera()

.task {
    if appSettings.cameraAuthorized {
        await startCameraAsync()
    }
}

And

private func startCameraAsync() async {
    await withTaskGroup(of: Void.self) { group in
        group.addTask {
            await cameraView.startCamera()
        }
    }
}

3      

The green camera indicator randomly turning on in your SwiftUI app could be due to the AVCaptureSession not being properly stopped when navigating away from views with CameraView. Ensure that you call stopCamera() when the view disappears. Add the following to your CameraView:

func onDisappear() {
    stopCamera()
}

And in your UIViewControllerRepresentable, implement the UIViewControllerRepresentable protocol's onDisappear function:

func onDisappear(perform action: (() -> Void)?) -> some View {
    return modifier(OnDisappearModifier(perform: action))
}

Now, use this onDisappear modifier in your SwiftUI views where you have the CameraView:

CameraView()
    .onDisappear {
        stopCamera()
    }

This ensures that the camera session stops properly when the view disappears, preventing the green indicator from randomly appearing.

3      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.