ViOS
ViOS

Reputation: 233

How to stop camera working when displaying another swiftUI view?

I'm having super simple app that can be able to take photos and save it to Apple Photo Library and have a settings button that will take to setting view.

So, my problem is when I open Setting in fullScreenCover then camera is still working(the green privacy indicator is not gone), please let me know if you have an elegant solution, will appreciate it a lot.

Here's the code:

import SwiftUI

struct ContentView: View {
    let cameraController: CustomCameraController
    @ObservedObject var cameraViewModel: CameraViewModel
    
    var body: some View {
        CameraView(cameraController: cameraController, cameraViewModel: cameraViewModel)
    }
}

struct CameraView: View {
    @State private var didTapCapture = false
    @State private var didTapSettings = false
    let cameraController: CustomCameraController
    @ObservedObject var cameraViewModel: CameraViewModel
    
    var body: some View {
        VStack {
            ZStack {
                CameraPreviewRepresentable(didTapCapture: $didTapCapture, cameraViewModel: cameraViewModel, cameraController: cameraController)
                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
                
            }
            .edgesIgnoringSafeArea(.all)
            
            Spacer()
            
            HStack(alignment: .center) {
                SettingsButton(didTap: $didTapSettings)
                    .frame(width: 80, height: 80, alignment: .leading)
                
                Spacer()
                
            }
            .overlay(
                CaptureButton(didTapCapture: $didTapCapture)
                        .frame(width: 100, height: 100, alignment: .center)
                        .padding(.bottom, 20)
            )
        }
        .fullScreenCover(isPresented: $didTapSettings) {
            VStack(spacing: 30) {
                Text("Settings View ⚙️")
                    .font(.largeTitle)
                
                Button("OK") {
                    didTapSettings.toggle()
                }
                
            }
        }
    }
}

struct CaptureButton: View {
    @Binding var didTapCapture: Bool
    
    var body: some View {
        Button {
            didTapCapture.toggle()
            
        } label: {
            Image(systemName: "camera")
                .font(.largeTitle)
                .padding(30)
                .background(Color.red)
                .foregroundColor(.white)
                .clipShape(Circle())
                .overlay(
                    Circle()
                        .stroke(Color.red)
                )
        }
    }
}

struct SettingsButton: View {
    @Binding var didTap: Bool
    
    var body: some View {
        Button {
            didTap.toggle()
            
        } label: {
            Image(systemName: "gearshape.2")
                .font(.largeTitle)
                .padding(30)
                .foregroundColor(.white)
        }
    }
}


import SwiftUI
import Combine
import AVFoundation

struct CameraPreviewRepresentable: UIViewControllerRepresentable {
    
    @Environment(\.presentationMode) var presentationMode
    @Binding var didTapCapture: Bool
    @ObservedObject var cameraViewModel: CameraViewModel
    
    let cameraController: CustomCameraController
    
    func makeUIViewController(context: Context) -> CustomCameraController {
        cameraController.delegate = context.coordinator
        
        return cameraController
    }
    
    func updateUIViewController(_ cameraViewController: CustomCameraController, context: Context) {
        
        if didTapCapture {
            cameraViewController.didTapRecord()
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self, cameraViewModel: cameraViewModel)
    }
    
    class Coordinator: NSObject, UINavigationControllerDelegate, AVCapturePhotoCaptureDelegate {
        let parent: CameraPreviewRepresentable
        var cameraViewModel: CameraViewModel
        
        var tokens = Set<AnyCancellable>()
        
        init(_ parent: CameraPreviewRepresentable, cameraViewModel: CameraViewModel) {
            self.parent = parent
            self.cameraViewModel = cameraViewModel
            super.init()
        }
        
        func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
            
            parent.didTapCapture = false
            
            if let imageData = photo.fileDataRepresentation(), let image = UIImage(data: imageData) {
                UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
            }
            
            parent.presentationMode.wrappedValue.dismiss()
        }
    }
}

import Combine
import AVFoundation

class CameraViewModel: ObservableObject {
    @Published var exposureTargetOffset: Float = 0
}

import UIKit
import Combine
import AVFoundation

class CustomCameraController: UIViewController {
    
    var image: UIImage?
    
    var captureSession = AVCaptureSession()
    var backCamera: AVCaptureDevice?
    var frontCamera: AVCaptureDevice?
    lazy var currentCamera: AnyPublisher<AVCaptureDevice?, Never> = currentCameraSubject.eraseToAnyPublisher()
    var photoOutput: AVCapturePhotoOutput?
    var cameraPreviewLayer: AVCaptureVideoPreviewLayer?
    private var currentCameraSubject = CurrentValueSubject<AVCaptureDevice?, Never>(nil)
    
    //DELEGATE
    var delegate: AVCapturePhotoCaptureDelegate?
    
    func didTapRecord() {
        
        let settings = AVCapturePhotoSettings()
        photoOutput?.capturePhoto(with: settings, delegate: delegate!)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setup()
    }
    
    func setup() {
        
        setupCaptureSession()
        setupDevice()
        setupInputOutput()
        setupPreviewLayer()
        startRunningCaptureSession()
    }
    
    func setupCaptureSession() {
        captureSession.sessionPreset = .photo
    }
    
    func setupDevice() {
        let deviceDiscoverySession =
            AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera],
                                                                      mediaType: .video,
                                                                      position: .unspecified)
        for device in deviceDiscoverySession.devices {
            
            switch device.position {
            case .front:
                self.frontCamera = device
            case .back:
                self.backCamera = device
            default:
                break
            }
        }
        
        self.currentCameraSubject.send(self.backCamera)
    }
    
    func setupInputOutput() {
        do {
          let captureDeviceInput = try AVCaptureDeviceInput(device: currentCameraSubject.value!)
          captureSession.addInput(captureDeviceInput)
          photoOutput = AVCapturePhotoOutput()
          captureSession.addOutput(photoOutput!)
        } catch {
          print(error)
        }
         
      }
    
    func setupPreviewLayer() {
        
        self.cameraPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        self.cameraPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
        
        let deviceOrientation = UIDevice.current.orientation
        cameraPreviewLayer?.connection?.videoOrientation = AVCaptureVideoOrientation(rawValue: deviceOrientation.rawValue)!
        
        self.cameraPreviewLayer?.frame = self.view.frame
        self.view.layer.insertSublayer(cameraPreviewLayer!, at: 0)
    }
    
    func startRunningCaptureSession() {
        captureSession.startRunning()
    }
}

Upvotes: 4

Views: 1071

Answers (1)

Raja Kishan
Raja Kishan

Reputation: 18924

Add one extra method for stop captureSession.

class CustomCameraController: UIViewController {
    
  /// Other code
    
    func startRunningCaptureSession() {
        captureSession.startRunning()
    }
    
    func stopCaptureSession() { // <<== Here
        if captureSession.isRunning {
            captureSession.stopRunning()
        }
    }
}

Now, add one more state var for start-stop session. Update state var on sheet appear - disappeared method.

struct CameraView: View {
    @State private var didTapCapture = false
    @State private var didTapSettings = false
    let cameraController: CustomCameraController
    @ObservedObject var cameraViewModel: CameraViewModel
    
    @State private var isRunning = true // << Here
    
    var body: some View {
        VStack {
            ZStack {
                CameraPreviewRepresentable(isRunning: $isRunning, didTapCapture: $didTapCapture, cameraViewModel: cameraViewModel, cameraController: cameraController) // << Here
                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
                
            }
            .edgesIgnoringSafeArea(.all)
            
            Spacer()
            
            HStack(alignment: .center) {
                SettingsButton(didTap: $didTapSettings)
                    .frame(width: 80, height: 80, alignment: .leading)
                
                Spacer()
                
            }
            .overlay(
                CaptureButton(didTapCapture: $didTapCapture)
                    .frame(width: 100, height: 100, alignment: .center)
                    .padding(.bottom, 20)
            )
        }
        .fullScreenCover(isPresented: $didTapSettings) {
            VStack(spacing: 30) {
                Text("Settings View ⚙️")
                    .font(.largeTitle)
                
                Button("OK") {
                    didTapSettings.toggle()
                }
                
            }.onDisappear() { // << Here
                isRunning = true
            }
            .onAppear() { // << Here
                isRunning = false
            }
        }
    }
}

Binding state var.

struct CameraPreviewRepresentable: UIViewControllerRepresentable {
    @Binding var isRunning: Bool // <--- Here
 
    /// Other code
    
    func updateUIViewController(_ cameraViewController: CustomCameraController, context: Context) {
        if isRunning {  // <--- Here
            cameraController.startRunningCaptureSession()
            if didTapCapture {
                cameraViewController.didTapRecord()
            }
        } else {
            cameraController.stopCaptureSession()
        }
    }
    
    /// Other code
}

Upvotes: 2

Related Questions