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

Posted

技术标签:

【中文标题】显示另一个 swiftUI 视图时如何停止相机工作?【英文标题】:How to stop camera working when displaying another swiftUI view? 【发布时间】:2021-07-19 12:56:10 【问题描述】:

我有一个超级简单的应用程序,它可以拍照并将其保存到 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()
    

【问题讨论】:

【参考方案1】:

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

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


现在,为启停会话再添加一个状态变量。 工作表上的更新状态变量出现 - 消失的方法。

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

【讨论】:

非常感谢 Raya ? 但我们有一个小问题,当我们关闭设置视图时相机开始冻结一两秒

以上是关于显示另一个 swiftUI 视图时如何停止相机工作?的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI 检测视图何时不可见(有点视图会消失)并停止发布者

使用 SwiftUI 时尝试在视图中显示工作表时应用程序崩溃

如何在 SwiftUI 中将数据从视图传递到另一个视图?

SwiftUI - 如何关闭工作表视图,同时关闭该视图

SwiftUI:关闭第一个工作表时如何显示第二个工作表

TabView 在 iOS13 SwiftUI 上无法正常工作