1

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?

3
  • It already does await the initialization of the @StateObject. What you must be looking for is awaiting something else async that is being dispatched inside the init of VideoStream (like asking for authorization). The code here is not enough to represent the issue. Commented Sep 7, 2022 at 22:23
  • 1
    I suggest using the alternative approach of not putting your (camera and configure) functions in the init() of VideoStream. Put these in the .onAppear{...} or a .task{...} of the View instead. There, you can use the await/async if needed. Commented Sep 7, 2022 at 22:42
  • 1
    Use an ‘if let’ that way once session loads it will show the view, else’ can show a progress view or a message while the session is initialized. Using async await would require major work. (Based on your last question). Commented Sep 7, 2022 at 22:48

1 Answer 1

2

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()
        }
    }
    
}
Sign up to request clarification or add additional context in comments.

1 Comment

This did the trick. Small typo in @Published you may want to correct. Thanks for the solution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.