Home > database >  Await StateObject initialization in SwiftUI
Await StateObject initialization in SwiftUI

Time:09-08

I am working on an app that uses an AVCaptureLayer to display a video preview layer and also a text representation of the brightness (calculated from the video metadata). I've tried to make my app more efficient by only creating one AVCaptureSession in a class VideoStream and I want to then pass the capture session to a UIViewRepresentable that creates a UIView showing the video preview layer. The View is straightforward:

struct ContentView: View {
    @StateObject var videoStream = VideoStream() // holds session variable for AVCaptureSession
    
    var body: some View {
        VStack {
            VideoPreviewHolder(runningSession: videoStream.session) // videoStream.session not initialized at build time
        }.frame(width: 300, height: 300, alignment: .center)
        Text(String(format: "%.2f Lux", videoStream.luminosityReading))
            .font(.largeTitle)
    }
    
}

Does SwiftUI provide a way to await the initialization of a StateObject class? The init() method for VideoStream() calls a function responsible for first checking for camera authorization and if necessary requesting it, and if authorization is granted then a second function is called which configures the AVCaptureSession. This all takes quite a bit of time, especially if the user needs to grant or deny the authorization.

I've tried adding await / async to the VideoStream() initializer and the functions called therein, but since my VideoStream() class inherits from NSObject, I receive an error that async initializer cannot be represented in Objective-C.

Is there a SwiftUI specific way of awaiting the StateObject's initialization?

CodePudding user response:

Make VideoStream's session property optional and published:

class VideoStream: ObservableObject {
    @Published var session: AVCaptureSession?

    // blah blah blah
}

Don't set the property until the session is fully configured. Make sure you dispatch back to the main queue/thread/actor to set it if needed.

Then make your view check whether the property is nil:

struct ContentView: View {
    @StateObject var videoStream = VideoStream()
    
    var body: some View {
        if let session = videoStream.session {
            VStack {
                VideoPreviewHolder(runningSession: session)
            }.frame(width: 300, height: 300, alignment: .center)
            Text(String(format: "%.2f Lux", videoStream.luminosityReading))
                .font(.largeTitle)
        } else {
            ProgressView()
        }
    }
    
}
  • Related