Skip to content

Instantly share code, notes, and snippets.

@jasoneveleth
Last active September 12, 2023 16:42
Show Gist options
  • Save jasoneveleth/4fa2faced568cb1dbd2da296e8f50e88 to your computer and use it in GitHub Desktop.
Save jasoneveleth/4fa2faced568cb1dbd2da296e8f50e88 to your computer and use it in GitHub Desktop.
//
// THIS DOESN'T WORK, see the final comment for fix
//
import SwiftUI
import AVFoundation
struct ContentView: View {
@StateObject private var cameraManager = CameraManager()
var body: some View {
ZStack {
if cameraManager.isSessionInitialized {
VideoPreviewView().edgesIgnoringSafeArea(.all)
} else {
Text("loading cam")
}
RecordButton()
}
.environmentObject(cameraManager)
}
}
class RecordingHandler: NSObject, AVCaptureFileOutputRecordingDelegate {
func fileOutput(_ output: AVCaptureFileOutput, didStartRecordingTo fileURL: URL, from connections: [AVCaptureConnection]) {}
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {}
}
class CameraManager: ObservableObject {
var captureSession = AVCaptureSession()
@Published var isSessionInitialized = false
private var movieOutput: AVCaptureMovieFileOutput?
private var recordingHandler: RecordingHandler
init() {
self.recordingHandler = RecordingHandler()
DispatchQueue.global(qos: .background).async { [weak self] in
guard let self = self else { return }
guard let camera = AVCaptureDevice.default(for: .video) else { return }
do {
let input = try AVCaptureDeviceInput(device: camera)
self.captureSession.addInput(input)
self.movieOutput = AVCaptureMovieFileOutput()
if self.captureSession.canAddOutput(self.movieOutput!) {
self.captureSession.addOutput(self.movieOutput!)
}
// Start running the capture session on the background queue
self.captureSession.startRunning()
DispatchQueue.main.async {
self.isSessionInitialized = true
}
} catch {
print(error.localizedDescription)
}
}
}
func startRecording() {
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let outputURL = documentsDirectory.appendingPathComponent(UUID().uuidString).appendingPathExtension("mov")
movieOutput?.startRecording(to: outputURL, recordingDelegate: recordingHandler)
}
func stopRecording() {
movieOutput?.stopRecording()
}
}
struct RecordButton: View {
@EnvironmentObject private var cameraManager: CameraManager
@State private var isRecording = false
var body: some View {
Button(action: {}) {
Image(systemName: "circle.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 75, height: 75)
.foregroundColor(isRecording ? .white : .red)
.gesture(DragGesture(minimumDistance: 0, coordinateSpace: .local)
.onChanged({ _ in
if !isRecording {
cameraManager.startRecording()
isRecording = true
}
})
.onEnded({ _ in
isRecording = false
cameraManager.stopRecording()
})
)
}
}
}
struct VideoPreviewView: UIViewRepresentable {
@EnvironmentObject var cameraManager: CameraManager
func makeUIView(context: Context) -> UIView {
let view = UIView()
let previewLayer = AVCaptureVideoPreviewLayer(session: cameraManager.captureSession)
previewLayer.frame = view.layer.bounds
previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
view.layer.addSublayer(previewLayer)
return view
}
func updateUIView(_ uiView: UIView, context: Context) {}
}
@jasoneveleth
Copy link
Author

jasoneveleth commented Sep 12, 2023

Another interpretation of that comment is this, which also still has a blank view (but does make the "now" print statement fire).

// ContentView
var body: some View {
    ZStack {
        if cameraManager.isSessionInitialized {
            VideoPreviewView(cameraManager: cameraManager).edgesIgnoringSafeArea(.all)
        } else {
            Text("nope not in this house")
        }
        RecordButton()
    }
    .environmentObject(cameraManager)
}

and

struct VideoPreviewView: UIViewRepresentable {
    let cameraManager: CameraManager
    
    init(cameraManager: CameraManager) {
        self.cameraManager = cameraManager
    }

    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        if cameraManager.isSessionInitialized {
            print("now")
            let previewLayer = AVCaptureVideoPreviewLayer(session: cameraManager.captureSession)
            previewLayer.frame = view.layer.bounds
            previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
            view.layer.addSublayer(previewLayer)
        }
        return view
    }

    func updateUIView(_ uiView: UIView, context: Context) {
    }
}

@jasoneveleth
Copy link
Author

The issue as @vadian pointed out is the bounds were zero since I the view I was taking the bounds from was just initialized. The correct code is a tiny change from my desired modifications. Use UIScreen.main.bounds rather than view.layer.bounds. In the initial code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment