### Integrate Configuration in App and Extension Source: https://github.com/juniperphoton/lockedcameracaptureextensiondemo/blob/main/README.md Example implementation of the main application and the Capture Extension entry points, demonstrating how to pass the AppStorageConfigProvider to the view hierarchy. ```swift @main struct LockedCameraCaptureExtensionDemoApp: App { var body: some Scene { WindowGroup { ContentView(configProvider: AppStorageConfigProvider.standard) } } } @main struct LockedExtension: LockedCameraCaptureExtension { var body: some LockedCameraCaptureExtensionScene { LockedCameraCaptureUIScene { session in LockedCameraCaptureView(session: session) } } } struct LockedCameraCaptureView: View { let session: LockedCameraCaptureSession var body: some View { ContentView(configProvider: AppStorageConfigProvider(session)) } } ``` -------------------------------- ### MainViewModel Camera Session Management (Swift) Source: https://context7.com/juniperphoton/lockedcameracaptureextensiondemo/llms.txt Manages the camera session, including setup, photo capture, and configuration synchronization. It handles camera permissions, position toggling, and updates application context using UserDefaults and AppCaptureIntent. Requires SwiftUI, AVFoundation, Photos, LockedCameraCapture, and Models frameworks. ```swift import SwiftUI import AVFoundation import Photos import LockedCameraCapture import Models class MainViewModel: ObservableObject { @Published var showNoPermissionHint: Bool = false @Published var cameraPosition: CameraPosition = .back { didSet { Task { await updateAppContext() } } } @Published var isSettingUpCamera = false @Published var showFlashScreen = false private var session: AVCaptureSession? = nil private var photoOutput: AVCapturePhotoOutput? = nil @MainActor func setup() async { guard await requestForPermission() else { showNoPermissionHint = true return } await setupCameraSession() } @MainActor func capturePhoto() async { guard let photoOutput = photoOutput else { return } let settings = AVCapturePhotoSettings() photoOutput.capturePhoto(with: settings, delegate: captureProcessor) self.showFlashScreen = true try? await Task.sleep(for: .seconds(0.2)) self.showFlashScreen = false } @MainActor func toggleCameraPositionSwitch() async { self.cameraPosition = self.cameraPosition == .back ? .front : .back await reconfigureCamera() } @MainActor func updateFromAppContext() async { #if !CAPTURE_EXTENSION // Main app: read from UserDefaults first self.cameraPosition = AppUserDefaultSettings.shared.cameraPosition #endif if #available(iOS 18, *) { do { if let appContext = try await AppCaptureIntent.appContext { self.cameraPosition = appContext.cameraPosition } } catch { print("error on getting app context") } } } @MainActor private func updateAppContext() async { #if !CAPTURE_EXTENSION AppUserDefaultSettings.shared.cameraPosition = self.cameraPosition #endif if #available(iOS 18, *) { let appContext = AppCaptureIntent.AppContext(cameraPosition: cameraPosition) try? await AppCaptureIntent.updateAppContext(appContext) } } private func requestForPermission() async -> Bool { let photoLibraryPermission = await PHPhotoLibrary.requestAuthorization(for: .addOnly) let cameraPermission = await AVCaptureDevice.requestAccess(for: .video) return photoLibraryPermission == .authorized && cameraPermission } } ``` -------------------------------- ### CaptureProcessor Photo Delegate (Swift) Source: https://context7.com/juniperphoton/lockedcameracaptureextensiondemo/llms.txt Handles captured photos by processing them and saving them to the Photo Library. It uses the AppStorageConfigProvider to get the appropriate storage URL. This class conforms to AVCapturePhotoCaptureDelegate and requires AVFoundation and Photos frameworks. ```swift import AVFoundation import Photos class CaptureProcessor: NSObject, ObservableObject, AVCapturePhotoCaptureDelegate { @Published var saveResultText: String = "" let configProvider: AppStorageConfigProvider init(configProvider: AppStorageConfigProvider) { self.configProvider = configProvider } func photoOutput( _ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: (any Error)? ) { if let error = error { print("photoOutput error (error)") return } Task { @MainActor in await savePhotoToLibrary(photo) } } @MainActor private func savePhotoToLibrary(_ photo: AVCapturePhoto) async { guard let photoData = photo.fileDataRepresentation() else { return } // Save to temporary file using session-provided URL guard let fileURL = configProvider.rootURL?.appendingPathComponent( UUID().uuidString, conformingTo: .heic ) else { return } do { try photoData.write(to: fileURL) } catch { saveResultText = "Failed to write file" return } // Save to Photo Library PHPhotoLibrary.shared().performChanges { PHAssetCreationRequest.creationRequestForAssetFromImage(atFileURL: fileURL) } completionHandler: { success, error in DispatchQueue.main.async { self.saveResultText = success ? "Saved to Photo Library" : "Failed to save" } } } } ``` -------------------------------- ### Manage Configuration State with AppContext Source: https://github.com/juniperphoton/lockedcameracaptureextensiondemo/blob/main/README.md Demonstrates how to read configuration values from CameraCaptureIntent.appContext and UserDefaults, ensuring compatibility across the main app and the Capture Extension. ```Swift @MainActor func updateFromAppContext() async { #if !CAPTURE_EXTENSION // If it's in the main app, first read the value from the UserDefaults. self.cameraPosition = AppUserDefaultSettings.shared.cameraPosition #endif if #available(iOS 18, *) { do { // If `AppCaptureIntent.appContext` exists, then read from it. if let appContext = try await AppCaptureIntent.appContext { self.cameraPosition = appContext.cameraPosition } } catch { print("error on getting app context") } } } ``` -------------------------------- ### Implement LockedCameraCaptureExtension Entry Point Source: https://context7.com/juniperphoton/lockedcameracaptureextensiondemo/llms.txt Defines the main entry point for the Capture Extension using the LockedCameraCaptureExtension protocol. It initializes the UI scene and injects the session into the view hierarchy while manually setting the scene phase to active. ```swift import Foundation import LockedCameraCapture import SwiftUI @main struct LockedExtension: LockedCameraCaptureExtension { var body: some LockedCameraCaptureExtensionScene { LockedCameraCaptureUIScene { session in LockedCameraCaptureView(session: session) } } } struct LockedCameraCaptureView: View { let session: LockedCameraCaptureSession var body: some View { ContentView(configProvider: AppStorageConfigProvider(session)) .environment(\.scenePhase, .active) .environment(\.openMainApp, OpenMainAppAction(session: session)) } } ``` -------------------------------- ### Share Configuration with CameraCaptureIntent Source: https://context7.com/juniperphoton/lockedcameracaptureextensiondemo/llms.txt Demonstrates how to share configuration data between the main app and the extension using CameraCaptureIntent. This approach bypasses privacy restrictions on UserDefaults and AppGroups by providing a structured way to update and read app context. ```swift import AppIntents import AVFoundation import Models @available(iOS 18, *) struct AppCaptureIntent: CameraCaptureIntent { struct MyAppContext: Codable, Sendable { public private(set) var cameraPosition: CameraPosition = .back public init(cameraPosition: CameraPosition) { self.cameraPosition = cameraPosition } } typealias AppContext = MyAppContext static let title: LocalizedStringResource = "AppCaptureIntent" static let description = IntentDescription("Capture photos with MyApp.") @MainActor func perform() async throws -> some IntentResult { return .result() } } @MainActor func updateAppContext(cameraPosition: CameraPosition) async { if #available(iOS 18, *) { let appContext = AppCaptureIntent.AppContext(cameraPosition: cameraPosition) do { try await AppCaptureIntent.updateAppContext(appContext) } catch { print("error on updating app context \(error)") } } } @MainActor func readFromAppContext() async -> CameraPosition? { if #available(iOS 18, *) { do { if let appContext = try await AppCaptureIntent.appContext { return appContext.cameraPosition } } catch { print("error on getting app context") } } return nil } ``` -------------------------------- ### Implement AppStorageConfigProvider for Dependency Injection Source: https://github.com/juniperphoton/lockedcameracaptureextensiondemo/blob/main/README.md A configuration provider structure used to inject the correct root URL into the app. It supports initialization from a LockedCameraCaptureSession to ensure the extension uses the session-specific content URL. ```swift struct AppStorageConfigProvider { let rootURL: URL? } extension AppStorageConfigProvider { static let standard = AppStorageConfigProvider( rootURL: getStandardRootURL() ) } @available(iOS 18, *) extension AppStorageConfigProvider { init(_ session: LockedCameraCaptureSession) { self.rootURL = session.sessionContentURL } } ``` -------------------------------- ### Retrieve Root URLs for File Storage in Swift Source: https://github.com/juniperphoton/lockedcameracaptureextensiondemo/blob/main/README.md Functions to obtain the standard document directory or a shared app group container URL for file storage. These methods provide the necessary base paths for saving captured media. ```swift func getRootURL() -> URL? { try? FileManager.default.url( for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true ) } func getSharedRootURL() -> URL? { try? FileManager.default.containerURL( forSecurityApplicationGroupIdentifier: "com.juniperphoton.appgroup" ) } ``` -------------------------------- ### Implement Custom Environment Action for App Navigation Source: https://context7.com/juniperphoton/lockedcameracaptureextensiondemo/llms.txt Defines a custom EnvironmentValue to trigger the main application from a Locked Camera Capture Extension. It uses NSUserActivity and LockedCameraCaptureSession to handle the transition safely. ```swift import SwiftUI import LockedCameraCapture struct OpenMainAppAction { private var action: (NSUserActivity) -> Void init() { self.init { _ in } } @available(iOS 18, *) init(session: LockedCameraCaptureSession) { self.init { activity in Task { do { try await session.openApplication(for: activity) } catch { print("failed to open application \(error)") } } } } init(action: @escaping (NSUserActivity) -> Void) { self.action = action } func callAsFunction(_ activity: NSUserActivity) { self.action(activity) } } struct OpenMainAppActionKey: EnvironmentKey { static var defaultValue: OpenMainAppAction = OpenMainAppAction() } extension EnvironmentValues { var openMainApp: OpenMainAppAction { get { self[OpenMainAppActionKey.self] } set { self[OpenMainAppActionKey.self] = newValue } } } ``` -------------------------------- ### Create Control Widget for Lock Screen Source: https://context7.com/juniperphoton/lockedcameracaptureextensiondemo/llms.txt Configures a ControlWidget to launch a camera capture intent from the lock screen or Control Center. Requires iOS 18 and the WidgetKit framework. ```swift import WidgetKit import SwiftUI @main struct LockedWidgetBundle: WidgetBundle { var body: some Widget { if #available(iOS 18, *) { LockedWidgetControl() } } } @available(iOS 18, *) struct LockedWidgetControl: ControlWidget { var body: some ControlWidgetConfiguration { StaticControlConfiguration( kind: "com.yourapp.widget.control" ) { ControlWidgetButton(action: AppCaptureIntent()) { Label("Launch App", systemImage: "camera.shutter.button") } } .displayName("Launch Camera") .description("A control that launches the camera app.") } } ``` -------------------------------- ### Implement Hardware Button Support with AVCaptureEventInteraction (SwiftUI) Source: https://context7.com/juniperphoton/lockedcameracaptureextensiondemo/llms.txt This snippet demonstrates how to integrate hardware capture button support into a SwiftUI view. It utilizes the `onCameraCaptureEvent` modifier for iOS 18+ and a `UIViewRepresentable` wrapper for iOS 17.2 compatibility. The `AVCaptureEventInteraction` is crucial for preventing the iOS extension from being terminated. ```swift import SwiftUI import AVFoundation extension View { @ViewBuilder func onPressCapture(action: @escaping () -> Void) -> some View { if #available(iOS 18.0, *) { self.onCameraCaptureEvent { event in switch event.phase { case .ended: action() default: break } } secondaryAction: { event in switch event.phase { case .ended: action() default: break } } } else if #available(iOS 17.2, *) { self.background { CaptureInteractionView(action: action) } } else { self } } } // For iOS 17.2 compatibility, use UIViewRepresentable wrapper @available(iOS 17.2, *) private struct CaptureInteractionView: UIViewRepresentable { var action: () -> Void func makeUIView(context: Context) -> some UIView { let uiView = UIView() let interaction = AVCaptureEventInteraction { event in if event.phase == .began { action() } } uiView.addInteraction(interaction) return uiView } func updateUIView(_ uiView: UIViewType, context: Context) { // ignored } } // Usage in your camera view struct CameraView: View { @StateObject var viewModel: MainViewModel var body: some View { CameraPreview() .onPressCapture { Task { await viewModel.capturePhoto() } } } } ``` -------------------------------- ### Inject Custom ScenePhase in Capture Extension Source: https://github.com/juniperphoton/lockedcameracaptureextensiondemo/blob/main/README.md Overrides the default ScenePhase environment value, which defaults to .background in extensions, to ensure components relying on active state function correctly. ```swift struct LockedCameraCaptureView: View { var body: some View { ContentView() .environment(\.scenePhase, .active) } } ``` -------------------------------- ### Implement AVCaptureEventInteraction in SwiftUI Source: https://github.com/juniperphoton/lockedcameracaptureextensiondemo/blob/main/README.md Wraps the UIKit AVCaptureEventInteraction into a SwiftUI-compatible UIViewRepresentable. This allows developers to trigger actions when hardware capture events occur within a SwiftUI view hierarchy. ```swift @available(iOS 17.2, *) private struct CaptureInteractionView: UIViewRepresentable { var action: () -> Void func makeUIView(context: Context) -> some UIView { let uiView = UIView() let interaction = AVCaptureEventInteraction { event in if event.phase == .began { action() } } uiView.addInteraction(interaction) return uiView } func updateUIView(_ uiView: UIViewType, context: Context) { // ignored } } ``` -------------------------------- ### Handle Session Content Updates Source: https://github.com/juniperphoton/lockedcameracaptureextensiondemo/blob/main/README.md Uses LockedCameraCaptureManager to listen for session content updates, allowing the main app to process captured files as they are added or removed. ```Swift for await update in LockedCameraCaptureManager.shared.sessionContentUpdates { switch update { case .initial(let urls): // Process captured content from existing session content directories. break case .added(let url): // Process captured content from a new session content directory. break case .removed(let url): // Process captured content from a removed session content directory. break default: // An unknown sessionContentUpdate was received. break } } ``` -------------------------------- ### Define CameraPosition Enum (Swift) Source: https://context7.com/juniperphoton/lockedcameracaptureextensiondemo/llms.txt Defines a Codable enum 'CameraPosition' with 'front' and 'back' cases. It includes a computed property 'avFoundationPosition' to map to AVFoundation's AVCaptureDevice.Position. This model is intended for use in sharing camera configuration between app and extension contexts. ```swift import AVFoundation public enum CameraPosition: String, Codable, Sendable { case front case back public var avFoundationPosition: AVCaptureDevice.Position { switch self { case .front: return .front case .back: return .back } } } ``` -------------------------------- ### Create View Modifier for Capture Events Source: https://github.com/juniperphoton/lockedcameracaptureextensiondemo/blob/main/README.md Provides a convenient extension method for the View protocol to attach capture interaction logic. It conditionally applies the interaction view based on the iOS version. ```swift extension View { @ViewBuilder func onPressCapture(action: @escaping () -> Void) -> some View { if #available(iOS 17.2, *) { self.background { CaptureInteractionView(action: action) } } else { self } } } ``` -------------------------------- ### Manage File Storage URLs with AppStorageConfigProvider (Swift) Source: https://context7.com/juniperphoton/lockedcameracaptureextensiondemo/llms.txt This code defines `AppStorageConfigProvider` to handle file storage URLs within a capture extension, as standard directories are inaccessible. It uses `sessionContentURL` from `LockedCameraCaptureSession` for temporary file storage. The `savePhoto` function illustrates how to save captured photo data to a URL provided by the config provider. ```swift import Foundation import LockedCameraCapture struct AppStorageConfigProvider: Sendable { let rootURL: URL? } extension AppStorageConfigProvider { // Standard provider for main app static let standard = AppStorageConfigProvider( rootURL: getStandardRootURL() ) } @available(iOS 18, *) extension AppStorageConfigProvider { // Provider for Capture Extension using session URL init(_ session: LockedCameraCaptureSession) { self.rootURL = session.sessionContentURL } } private func getStandardRootURL() -> URL? { try? FileManager.default.url( for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true ) } // Usage for saving captured photos func savePhoto(photoData: Data, configProvider: AppStorageConfigProvider) -> URL? { guard let fileURL = configProvider.rootURL?.appendingPathComponent( UUID().uuidString, conformingTo: .heic ) else { print("can't get fileURL") return nil } do { try photoData.write(to: fileURL) return fileURL } catch { print("failed to write to file (error)") return nil } } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.