Integrate SwiftData with your BlackBerry Dynamics app

Follow the steps below to define SwiftData models and create a BlackBerry Dynamics–backed model container after BlackBerry Dynamics authorization.
  • Set the deployment target to iOS 18 or later. Build your app with Xcode version 16 or later.
  • Complete BlackBerry Dynamics provisioning and authorization for your app so that the secure container can unlock. If you create the BlackBerry Dynamics container (step 3) before the authorization callback, the operation fails because the secure container is locked.
  • Review Unsupported SwiftData features for limitations that affect your design.
  1. Define @Model classes as you would in any SwiftData app:
    import SwiftData
    @Model final class Note {
        var title: String
        var body: String
        var createdAt: Date
        init(title: String, body: String, createdAt: Date = .now) {
            self.title = title
            self.body = body
            self.createdAt = createdAt
        }
    }
  2. From your BlackBerry Dynamics authorized event handler, create a GDSecureModelConfiguration that names the store, lists every model type (including relationship targets), and sets the store URL.
    @available(iOS 18, *)
    public final class GDSecureModelConfiguration {
        public var name: String
        public let storeURL: URL                 // auxiliary files live alongside this URL
        public let configurationName: String?   // pass nil unless partitioning entities
        // Tuning knobs — set before create(...)
        public var relationshipPrefetchDepth: Int                     // default 1
        public var externalBlobCachePolicy: GDExternalBlobCachePolicy // default .standard
        // Two ways to declare the schema:
        public init(name: String,
                    models: [any PersistentModel.Type],
                    storeURL: URL,
                    configurationName: String? = nil) throws
        public init(name: String,
                    versionedSchema: any VersionedSchema.Type,
                    storeURL: URL,
                    configurationName: String? = nil) throws
    }
    Optionally, you can set relationshipPrefetchDepth or externalBlobCachePolicy before you call create(...) to tune prefetch and external binary caching. The models: initializer does not infer related types the way Apple's ModelContainer does. If a model has a relationship to another type, include that type in the array, or use the versionedSchema: initializer instead. GDSecureModelConfiguration is single-use; create a new configuration for each GDSecureModelContainer.create(...) call.
  3. Create the container by calling GDSecureModelContainer.create(...) with the configuration.
    Always use this factory. Do not build a ModelContainer directly from a GDSecureModelConfiguration. Pass a migrationPlan: only when you use a VersionedSchema configuration. For schema evolution details, see Considerations for schema migration.
    import SwiftData
    import BlackBerryDynamics.Runtime
    // Call this from your Dynamics "authorized" event handler — not at app launch.
    @available(iOS 18, *)
    func makeContainer() throws -> ModelContainer {
        let documentsURL = FileManager.default.urls(for: .documentDirectory,
                                                    in: .userDomainMask)[0]
        let storeURL = documentsURL.appendingPathComponent("Notes.sqlite")
        let config = try GDSecureModelConfiguration(name: "Notes",
                                                    models: [Note.self],
                                                    storeURL: storeURL)
        return try GDSecureModelContainer.create(config)
    }
  4. Attach the returned ModelContainer to the view that you install after authorization. You typically do this by replacing the window's root with a UIHostingController. Do not put .modelContainer on the App scene, because the container does not exist at launch and App.body will not re-evaluate after the authorized event.
    @main
    struct MyApp: App {
        @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
        var body: some Scene {
            WindowGroup { 
                EmptyView()  // real UI is installed after authorization
            }
        }
    }
    
    // In your GDiOSDelegate authorized handler:
    let container = try makeContainer()
    window?.rootViewController = UIHostingController(
        rootView: ContentView().modelContainer(container))
  5. Read and write data using standard SwiftData APIs such as @Query and ModelContext.
    To remove a store and its auxiliary files, release any live container for that URL, then call GDSecureModelContainer.eraseStore(storeURL:).