LockedCameraCapture - Lock Screen Camera Capture UI Question

After the session video, "Build a great Lock Screen camera capture experience", was unclear about the UI.

So do developers need to provide a whole new UI in the extension? The main UI cannot be repurposed?

Answered by Frameworks Engineer in 790448022

Hello!

You're welcome to use your existing UI in a Capture Extension. Make any source files you want to use available to both your app target and your capture extension target. Then you can embed the UI you want to use into your LockedCameraCaptureUIScene.

Just keep in mind that your existing UI may need modification in some important ways, such as transitioning the user to the app when they attempt to do something the extension cannot not support.

Accepted Answer

Hello!

You're welcome to use your existing UI in a Capture Extension. Make any source files you want to use available to both your app target and your capture extension target. Then you can embed the UI you want to use into your LockedCameraCaptureUIScene.

Just keep in mind that your existing UI may need modification in some important ways, such as transitioning the user to the app when they attempt to do something the extension cannot not support.

Thank you for the informative answer.

One last question on this: The UI code doesn't have to be in SwiftUI, correct?

Good question!

The LockedCameraCaptureUIScene does take in a SwiftUI view. However, you can use UIKit code by creating a wrapper that implements either UIViewControllerRepresentable or UIViewRepresentable.

There's an example of this in the documentation where the code wraps UIImagePickerController: https://developer.apple.com/documentation/lockedcameracapture/creating-a-camera-experience-for-the-lock-screen

Somehow related, I'm trying to build an example app to get familiar with this new extension. I followed the steps in the docs but I think I'm missing something.

I added the Control through a Widget Extension, and I see that the perform method is called in my Intent, however I'm missing the part where this perform method would open the UI to capture the photos.

This is my Intent:

struct MyAppCaptureIntent: CameraCaptureIntent {
    static var title: LocalizedStringResource = "MyAppCaptureIntent"

    typealias AppContext = MyAppContext
    static let description = IntentDescription("Capture photos with MyApp.")

    @MainActor
    func perform() async throws -> some IntentResult {
        let dialog = IntentDialog("Intent result")
        do {
            if let context = try await MyAppCaptureIntent.appContext {
                return .result()
            }
        } catch {
             // Handle error condition.
        }
        return .result()
    }
}

struct MyAppContext: Decodable, Encodable {
    var data = ContextData()
}

struct ContextData: IntentResult, Decodable, Encodable {
    var value: Never? {
        nil
    }
}

How do I connect this with my LockedCameraCaptureExtension?

To allow the system to launch your extension or application, your intent should be also included in your app and extension targets. The system is able to use the presence of the intent in your widget extension (passed to your control), as well as in both the app and capture extension to determine whether to launch your app or extension when your control’s action is performed.

It is included in the app target, the widget extension target and the locked screen camera extension target.

I am facing the exact same problem. My intent is included as a target in the widget extension, capture extension and main app. When I press on the control in control center or the lock screen, the main app opens and the intent's perform function is executed.

What am I missing?

For Julio:

The CameraCaptureIntent should be connected in two ways: it should be included in all three targets, and it should be given to your Control in the Widget Extension as the App Intent that needs to be run.

The perform block is for configuring your app if the system launches the app. This would likely be used for switching quickly to a camera UI.

For ashmitz-wbd:

The system may decide to open your app rather than the extension based on whether the device is locked and where the control is pressed.

If you press the control while the device is locked, you should get the capture extension in that context.

Thanks for the quick reply.

it should be given to your Control in the Widget Extension as the App Intent that needs to be run.

I think I'm doing this too. Here the code of my control:

struct LockedCameraWidgetExtensionControl: ControlWidget {
    var body: some ControlWidgetConfiguration {
        StaticControlConfiguration(
            kind: "com.Yuju.LockedCameraPlayground.LockedCameraWidgetExtension") {
                ControlWidgetButton(action: MyAppCaptureIntent()) {
                    Label("Capture", systemImage: "camera")
                }
        }
    }
}

And since we are here, this is my ViewFinder:

struct LockedCameraExtensionViewFinder: UIViewControllerRepresentable {
    let session: LockedCameraCaptureSession
    var sourceType: UIImagePickerController.SourceType = .camera

    init(session: LockedCameraCaptureSession) {
        self.session = session
    }
 
    func makeUIViewController(context: Self.Context) -> UIImagePickerController {
        let imagePicker = UIImagePickerController()
        imagePicker.sourceType = sourceType
        imagePicker.mediaTypes = [UTType.image.identifier, UTType.video.identifier]
        imagePicker.cameraDevice = .rear
 
        return imagePicker
    }
 
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Self.Context) {
    }
}

Make sure you also have a Privacy - Camera Usage Description in both your app and capture extension's Info.plist.

Another thing worth noting, if your app does not have permission to use the camera (the user hasn't been prompted yet, or was denied), then the app will open, not the capture extension.

If you feel like you've covered all of the available ground and it's still not working, feel free to file a Feedback with your code and post the FB number here. We'll do our best to take a look quickly for you!

Yes, I covered that too. I made sure camera permissions where accepted by the user.

Feedback sent -> FB13889233

After using the code provided about I got the same experience that the control widget opens the app. When I tried adding it on the lock screen of the simulator it still opens the app.

Then I tried it on my iPhone. Placing the button in de control center will still open the app. But using it from the lockscreen opens the camera of included in de Locked Camera Capture Extension

hi @codeblocknl ! It doesn't open the camera for me when I place it in the locked screen. Did you use the code I posted in the feedback (FB13889233)? if so, did you modify anything?

Hi codeblocknl,

Yup, that's expected. When the control is pressed, iOS will decide whether to open the app - while running the "perform" block of the CameraCaptureIntent - or the capture extension.

This can vary based on where the button was pressed, and whether the device was locked or unlocked.

For juliomartinezb:

We took a look at your provided test code and confirmed it works when "Privacy - Camera Usage Description" and a valid string value for it is added to the Capture Extension.

BOTH the app and the capture extension need this!

Once you do this, and make sure that you have camera permission in your app, you should see the capture extension launch. 🚀 😊

Currently I still got an issue that when you press Use Photo button after taking the image, the Capture Extension crashes or freezes

Hey codeblocknl:

UIImagePickerController is only an example for Capture Extensions as a starting point. The example code provided doesn't really go anywhere, so to speak, since it's designed to be a modal popup for a larger app. So any behavior that would dismiss the view, like selecting "Use Photo," will cause some wacky behavior. That's expected.

If you want to go deeper into Capture Extensions, the next step would be to start working with AVFoundation's camera API's yourself.

Hello,

I experienced several of the issues described here but the explanations from the Frameworks Engineer were very helpful to debugging, thank you (I think it's retrospectively obvious, but not obvious in the moment, that the app and extension both need the privacy camera usage strings and that the main app has to first receive camera permissions from the user before the extension can inherit them, so as an enhancement I would suggest adding it to the documentation or emphasizing it more if it is in there, along with a request to add a full working example of the implementation of the control widget, and I also stumbled on adding the Widget kind to the WidgetKit env vars in its scheme, although the Xcode error messages were helpful).

My extension and control widget are now working, but I filed feedback FB13958518 about the pin unlock numpad popping up over the extension viewfinder when it works; maybe you would be able to check it out.

Thanks again!

Hi Halle,

Thank you for filling a feedback! The issue you mention is a known issue, listed on the Xcode 16 Release Notes:

https://developer.apple.com/documentation/xcode-release-notes/xcode-16-release-notes

It's under investigation, stay tuned 😀

Your documentation feedback is also appreciated. I'll pass it on.

Whoops, sorry for filing a known issue! Thanks for checking it out, regardless.

LockedCameraCapture - Lock Screen Camera Capture UI Question
 
 
Q