显示另一个 swiftUI 视图时如何停止相机工作?

How to stop camera working when displaying another swiftUI view?

我有一个超级简单的应用程序,它可以拍照并将其保存到 Apple 照片库,并且有一个设置按钮可以进入设置视图。

所以,我的问题是,当我在fullScreenCover中打开设置时,相机仍然在工作(绿色隐私指示灯没有消失),如果您有一个优雅的解决方案,请告诉我,将不胜感激。

代码如下:

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()
    }
}

添加一种额外的停止方法 captureSession

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

现在,为启停会话添加一个状态变量。 在 sheet 出现时更新状态变量 - 消失的方法。

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
            }
        }
    }
}

绑定状态变量

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
}