Reputation: 233
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
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