diff --git a/source/frameworks/swiftui.txt b/source/frameworks/swiftui.txt index 4fe996a62f..8a6c77d0ee 100644 --- a/source/frameworks/swiftui.txt +++ b/source/frameworks/swiftui.txt @@ -8,3 +8,12 @@ Build with SwiftUI :titlesonly: Quick Start + Model Data + Configure and Open a Database + React to Changes + Pass Data Between Views + Write Data + Filter Data + Handle Sync Errors + Sync Data in the Background + Use the SDK with SwiftUI Previews diff --git a/source/frameworks/swiftui/background-sync.txt b/source/frameworks/swiftui/background-sync.txt new file mode 100644 index 0000000000..09e1f13647 --- /dev/null +++ b/source/frameworks/swiftui/background-sync.txt @@ -0,0 +1,311 @@ +.. _swiftui-background-sync: + +===================================== +Sync Data in the Background - SwiftUI +===================================== + +.. meta:: + :description: Learn how to use a SwiftUI BackgroundTask to sync data in the background. + :keywords: Realm, Swift SDK, code example + +.. facet:: + :name: genre + :values: tutorial + +.. facet:: + :name: programming_language + :values: swift + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +You can use a SwiftUI :apple:`BackgroundTask ` +to update a synced database when your app is in the background. This example +demonstrates how to configure and perform background syncing in an iOS app. + +You can follow along with the example on this page using the SwiftUI Device +Sync Template App. To get your own copy of the SwiftUI Device Sync +Template App, check out the :ref:`Device Sync SwiftUI tutorial +` and go through the :guilabel:`Prerequisites` +and :guilabel:`Start with the Template` sections. + +Enable Background Modes for Your App +------------------------------------ + +To enable background tasks for your app: + +.. procedure:: + + .. step:: Add Background Modes Capability + + Select your app Target, go to the :guilabel:`Signing & Capabilities` + tab, and click :guilabel:`+ Capability` to add the capability. + + .. figure:: /images/xcode-select-target-add-capability.png + :alt: Screenshot of Xcode with app Target selected, Signing & Capabilities tab open, and arrow pointing to add Capabilities. + :lightbox: + + Search for "background", and select :guilabel:`Background Modes`. + + .. step:: Select Background Modes + + Now you should see a :guilabel:`Background Modes` section in your + :guilabel:`Signing & Capabilities` tab. Expand this section, and + click the checkboxes to enable :guilabel:`Background fetch` and + :guilabel:`Background processing`. + + .. step:: Update the Info.plist + + Go to your project's :file:`Info.plist`, and add a new row for + ``Permitted background task scheduler identifiers``. If you are + viewing raw keys and values, the key is + ``BGTaskSchedulerPermittedIdentifiers``. This field is an array. + Add a new item to it for your background task identifier. Set the + new item's value to the string you intend to use as the identifier + for your background task. For example: ``refreshTodoRealm``. + +Schedule a Background Task +-------------------------- + +After enabling background processes for your app, you can start adding the +code to the app to schedule and execute a background task. First, import +``BackgroundTasks`` in the files where you will write this code: + +.. code-block:: swift + :emphasize-lines: 3 + + import SwiftUI + import RealmSwift + import BackgroundTasks + +Now you can add a scheduled background task. If you're following along +via the Template App, you can update your ``@main`` view: + +.. code-block:: swift + :emphasize-lines: 3, 9-14 + + @main + struct realmSwiftUIApp: SwiftUI.App { + @Environment(\.scenePhase) private var phase + + var body: some Scene { + WindowGroup { + ContentView(app: realmApp) + } + .onChange(of: phase) { newPhase in + switch newPhase { + case .background: scheduleAppRefresh() + default: break + } + } + } + +You can add an environment variable to store a change to the ``scenePhase``: +``@Environment(\.scenePhase) private var phase``. + +Then, you can add the ``.onChange(of: phase)`` block that calls the +``scheduleAppRefresh()`` function when the app goes into the background. + +Create the ``scheduleAppRefresh()`` function: + +.. code-block:: swift + + func scheduleAppRefresh() { + let backgroundTask = BGAppRefreshTaskRequest(identifier: "refreshTodoRealm") + backgroundTask.earliestBeginDate = .now.addingTimeInterval(10) + try? BGTaskScheduler.shared.submit(backgroundTask) + } + +This schedules the work to execute the background task whose identifier you +added to the Info.plist above when you enabled Background Modes. In this +example, the identifier ``refreshTodoRealm`` refers to this task. + +Create the Background Task +-------------------------- + +Now that you've scheduled the background task, you need to create the background +task that will run to update the synced realm. + +If you're following along with the Template App, you can add this +``backgroundTask`` to your ``@main`` view, after the ``.onChange(of: phase)``: + +.. code-block:: swift + :emphasize-lines: 7-23 + + .onChange(of: phase) { newPhase in + switch newPhase { + case .background: scheduleAppRefresh() + default: break + } + } + .backgroundTask(.appRefresh("refreshTodoRealm")) { + guard let user = realmApp.currentUser else { + return + } + let config = user.flexibleSyncConfiguration(initialSubscriptions: { subs in + if let foundSubscription = subs.first(named: "user_tasks") { + foundSubscription.updateQuery(toType: Item.self, where: { + $0.owner_id == user.id + }) + } else { + subs.append(QuerySubscription(name: "user_tasks") { + $0.owner_id == user.id + }) + } + }, rerunOnOpen: true) + await refreshSyncedRealm(config: config) + } + +This background task first checks that your app has a logged-in user. If so, +it sets a :swift-sdk:`.flexibleSyncConfiguration +` +with a :ref:`subscription ` the +app can use to sync the realm. + +This is the same configuration used in the Template App's ``ContentView``. +However, to use it here you need access to it farther up the view hierarchy. +You could refactor this to a function you can call from either view that +takes a :swift-sdk:`User ` as a +parameter and returns a :swift-sdk:`Realm.configuration +`. + +Finally, this task awaits the result of a function that actually syncs the +database. Add this function: + +.. code-block:: swift + + func refreshSyncedRealm(config: Realm.Configuration) async { + do { + try await Realm(configuration: config, downloadBeforeOpen: .always) + } catch { + print("Error opening the Synced realm: \(error.localizedDescription)") + } + } + +By opening this synced database and using the ``downloadBeforeOpen`` parameter +to specify that you want to download updates, you load the fresh data into +the database in the background. Then, when your app opens again, it already +has the updated data on the device. + +.. important:: + + Do not try to write to the database directly in this background task. You + may encounter threading-related issues due to the SDK's thread-confined + architecture. + +Test Your Background Task +------------------------- + +When you schedule a background task, you are setting the earliest time that +the system could execute the task. However, the operating system factors in +many other considerations that may delay the execution of the background task +long after your scheduled ``earliestBeginDate``. Instead of waiting for a +device to run the background task to verify it does what you intend, you can +set a breakpoint and use LLDB to invoke the task. + +.. procedure:: + + .. step:: Configure a Device to Run Your App + + To test that your background task is updating the synced database in the + background, you'll need a physical device running at minimum iOS 16. + Your device must be configured to run in :apple:`Developer Mode + `. If you get an + ``Untrusted Developer`` notification, go to :guilabel:`Settings`, + :guilabel:`General`, and :guilabel:`VPN & Device Management`. Here, you + can verify that you want to run the app you're developing. + + Once you can successfully run your app on your device, you can test the + background task. + + .. step:: Set a Breakpoint + + Start by setting a breakpoint in your ``scheduleAppRefresh()`` function. + Set the breakpoint *after* the line where you submit the task to + ``BGTaskScheduler``. For this example, you might add a ``print`` line and + set the breakpoint at the print line: + + .. code-block:: swift + :emphasize-lines: 5 + + func scheduleAppRefresh() { + let backgroundTask = BGAppRefreshTaskRequest(identifier: "refreshTodoRealm") + backgroundTask.earliestBeginDate = .now.addingTimeInterval(10) + try? BGTaskScheduler.shared.submit(backgroundTask) + print("Successfully scheduled a background task") // Set a breakpoint here + } + + .. step:: Run the App + + Now, run the app on the connected device. Create or sign into an account + in the app. If you're using the SwiftUI Template App, create some Items. + You should see the Items sync to the ``Item`` collection linked to your + Atlas App Services app. + + Then, while leaving the app running in Xcode, send the app to the background + on your device. You should see the console print "Successfully scheduled a + background task" and then get an LLDB prompt. + + .. step:: Add or Change Data in Atlas + + While the app is in the background but still running in Xcode, Insert a new + document in the relevant Atlas collection that should sync to the device. + Alternately, change a value of an existing document that you created from + the device. After successfully running the background task, you should + see this data synced to the device from the background process. + + If you're using the SwiftUI Template App, you can find relevant documents + in your Atlas cluster's ``Item`` collection. For more information on how + to add or change documents in Atlas, see: :atlas:`MongoDB Atlas: Create, + View, Update, and Delete Documents `. + + .. step:: Invoke the Background Task in LLDB + + Use this command to manually execute the background task in LLDB: + + .. code-block:: shell + + e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"refreshTodoRealm"] + + If you have used a different identifier for your background task, replace + ``refreshTodoRealm`` with your task's identifier. This causes the task to + immediately begin executing. + + If successful, you should see something like: + + .. code-block:: shell + + 2022-11-11 15:09:10.403242-0500 App[1268:196548] Simulating launch for task with identifier refreshTodoRealm + 2022-11-11 15:09:16.530201-0500 App[1268:196811] Starting simulated task + + After you have kicked off the task, use the :guilabel:`Continue program execution` + button in the Xcode debug panel to resume running the app. + + .. step:: Turn on Airplane Mode on the Device + + After waiting for the background task to complete, but before you open the + app again, turn on Airplane Mode on the device. Make sure you have turned + off WiFi. This ensures that when you open the app again, it doesn't + start a fresh Sync and you see only the values that are now in the database + on the device. + + .. step:: Open the App + + Open the app on the device. You should see the updated data that you changed + in Atlas. + + To verify the updates came through the background task, confirm you have + successfully disabled the network. + + Create a new task using the app. You should see the task in the app, but + it should not sync to Atlas. Alternately, you could create or change data + in Atlas, but should not see it reflected on the device. + + This tells you that the network has successfully been disabled, + and the updated data that you see came through the background task. diff --git a/source/frameworks/swiftui/configure-and-open-database.txt b/source/frameworks/swiftui/configure-and-open-database.txt new file mode 100644 index 0000000000..8d0d61f0d7 --- /dev/null +++ b/source/frameworks/swiftui/configure-and-open-database.txt @@ -0,0 +1,165 @@ +.. _swiftui-open-database: + +======================================= +Configure and Open a Database - SwiftUI +======================================= + +.. meta:: + :description: Use the SDK's built in SwiftUI property wrappers to configure and open a database. + :keywords: Realm, Swift SDK, code example + +.. facet:: + :name: genre + :values: reference + +.. facet:: + :name: programming_language + :values: swift + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +The Swift SDK provides property wrappers to open a database in a +SwiftUI-friendly way. + +You can: + +- :ref:`Implicitly open a database ` + with a ``defaultConfiguration`` or specify a different configuration. + This works for both non-synced and synced databases. +- :ref:`Always download changes before opening a synced database + `, which times out when the user is offline. +- :ref:`Open a synced database even when a user is offline + `. The database may lack the most recent data. + +.. _swiftui-open-database-with-configuration: + +Open a Database with a Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When you use :swift-sdk:`@ObservedRealmObject ` +or :swift-sdk:`@ObservedResults `, these +property wrappers implicitly open a database and retrieve the specified +objects or results. + +.. literalinclude:: /examples/generated/swiftui/PassObjectsToView.snippet.implicitly-open-realm.swift + :language: swift + +.. include:: /includes/note-observedresults-swiftui-view.rst + +When you do not specify a configuration, these property wrappers use the +:swift-sdk:`defaultConfiguration `. +You can :ref:`set the defaultConfiguration ` +globally, and property wrappers across the app can use that configuration +when they implicitly open a database. + +You can provide alternative configurations that the property wrappers use +to implicitly open the database. You might want to do this when using +multiple configurations in your app, as in cases where you have both +a :swift-sdk:`SyncConfiguration ` and +a local :swift-sdk:`Configuration `. +To do this, :ref:`create explicit configurations `. +Then, :ref:`use environment injection to pass the respective configurations +to the views that need them `. +Passing a configuration to a view where property wrappers open a database +uses the passed configuration instead of the ``defaultConfiguration``. + +.. _swiftui-open-synced-database: + +Open a Synced Database +~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 10.12.0 + +These SwiftUI property wrappers open synced databases and populate views. +The main difference between these property wrappers is whether the user +must be online: + +- To download updates from your Atlas App Services app before opening a database, + use the :ref:`@AsyncOpen ` property wrapper. + This requires the user to have a network connection. +- To open a synced database regardless of whether the user has a network + connection, use the :ref:`@AutoOpen ` + property wrapper. This property wrapper enables developers to design + offline-first capabilities into their apps. + +.. _swiftui-async-open-synced-database: + +Download Changes Before Opening a Synced Database +````````````````````````````````````````````````` + +Use the :swift-sdk:`@AsyncOpen ` property wrapper +for apps that require up-to-date information from the server, such as game +apps with live leaderboards that the user can play on multiple devices. This +ensures the user is never using the app with stale data. + +You can add subscription queries in ``.onAppear`` after opening the database. + +.. literalinclude:: /examples/generated/swiftui/OpenRealm.snippet.fs-property-wrapper-sans-config-comment.swift + :language: swift + +You can create a :swift-sdk:`flexibleSyncConfiguration() +` +with the ``initialSubscriptions`` parameter. You can use this parameter +to :ref:`subscribe to Sync queries ` in the +configuration. If this runs more than once - for example, if it's in a view +that reloads regularly - check whether the subscription exists already +before adding it. Adding the same subscription again throws an error. + +.. literalinclude:: /examples/generated/swiftui/Authenticate.snippet.flexible-sync-config.swift + :language: swift + +Then, pass the configuration to the view that contains the property +wrappers as an environment object. + +.. literalinclude:: /examples/generated/swiftui/Authenticate.snippet.inject-flex-sync-config-as-environment-object.swift + :language: swift + +For a complete example, see the :ref:`SwiftUI Quick Start `. + +This SwiftUI property wrapper initiates ``Realm.asyncOpen()`` for the current +user. The property wrapper publishes states, represented by the :swift-sdk:`AsyncOpenState +enum `, +which you can use to update the view. + +.. example:: + + This example illustrates one way you might use ``@AsyncOpen`` to + open a database in a view. First, check for a user, or log them in. + Then, attempt to open the database, switching on the ``AsyncOpenState`` + to display an appropriate view. When the database opens successfully, + inject it as an environment value to populate the view. + + .. literalinclude:: /examples/generated/swiftui/OpenRealm.snippet.open-realm-view-flex-sync.swift + :language: swift + +.. _swiftui-auto-open-synced-database: + +Open a Synced Database Offline +`````````````````````````````` + +Like ``@AsyncOpen``, :swift-sdk:`@AutoOpen ` attempts +to download updates before opening the database. However, if a network +connection is not available, this method instead opens a database with +data on the device. + +Use this property wrapper for apps where it's not a problem for the user +to work with potentially stale data, such as note-taking apps where users +should be able to work with data on the device + +.. code-block:: swift + + @AutoOpen(appId: "app_id") var autoOpen + +This SwiftUI property wrapper attempts to download updates before opening a +database for the current user. If there is no internet connection, this property +wrapper instead returns the most up-to-date version of the local database file +for the given ``appId`` and Sync configuration. + +The property wrapper publishes states, represented by the :swift-sdk:`AsyncOpenState +enum `, +which you can use to update the view. For a full example, see the ``@AsyncOpen`` +code examples above. diff --git a/source/frameworks/swiftui/filter-data.txt b/source/frameworks/swiftui/filter-data.txt new file mode 100644 index 0000000000..a94d3894cc --- /dev/null +++ b/source/frameworks/swiftui/filter-data.txt @@ -0,0 +1,95 @@ +===================== +Filter Data - SwiftUI +===================== + +.. meta:: + :description: Extend Apple's .searchable implementation to SDK objects, filter or query ObservedResults, or section results. + :keywords: Realm, Swift SDK, code example + +.. facet:: + :name: genre + :values: reference + +.. facet:: + :name: programming_language + :values: swift + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Observe in SwiftUI Views +------------------------ + +The ``@ObservedResults`` property wrapper used in the examples on this page +is intended for use in a SwiftUI View. If you want to observe results +in a view model instead, :ref:`register a change listener +`. + +Search an SDK Collection +------------------------ + +The Swift SDK allows you to extend :apple:`.searchable +`. +When you use :swift-sdk:`ObservedResults ` +to query a realm, you can specify collection and keypath in the result set +to mark it as searchable. + +The collection is the bound collection represented by your ``ObservedResults`` +query. In this example, it is the ``dogs`` variable that represents the +collection of all Dog objects in the database. + +The keypath is the object property that you want to search. In this +example, we search the dogs collection by dog name. The SDK's +``.searchable`` implementation only supports keypaths with ``String`` types. + +.. literalinclude:: /examples/generated/swiftui/FilterData.snippet.searchable.swift + :language: swift + +Filter or Query a Database with ObservedResults +----------------------------------------------- + +The :swift-sdk:`@ObservedResults ` property wrapper +opens a database and returns all objects of the specified type. However, you +can filter or query ``@ObservedResults`` to use only a subset of the objects +in your view. + +.. seealso:: + + For more information about the query syntax and types of queries that the + SDK supports, refer to: :ref:`Read ` and + :ref:`sdks-filter-data-swift`. + +Query with the Swift Query API +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To use ``@ObservedResults`` with the :ref:`Swift Query API +`, pass a query in a closure as an argument to +``where``: + +.. literalinclude:: /examples/generated/swiftui/FilterData.snippet.type-safe-query-filter.swift + :language: swift + +Filter with an NSPredicate +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To filter ``@ObservedResults`` using the :ref:`NSPredicate Query API +`, pass an :apple:`NSPredicate +` as an argument to ``filter``: + +.. literalinclude:: /examples/generated/swiftui/FilterData.snippet.nspredicate-filter.swift + :language: swift + +Section Results +--------------- + +The :swift-sdk:`@ObservedSectionedResults ` +property wrapper opens a database and returns all objects of the specified type, +divided into sections by the specified key path. Similar to +``@ObservedResults`` above, you can filter or query ``@ObservedSectionedResults`` +to use only a subset of the objects in your view: + +.. literalinclude:: /examples/generated/swiftui/FilterData.snippet.observed-filtered-sectioned-results.swift + :language: swift diff --git a/source/frameworks/swiftui/handle-sync-errors.txt b/source/frameworks/swiftui/handle-sync-errors.txt new file mode 100644 index 0000000000..e33a06b832 --- /dev/null +++ b/source/frameworks/swiftui/handle-sync-errors.txt @@ -0,0 +1,72 @@ +.. _swiftui-handle-sync-errors: + +============================ +Handle Sync Errors - SwiftUI +============================ + +.. meta:: + :description: Learn how to handle Sync errors in your SwiftUI-based app. + :keywords: Realm, Swift SDK, code example + +.. facet:: + :name: genre + :values: reference + +.. facet:: + :name: programming_language + :values: swift + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. _swiftui-register-sync-error-handler: + +Handle Sync Errors +------------------ + +While developing an application that uses Device Sync, set an error handler. +This error handler detects and responds to any failed sync-related API calls. + +.. seealso:: + + For a complete example app with a working Sync error handler implementation, + :ref:`create a template app ` and check out + :ref:`the SwiftUI client `. The error handler + implementation is in the :file:`App.swift` file. + +For a SwiftUI-friendly implementation of a Sync error handler, create +an ``ObservableObject`` with an optional ``@Published`` variable to contain +a potential error. This handler uses the :swift-sdk:`SyncManager +` to listen for errors. +The ``SyncManager`` reports errors of the type ``SyncError``, and it also +reports other connection issues. + +For more information, refer to the underlying Objective-C :objc-sdk:`RLMSyncError +`. + +.. literalinclude:: /examples/generated/swiftui/HandleSyncErrors.snippet.swiftui-error-handler.swift + :language: swift + +.. include:: /includes/sync-errors-in-app-services.rst + +Initialize the error handler as a ``@StateObject``. Inject it into the +view hierarchy as an environment object. In this example, we display an +``.alert`` to the user when a Sync error occurs. + +.. literalinclude:: /examples/generated/swiftui/HandleSyncErrors.snippet.swiftui-app-with-error-handler.swift + :language: swift + +Then, in the view where you are observing the SDK ``App``, you can use the +error handler as an ``@EnvironmentObject`` to react to Sync errors. An +error that occurs here pops up an alert for the user, using the ``.alert`` +set in the view above. + +.. literalinclude:: /examples/generated/swiftui/HandleSyncErrors.snippet.use-app-and-error-handler-in-next-view.swift + :language: swift + +.. TODO: Re-test and add the section in the following file: +.. `includes/swiftui-handle-client-reset-error.rst` after realm-swift merges +.. this PR: https://github.com/realm/realm-swift/pull/8109 diff --git a/source/frameworks/swiftui/model-data.txt b/source/frameworks/swiftui/model-data.txt new file mode 100644 index 0000000000..a3c1e69a10 --- /dev/null +++ b/source/frameworks/swiftui/model-data.txt @@ -0,0 +1,12 @@ +==================== +Model Data - SwiftUI +==================== + +.. toctree:: + :titlesonly: + + Object Models + Change an Object Model + +- :doc:`Object Models ` +- :doc:`Change an Object Model ` diff --git a/source/frameworks/swiftui/model-data/change-an-object-model.txt b/source/frameworks/swiftui/model-data/change-an-object-model.txt new file mode 100644 index 0000000000..980c539e7c --- /dev/null +++ b/source/frameworks/swiftui/model-data/change-an-object-model.txt @@ -0,0 +1,145 @@ +.. _swiftui-realm-migrations: + +================================ +Change an Object Model - SwiftUI +================================ + +.. meta:: + :description: Learn how to update your data model and perform migrations in an app that uses Atlas Device SDK SwiftUI property wrappers. + :keywords: Realm, Swift SDK, code example + +.. facet:: + :name: genre + :values: reference + +.. facet:: + :name: programming_language + :values: swift + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +When you update your object schema, you must increment the schema version +and perform a migration. You might update your object schema between major +version releases of your app. + +For information on how to actually perform the migration, see: +:ref:`Change an Object Model `. + +This page focuses on how to use migrated data in SwiftUI Views. + +.. include:: /includes/note-modify-schema-properties-of-synced-realms.rst + +Use Migrated Data with SwiftUI +------------------------------ + +To perform a migration: + +- Update your schema and write a migration block, if required +- Specify a :swift-sdk:`Realm.Configuration ` + that uses this migration logic and/or updated schema version when you + initialize your database. + +From here, you have a few options to pass the configuration object. You can: + +- Set the configuration as the :ref:`default configuration + `. If you do not explicitly pass the + configuration via environment injection or as a parameter, property + wrappers use the default configuration. +- Use environment injection to provide this configuration to the first view + in your hierarchy that uses the database +- Explicitly provide the configuration to an SDK property wrapper that takes + a configuration object, such as ``@ObservedResults`` or ``@AsyncOpen``. + +.. example:: + + For example, you might want to add a property to an existing object. We + could add a ``favoriteTreat`` property to the ``Dog`` object in DoggoDB: + + .. code-block:: swift + :copyable: false + + @Persisted var favoriteTreat = "" + + After you add your new property to the schema, you must increment the + schema version. Your ``Realm.Configuration`` might look like this: + + .. literalinclude:: /examples/generated/swiftui/SyncOrLocalRealm.snippet.update-schema-version.swift + :language: swift + :copyable: false + + Declare this configuration somewhere that is accessible to the first view + in the hierarchy that needs it. Declaring this above your ``@main`` app + entrypoint makes it available everywhere, but you could also put it in + the file where you first open a realm. + +Set a Default Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can set a default configuration in a SwiftUI app the same as any other +SDK app. Set the default database configuration by assigning a new +``Realm.Configuration`` instance to the +:swift-sdk:`Realm.Configuration.defaultConfiguration +` +class property. + +.. literalinclude:: /examples/generated/code/start/OpenCloseRealm.snippet.open-local-realm.swift + :language: swift + +Pass the Configuration Object as an Environment Object +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Once you have declared the configuration, you can inject it as an environment +object to the first view in your hierarchy that opens a database. If you are +using the ``@ObservedResults`` or ``@ObservedRealmObject`` property wrappers, +these views implicitly open a database, so they also need access to this +configuration. + +.. code-block:: swift + :copyable: false + + .environment(\.realmConfiguration, config) + +If your app uses either a non-synced or a synced database, the first view in +the hiearchy that opens a database varies depending on whether you're using +the app with or without Sync. + +Without sync, you can pass the database configuration environment object +directly to the ``LocalOnlyContentView``: + +.. literalinclude:: /examples/generated/swiftui/SyncOrLocalRealm.snippet.pass-config-environment-object.swift + :language: swift + +Which opens a database implicitly with: + +.. literalinclude:: /examples/generated/swiftui/PassObjectsToView.snippet.local-only-view.swift + :language: swift + +However, when your app uses Sync, you open the database explicitly using the +``@AsyncOpen`` or ``@AutoOpen`` property wrapper: + +.. literalinclude:: /examples/generated/swiftui/OpenRealm.snippet.open-realm-view-flex-sync.swift + :language: swift + +So you must pass the environment object to the view that explicitly +opens the database. In this case, the ``OpenFlexibleSyncRealmView``. + +The important thing to remember is to make sure to pass the +``Realm.Configuration`` that encompasses your migration logic to any view +hierarchy that implicitly or explicitly opens a database. + +Explicitly Pass the Updated Configuration to an SDK SwiftUI Property Wrapper +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can explicitly pass the configuration object to an SDK SwiftUI +property wrapper that takes a configuration object, such as ``@ObservedResults`` +or ``@AutoOpen``. In this case, you might pass it directly to ``@ObservedResults`` +in our ``DogsView``. + +.. code-block:: swift + + // Use a `config` that you've passed in from above. + @ObservedResults(Dog.self, configuration: config) var dogs diff --git a/source/frameworks/swiftui/model-data/define-an-object-model.txt b/source/frameworks/swiftui/model-data/define-an-object-model.txt new file mode 100644 index 0000000000..5fa9d32ce5 --- /dev/null +++ b/source/frameworks/swiftui/model-data/define-an-object-model.txt @@ -0,0 +1,133 @@ +.. _swiftui-object-models: + +======================= +Object Models - SwiftUI +======================= + +.. meta:: + :description: Model SDK objects for SwiftUI, and bind them to your views. + :keywords: Realm, Swift SDK, code example + +.. facet:: + :name: genre + :values: tutorial + +.. facet:: + :name: programming_language + :values: swift + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Concepts: Object Models and Relationships +----------------------------------------- + +Modeling data for SwiftUI builds on the same object model and relationship +concepts in Atlas Device SDK. If you are unfamiliar with the SDK's data +modeling concepts, refer to: :ref:`sdks-object-models`. + +Binding the Object Model to the UI +---------------------------------- + +The Model-View-ViewModel (MVVM) design pattern advocates creating a view +model that abstracts the model from the View code. While you can certainly +do that with the SDK, the Swift SDK provides tools that make it easy to +work directly with your data in SwiftUI Views. These tools include things +like: + +- Property wrappers that create bindings to underlying observable objects +- A class to project and transform underlying model objects for use in + specific views + +Transforming Data for SwiftUI Views +----------------------------------- + +The Swift SDK provides a special type of object, called a +:swift-sdk:`Projection `, to transform +and work with subsets of your data. Consider a projection similar to +a view model. It lets you pass through or transform the original +object's properties in different ways: + +- Passthrough: The projection's property has the same name and type as + the original object. +- Rename: The projection's property has the same type as the original object, + but a different name. +- Keypath resolution: Use this to access specific properties of the + projected Object. +- Collection mapping: You can map some :ref:`collection types + ` to a collection of primitive values. +- Exclusion: All properties of the original SDK object not defined in + the projection model. Any changes to those properties do not trigger a + change notification when observing the projection. + +When you use a Projection, you get all the benefits of the SDK's +live objects: + +- The class-projected object live updates +- You can observe it for changes +- You can apply changes directly to the properties in write transactions + +.. _swiftui-model: + +Define a New Object +------------------- + +You can define an SDK object by deriving from the +:swift-sdk:`Object ` or +:swift-sdk:`EmbeddedObject ` +class. The name of the class becomes the table name in the database, +and properties of the class persist in the database. This makes it +as easy to work with persisted objects as it is to work with +regular Swift objects. + +The SwiftUI documentation uses a model for a fictional app, +DoggoDB. This app is a company directory of employees who have dogs. It +lets people share a few details about their dogs with other employees. + +The data model includes a Person object, with a :ref:`to-many +relationship ` to that person's Dog objects. +It also uses a special Realm Swift SDK data type, :swift-sdk:`PersistableEnum +`, to store information +about the person's business unit. + +.. literalinclude:: /examples/generated/swiftui/Model.snippet.objects.swift + :language: swift + +.. seealso:: + + For complete details about defining an SDK object model, see: + + - :ref:`Object Models ` + - :ref:`Relationships ` + - :ref:`Property Types ` + +.. _swiftui-projection: + +Define a Projection +------------------- + +Our fictional DoggoDB app has a user Profile view. This view displays +some details about the person, but we don't need all of the properties +of the ``Person`` model. We can create a :swift-sdk:`Projection +` with only the details we want. We can also modify +the ``lastName`` property to use just the first initial of the last name. + +.. literalinclude:: /examples/generated/swiftui/Model.snippet.projection.swift + :language: swift + +We can use this projection in the Profile view instead of the original +``Person`` object. + +Class projection works with SwiftUI property wrappers: + +- :swift-sdk:`ObservedRealmObject ` +- :swift-sdk:`ObservedResults ` + +.. seealso:: + + For a complete example of using a class projection in a SwiftUI + application, see :github:`the Projections example app + `. diff --git a/source/frameworks/swiftui/pass-data-between-views.txt b/source/frameworks/swiftui/pass-data-between-views.txt new file mode 100644 index 0000000000..2610beb8d5 --- /dev/null +++ b/source/frameworks/swiftui/pass-data-between-views.txt @@ -0,0 +1,79 @@ +.. _swiftui-pass-data-between-views: + +================================= +Pass Data Between Views - SwiftUI +================================= + +.. meta:: + :description: Pass SDK objects explicitly to child views, access data in child views with property wrappers, or pass environment values to access the database. + :keywords: Realm, Swift SDK, code example + +.. facet:: + :name: genre + :values: reference + +.. facet:: + :name: programming_language + :values: swift + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Atlas Device SDK provides several ways to pass SDK data between views: + +- Pass SDK objects to a view +- Use environment injection to: + + - Inject an opened database into a view + - Inject a database configuration into a view + +Pass SDK Objects to a View +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When you use the ``@ObservedRealmObject`` or ``@ObservedResults`` property +wrapper, you implicitly open a database and retrieve the specified objects +or results. You can then pass those objects to a view further down the +hierarchy. + +.. literalinclude:: /examples/generated/swiftui/PassObjectsToView.snippet.implicitly-open-realm-and-pass-objects.swift + :language: swift + +.. _swiftui-pass-environment-values: + +Pass Environment Values +~~~~~~~~~~~~~~~~~~~~~~~ + +:apple:`Environment ` injection is a +useful tool in SwiftUI development with the SDK. +Atlas Device SDK property wrappers provide different ways for you to +work with environment values when developing your SwiftUI application. + +.. _swiftui-inject-database-as-environment-value: + +Inject an Opened Database +````````````````````````` + +You can inject a database that you opened in another SwiftUI view into +a view as an environment value. The property wrapper uses this passed-in +database to populate the view: + +.. code-block:: swift + + ListView() + .environment(\.realm, realm) + +.. _swiftui-inject-database-configuration: + +Inject a Database Configuration +``````````````````````````````` + +You can use a database other than the default database by passing a different +configuration in an environment object. + +.. code-block:: swift + + LocalOnlyContentView() + .environment(\.realmConfiguration, Realm.Configuration( /* ... */ )) diff --git a/source/frameworks/swiftui/quick-start.txt b/source/frameworks/swiftui/quick-start.txt index f4161d5187..d131f755db 100644 --- a/source/frameworks/swiftui/quick-start.txt +++ b/source/frameworks/swiftui/quick-start.txt @@ -1,8 +1,8 @@ .. _ios-swiftui-quick-start: -======================== -Quick Start with SwiftUI -======================== +===================== +Quick Start - SwiftUI +===================== .. meta:: :description: Use Atlas Device SDK for Swift with SwiftUI property wrappers. diff --git a/source/frameworks/swiftui/react-to-changes.txt b/source/frameworks/swiftui/react-to-changes.txt new file mode 100644 index 0000000000..d158f46c2e --- /dev/null +++ b/source/frameworks/swiftui/react-to-changes.txt @@ -0,0 +1,116 @@ +========================== +React to Changes - SwiftUI +========================== + +.. meta:: + :description: Use the SDK's built in SwiftUI property wrappers to invalidate and update views when the database data changes. + :keywords: Realm, Swift SDK, code example + +.. facet:: + :name: genre + :values: reference + +.. facet:: + :name: programming_language + :values: swift + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. _swiftui-update-ui-when-objects-change: + +Observe an Object +----------------- + +The SDK provides the :swift-sdk:`@ObservedRealmObject +` property wrapper that invalidates a view +when an observed object changes. You can use this property wrapper to +create a view that automatically updates itself when the observed object +changes. + +.. literalinclude:: /examples/generated/swiftui/PassObjectsToView.snippet.dog-detail-view.swift + :language: swift + +.. _swiftui-update-ui-when-query-results-change: + +Observe Query Results +--------------------- + +The SDK provides the :swift-sdk:`@ObservedResults ` +property wrapper that lets you observe a collection of query results. You +can perform a quick write to an ``ObservedResults`` collection, and the view +automatically updates itself when the observed query changes. For example, +you can remove a dog from an observed list of dogs using ``onDelete``. + +.. include:: /includes/note-observedresults-swiftui-view.rst + +.. literalinclude:: /examples/generated/swiftui/PassObjectsToView.snippet.implicitly-open-realm-and-pass-objects.swift + :language: swift + +.. seealso:: + + For more information about the query syntax and types of queries that the + SDK supports, refer to: :ref:`Read ` and + :ref:`sdks-filter-data-swift`. + +Sort Observed Results +~~~~~~~~~~~~~~~~~~~~~ + +The :swift-sdk:`@ObservedResults ` +property wrapper can take a :swift-sdk:`SortDescriptor +` parameter to sort the query results. + +.. literalinclude:: /examples/generated/swiftui/FilterData.snippet.sort-descriptor.swift + :language: swift + +.. tip:: + + You cannot use a computed property as a ``SortDescriptor`` for ``@ObservedResults``. + +.. _swiftui-observe-sectioned-results: + +Observe Sectioned Results +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 10.29.0 + +You can observe a results set that is divided into sections by a key +generated from a property on the object. We've added a computed variable +to the model that we don't persist; we just use this to section the results +set. + +.. literalinclude:: /examples/generated/swiftui/Model.snippet.computed-var-sectioned-results.swift + :language: swift + +Then, we can use the :swift-sdk:`@ObservedSectionedResults +` property wrapper to +observe the results set divided into sections based on the computed variable +key. + +.. literalinclude:: /examples/generated/swiftui/SectionedResults.snippet.observed-sectioned-results.swift + :language: swift + +You might use these observed sectioned results to populate a List view +divided by sections: + +.. literalinclude:: /examples/generated/swiftui/SectionedResults.snippet.sectioned-dogs-list-view.swift + :language: swift + +.. _swiftui-react-to-login-state-changes: + +Observe App State +----------------- + +If your app uses Atlas Device Sync, you can observe the :swift-sdk:`App +` object to react to login state changes. This enables +your app to perform operations while it has an ``app.currentUser``, or direct +the user to log in if there is no ``app.currentUser``. + +Because the SDK caches user credentials on the device, your app can work +offline while it has an ``app.currentUser``. + +.. literalinclude:: /examples/generated/swiftui/Authenticate.snippet.flexible-sync-content-view.swift + :language: swift diff --git a/source/frameworks/swiftui/swiftui-previews.txt b/source/frameworks/swiftui/swiftui-previews.txt new file mode 100644 index 0000000000..6d83418746 --- /dev/null +++ b/source/frameworks/swiftui/swiftui-previews.txt @@ -0,0 +1,323 @@ +.. _swiftui-previews: + +================================= +Use the SDK with SwiftUI Previews +================================= + +.. meta:: + :description: Use and debug SwiftUI Previews while developing with the Atlas Device SDK for Swift. + :keywords: Realm, Swift SDK, code example + +.. facet:: + :name: genre + :values: tutorial + +.. facet:: + :name: programming_language + :values: swift + +.. contents:: On this page + :local: + :backlinks: none + :depth: 3 + :class: singlecol + +Overview +-------- + +SwiftUI Previews are a useful tool during development. You can work with the SDK +data in SwiftUI Previews in a few ways: + +- Initialize individual objects to use in detail views +- Conditionally use an array of objects in place of ``@ObservedResults`` +- Create a database that contains data for the previews + +SwiftUI Preview debugging can be opaque, so we also have a few tips to debug +issue with persisting Realms within SwiftUI Previews. + +.. _swift-use-objects-in-a-detail-view: + +Initialize an Object for a Detail View +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In the simplest case, you can use SwiftUI Previews with one or more objects +that use SDK properties you can set directly at initialization. +You might want to do this when previewing a Detail view. Consider DoggoDB's +``DogDetailView``: + +.. literalinclude:: /examples/generated/swiftui/PassObjectsToView.snippet.dog-detail-view.swift + :language: swift + +Create an extension for your model object. Where you put this extension depends +on convention in your codebase. You may put it directly in the model file, +have a dedicated directory for sample data, or use some other convention in +your codebase. + +In this extension, initialize one or more SDK objects with ``static let``: + +.. literalinclude:: /examples/generated/swiftui/Model.snippet.preview-extend-model-class-with-objects.swift + :language: swift + +In this example, we :ref:`initialize objects with a value +`. You can only initialize objects with +a value when your model contains properties that you can directly initialize. +If your model object contains properties that are only mutable within a +write transaction, such as a :ref:`List property `, +you must instead :ref:`create a database to use with your SwiftUI Previews +`. + +After you have initialized an object as an extension of your model class, +you can use it in your SwiftUI Preview. You can pass the object directly +to the View in the Preview: + +.. literalinclude:: /examples/generated/swiftui/Previews.snippet.preview-detail-view.swift + :language: swift + +.. _conditionally-use-observedresults-in-a-list-view: + +Conditionally Use ObservedResults in a List View +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When you use :swift-sdk:`@ObservedResults ` +in a List view, this implicitly opens a database and queries it. For this to +work in a Preview, you need :ref:`a database populated with data +`. As an alternative, you can conditionally +use a static array in Previews and only use the ``@ObservedResults`` variable +when running the app. + +You could do this in multiple ways, but for the sake of making our +code easier to read and understand, we'll create an ``EnvironmentValue`` +that can detect whether the app is running in a Preview: + +.. code-block:: swift + + import Foundation + import SwiftUI + + public extension EnvironmentValues { + var isPreview: Bool { + #if DEBUG + return ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" + #else + return false + #endif + } + } + +Then, we can use this as an environment value in our view, and conditionally +change which variable we use based on whether or not we are in a Preview. + +This example builds on the :ref:`Dog extension we defined above +`. We'll create an ``dogArray`` as +a ``static let`` in our Dog extension, and include the item objects we +already created: + +.. code-block:: swift + + static let dogArray = [dog1, dog2, dog3] + +Then, when we iterate through our List, use the static ``dogArray`` if +running in a Preview, or use the ``@ObservedResults`` query if not in a Preview. + +.. code-block:: swift + + struct DogsView: View { + @Environment(\.isPreview) var isPreview + @ObservedResults(Dog.self) var dogs + var previewDogs = Dog.dogArray + + var body: some View { + NavigationView { + VStack { + List { + if isPreview { + ForEach(previewDogs) { dog in + DogRow(dog: dog) + } + } else { + ForEach(dogs) { dog in + DogRow(dog: dog) + }.onDelete(perform: $dogs.remove) + } + } + ... More View code + +This has the benefit of being lightweight and not persisting any data, but +the downside of making the View code more verbose. If you prefer cleaner +View code, you can create a database with data that you use in the Previews. + +.. _swift-create-a-db-with-data: + +Create a Database with Data for Previews +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In some cases, your only option to see database data in a SwiftUI Preview +is to create a database that contains the data. You might do this when populating +a property that can only be populated during a write transaction, rather +than initialized directly with a value, such as a :ref:`List +` or :ref:`MutableSet `. +You might also want to do this if your view relies on more complex object +hierarchies being passed in from other views. + +However, using a database directly does inject state into your SwiftUI Previews, +which can come with drawbacks. Whether you're using the SDK or Core Data, +stateful SwiftUI Previews can cause issues like: + +- Seeing unexpected or duplicated data due to re-running the database file + creation steps repeatedly +- Needing to perform a migration within the SwiftUI Preview when you make model changes +- Potential issues related to changing state within views +- Unexplained crashes or performance issues related to issues that are not + surfaced in a visible way in SwiftUI Previews + +You can avoid or fix some of these issues with these tips: + +- :ref:`Use an in-memory database, when possible (demonstrated in the example above) ` +- :ref:`Manually delete all preview data from the command line to reset state ` +- :ref:`Check out diagnostic logs to try to troubleshoot SwiftUI Preview issues ` + +You can create a static variable for your database in your model extension. +This is where you do the work to populate your database. In our case, we +create a ``Person`` and append some ``Dog`` objects to the ``dogs`` +List property. This example builds on the example above where we :ref:`initialized +a few Dog objects in an Dog extension `. + +We'll create a ``Person`` extension, and create a single ``Person`` object +in that extension. Then, we'll create a ``previewRealm`` by adding the +``Person`` we just created, and appending the example ``Dog`` objects from +the ``Dog`` extension. + +To avoid adding these objects more than once, we add a check to see if the +Person already exists by querying for Person objects and checking that +the count is 1. If the realm contains a Person, we can use it in our +SwiftUI Preview. If not, we add the data. + +.. literalinclude:: /examples/generated/swiftui/Model.snippet.extend-model-class-with-realm.swift + :language: swift + +To use it in the SwiftUI Preview, our ProfileView code expects a Profile. +This is a :ref:`projection of the Person object `. In our +Preview, we can get the database, query it for the Profile, and pass it to the +view: + +.. literalinclude:: /examples/generated/swiftui/Previews.snippet.preview-with-realm.swift + :language: swift + +If you don't have a View that is expecting a database object to be passed in, +but instead uses ``@ObservedResults`` to query a database or otherwise work +with an existing database, you can :ref:`inject the database into the view as +an environment value `: + +.. code-block:: swift + + struct SomeListView_Previews: PreviewProvider { + static var previews: some View { + SomeListView() + .environment(\.realm, Person.previewRealm) + } + } + +.. _swiftui-preview-use-in-memory-database: + +Use an In-Memory Database +````````````````````````` + +When possible, use an :ref:`in-memory database ` +to get around some of the state-related issues that can come from using +a database within a SwiftUI Preview. + +Use the :swift-sdk:`inMemoryIdentifier +` +configuration property when you initialize the database. + +.. code-block:: swift + + static var previewRealm: Realm { + var realm: Realm + let identifier = "previewRealm" + let config = Realm.Configuration(inMemoryIdentifier: identifier) + do { + realm = try Realm(configuration: config) + // ... Add data to realm + +.. note:: + + Do not use the :swift-sdk:`deleteRealmIfMigrationNeeded + ` + configuration property when you initialize a database for SwiftUI Previews. + Due to the way Apple has implemented SwiftUI Previews, using this property + to bypass migration issues causes SwiftUI Previews to crash. + +.. _swiftui-preview-delete-db-from-preview: + +Delete SwiftUI Previews +``````````````````````` + +If you run into other SwiftUI Preview issues related to state, +such as a failure to load a database in a Preview due to migration being +required, there are a few things you can do to remove cached Preview data. + +The Apple-recommended fix is to close Xcode and use the command line to +delete all your existing SwiftUI Preview data. + +1. Close Xcode. +2. From your command line, run: + + .. code-block:: shell + + xcrun simctl --set previews delete all + +It's possible that data may persist after running this command. This is +likely due to Xcode retaining a reference due to something in the Preview +and being unable to delete it. You can also try these steps to resolve issues: + +- Build for a different simulator +- Restart the computer and re-run ``xcrun simctl --set previews delete all`` +- Delete stored Preview data directly. This data is stored in + ``~/Library/Developer/Xcode/UserData/Previews``. + +.. _swiftui-preview-diagnose-crashes: + +Get Detailed Information about SwiftUI Preview Crashes +`````````````````````````````````````````````````````` + +If you have an unexplained SwiftUI Preview crash when using the SDK, first try +running the application on the simulator. The error messaging and logs available +for the simulator make it easier to find and diagnose issues. If you can +debug the issue in the simulator, this is the easiest route. + +If you cannot replicate a SwiftUI Preview crash in the simulator, you can +view crash logs for the SwiftUI Preview app. These logs are available in +``~/Library/Logs/DiagnosticReports/``. These logs sometimes appear after +a delay, so wait a few minutes after a crash if you don't see the relevant +log immediately. + +.. _swift-use-a-synced-database-in-previews: + +Use a Synced Database in Previews +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If your app uses Atlas Device Sync, you may wonder how to use a synced database +in your SwiftUI Previews. A better practice is to use static objects or a +local database that you populate with data for your SwiftUI Previews. + +In our example app, we can preview a view *associated* with Device Sync - +the LoginView - without needing to use a database at all: + +.. literalinclude:: /examples/generated/swiftui/Previews.snippet.preview-view-associated-with-sync.swift + :language: swift + +Since we're only viewing the static UI, we don't need to worry about the +``SyncContentView`` that contains the logic of whether to show the ``LoginView`` +or go to the ``OpenSyncedRealmView``. We can also skip previewing the +``OpenSyncedRealmView``, because that just handles logic associated with opening +a synced database and populating it for the ``DogsView``. So the next view we +want to see in a Preview is the ``DogsView``. + +Fortunately, the code to work with the SDK doesn't care whether +the database uses Device Sync or not - you work with the database in the same way. +So we can use the same non-synced database that we :ref:`created in the example +above ` in the SwiftUI Preview. + +.. literalinclude:: /examples/generated/swiftui/Previews.snippet.preview-dogs-view.swift + :language: swift diff --git a/source/frameworks/swiftui/write.txt b/source/frameworks/swiftui/write.txt new file mode 100644 index 0000000000..d73a08e067 --- /dev/null +++ b/source/frameworks/swiftui/write.txt @@ -0,0 +1,209 @@ +==================== +Write Data - SwiftUI +==================== + +.. meta:: + :description: Perform a quick write directly from a SwiftUI view, or use Swift code to update objects in the database. + :keywords: Realm, Swift SDK, code example + +.. facet:: + :name: genre + :values: tutorial + +.. facet:: + :name: programming_language + :values: swift + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Perform a Quick Write +--------------------- + +In addition to performing writes inside a transaction block, the Swift +SDK offers a convenience feature to enable quick writes without explicitly +performing a write transaction. + +When you use the ``@ObservedRealmObject`` or ``@ObservedResults`` property +wrappers, you can implicitly open a write transaction. Use the ``$`` operator +to create a two-way binding to the state object. Then, when you make changes +to the bound object or collection, you initiate an implicit write. + +The SwiftUI property wrappers work with :ref:`frozen data +` to provide thread safety. When you use ``$`` +to create a two-way binding, the Swift SDK manages thawing the frozen objects +so you can write to them. + +Update an Object's Properties +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In this example, we create a two-way binding with one of the state object's +properties. ``$dog.favoriteToy`` creates a binding to the model Dog +object's ``favoriteToy`` property + +When the app user updates that field in this example, the SDK +opens an implicit write transaction and saves the new value to the database. + +.. literalinclude:: /examples/generated/swiftui/QuickWrite.snippet.quick-write-property.swift + :language: swift + +Add or Remove Objects in an ObservedResults Collection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +While a regular :ref:`Results collection ` +is immutable, :swift-sdk:`ObservedResults ` +is a mutable collection that allows you to perform writes using a two-way +binding. When you update the bound collection, the SDK opens an implicit write +transaction and saves the changes to the collection. + +In this example, we remove an element from the results set using +``$dogs.remove`` in the ``onDelete``. Using the ``$dogs`` here creates a +two-way binding to a ``BoundCollection`` that lets us mutate the +``@ObservedResults`` ``dogs`` collection. + +We add an item to the results using ``$dogs.append`` in the +``addDogButton``. + +These actions write directly to the ``@ObservedResults`` collection. + +.. literalinclude:: /examples/generated/swiftui/QuickWrite.snippet.update-observed-results.swift + :language: swift + :emphasize-lines: 15, 27 + +.. include:: /includes/note-observedresults-swiftui-view.rst + +Append an Object to a List +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When you have a two-way binding with an ``@ObservedRealmObject`` that has +a list property, you can add new objects to the list. + +In this example, the ``Person`` object has a list property that forms a +:ref:`to-many relationship ` with one or more dogs. + +.. code-block:: swift + + class Person: Object, ObjectKeyIdentifiable { + @Persisted(primaryKey: true) var _id: ObjectId + @Persisted var firstName = "" + @Persisted var lastName = "" + ... + @Persisted var dogs: List + } + +When the user presses the ``Save`` button, this: + +- Creates a ``Dog`` object with the details that the user has entered +- Appends the ``Dog`` object to the ``Person`` object's ``dogs`` list + +.. include:: /examples/generated/swiftui/CreateObjects.snippet.add-dog-to-person-view.swift.rst + +Use Create to Copy an Object Into the Database +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There may be times when you create a new object, and set one of its properties +to an object that already exists in the database. Then, when you go to add the +new object to the database, you see an error similar to: + +.. code-block:: shell + + Object is already managed by another Realm. Use create instead to copy it into this Realm. + +When this occurs, you can use the :swift-sdk:`.create +` +method to initialize the object, and use ``modified: .update`` to set its +property to the existing object. + +.. example:: + + Consider a version of the DoggoDB ``Dog`` model where the ``favoriteToy`` + property isn't just a ``String``, but is an optional ``DogToy`` object: + + .. code-block:: swift + + class Dog: Object, ObjectKeyIdentifiable { + @Persisted(primaryKey: true) var _id: UUID + @Persisted var name = "" + ... + @Persisted var favoriteToy: DogToy? + ... + } + + When your app goes to create a new ``Dog`` object, perhaps it checks to see + if the ``DogToy`` already exists in the database, and then set the + ``favoriteToy`` property to the existing dog toy. + + When you go to append the new ``Dog`` to the ``Person`` object, you may + see an error similar to: + + .. code-block:: shell + + Object is already managed by another Realm. Use create instead to copy it into this Realm. + + The ``Dog`` object remains unmanaged until you append it to the ``Person`` + object's ``dogs`` property. When the Swift SDK checks the ``Dog`` object to + find the database that is currently managing it, it finds nothing. + + When you use the ``$`` notation to perform a quick write that appends the + ``Dog`` object to the ``Person`` object, this write uses the database it has + access to in the view. This is a database instance implicitly opened by + the ``@ObservedRealmObject`` or ``@ObservedResults`` property wrapper. + The existing ``DogToy`` object, however, may be managed by a different + realm instance. + + To solve this error, use the :swift-sdk:`.create + ` + method when you initialize the ``Dog`` object, and use + ``modified: .update`` to set its ``favoriteToy`` value to the existing + object: + + .. literalinclude:: /examples/generated/code/start/SwiftUI.snippet.copy-to-realm-with-create.swift + :language: swift + +Perform an Explicit Write +------------------------- + +In some cases, you may want or need to explicitly perform a write transaction +instead of using the implicit ``$`` to perform a quick write. You may want +to do this when: + +- You need to look up additional objects to perform a write +- You need to perform a write to objects you don't have access to in the view + +If you pass an object you are observing with ``@ObservedRealmObject`` or +``@ObservedResults`` into a function where you perform an explicit write +transaction that modifies the object, you must thaw it first. + +.. literalinclude:: /examples/generated/code/start/SwiftUI.snippet.thaw-the-passed-in-object.swift + :language: swift + +You can access the database that is managing the object or objects by calling +``.realm`` on the object or collection: + +.. literalinclude:: /examples/generated/code/start/SwiftUI.snippet.get-an-object-realm.swift + :language: swift + +Because the SwiftUI property wrappers use frozen objects, you must thaw +the database before you can write to it. + +.. example:: + + Consider a version of the DoggoDB app where a ``Company`` object + has a list of ``Employee`` objects. Each ``Employee`` has a list of + ``Dog`` objects. But for business reasons, you also wanted to have a + list of ``Dog`` objects available directly on the ``Company`` object, + without being associated with an ``Employee``. The model might look + something like: + + .. literalinclude:: /examples/generated/code/start/SwiftUI.snippet.swiftui-company-model.swift + :language: swift + + Consider a view where you have access to the ``Company`` object, but + want to perform an explicit write to add an existing dog to an existing + employee. Your function might look something like: + + .. literalinclude:: /examples/generated/code/start/SwiftUI.snippet.write-with-swiftui-observed-realm-object.swift + :language: swift diff --git a/source/includes/note-modify-schema-properties-of-synced-realms.rst b/source/includes/note-modify-schema-properties-of-synced-realms.rst index a02bd6549d..f7a56f5347 100644 --- a/source/includes/note-modify-schema-properties-of-synced-realms.rst +++ b/source/includes/note-modify-schema-properties-of-synced-realms.rst @@ -1,5 +1,5 @@ -.. note:: Modify Schema Properties of a Synced Realm +.. note:: Modify Schema Properties of a Synced Database - The following page demonstrates how to modify schema properties of a local - realm. Learn how to :ref:`modify schema properties of a synced realm - `. \ No newline at end of file + The following page demonstrates how to modify schema properties of a + non-synced database. Learn how to :ref:`modify schema properties of a + synced database `. diff --git a/source/includes/note-observedresults-swiftui-view.rst b/source/includes/note-observedresults-swiftui-view.rst index 3353039cc0..a361d7bc00 100644 --- a/source/includes/note-observedresults-swiftui-view.rst +++ b/source/includes/note-observedresults-swiftui-view.rst @@ -2,5 +2,5 @@ The ``@ObservedResults`` property wrapper is intended for use in a SwiftUI View. If you want to observe results in a view model, :ref:`register - a change listener `. + a change listener `. \ No newline at end of file diff --git a/source/includes/tip-swift-migrate-to-flexible-sync.rst b/source/includes/tip-swift-migrate-to-flexible-sync.rst deleted file mode 100644 index 8068ebc142..0000000000 --- a/source/includes/tip-swift-migrate-to-flexible-sync.rst +++ /dev/null @@ -1,7 +0,0 @@ -.. tip:: Migrate to Flexible Sync - - You can automatically migrate your App Services Device Sync Mode from - Partition-Based Sync to Flexible Sync. This enables you to take advantage - of the more expressive and granular Flexible Sync subscriptions and - permissions to manage what synced data your users can read and write. - For more information, refer to :ref:`ios-migrate-pbs-to-fs`. \ No newline at end of file diff --git a/source/includes/xcode-set-up-background-task-in-project.rst b/source/includes/xcode-set-up-background-task-in-project.rst deleted file mode 100644 index b34c923b5a..0000000000 --- a/source/includes/xcode-set-up-background-task-in-project.rst +++ /dev/null @@ -1,31 +0,0 @@ -To enable background tasks for your app: - -.. procedure:: - - .. step:: Add Background Modes Capability - - Select your app Target, go to the :guilabel:`Signing & Capabilities` - tab, and click :guilabel:`+ Capability` to add the capability. - - .. figure:: /images/xcode-select-target-add-capability.png - :alt: Screenshot of Xcode with app Target selected, Signing & Capabilities tab open, and arrow pointing to add Capabilities. - :lightbox: - - Search for "background", and select :guilabel:`Background Modes`. - - .. step:: Select Background Modes - - Now you should see a :guilabel:`Background Modes` section in your - :guilabel:`Signing & Capabilities` tab. Expand this section, and - click the checkboxes to enable :guilabel:`Background fetch` and - :guilabel:`Background processing`. - - .. step:: Update the Info.plist - - Go to your project's :file:`Info.plist`, and add a new row for - ``Permitted background task scheduler identifiers``. If you are - viewing raw keys and values, the key is - ``BGTaskSchedulerPermittedIdentifiers``. This field is an array. - Add a new item to it for your background task identifier. Set the - new item's value to the string you intend to use as the identifier - for your background task. For example: ``refreshTodoRealm``. diff --git a/source/sdk/files/configure-and-open.txt b/source/sdk/files/configure-and-open.txt index 34625c94c7..7492f53a55 100644 --- a/source/sdk/files/configure-and-open.txt +++ b/source/sdk/files/configure-and-open.txt @@ -31,6 +31,15 @@ file can manage. Some SDKs require explicit realm file schemas, while others automatically manage schemas for any object in your project, and you can specify a subset of schemas (objects) when opening a realm. +.. _sdks-default-and-file-url-database: + +Open a Default Database or Database at a File URL +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. _sdks-in-memory-database: + +Open a Database in Memory +~~~~~~~~~~~~~~~~~~~~~~~~~ .. _sdks-open-database-asynchronously: @@ -46,4 +55,3 @@ Provide a Subset of Models to a Database Expose More Than One Database ----------------------------- - diff --git a/temp/swift/swiftui/filter-data.txt b/temp/swift/swiftui/filter-data.txt index 1443b96dcc..a94d3894cc 100644 --- a/temp/swift/swiftui/filter-data.txt +++ b/temp/swift/swiftui/filter-data.txt @@ -2,6 +2,18 @@ Filter Data - SwiftUI ===================== +.. meta:: + :description: Extend Apple's .searchable implementation to SDK objects, filter or query ObservedResults, or section results. + :keywords: Realm, Swift SDK, code example + +.. facet:: + :name: genre + :values: reference + +.. facet:: + :name: programming_language + :values: swift + .. contents:: On this page :local: :backlinks: none @@ -14,74 +26,67 @@ Observe in SwiftUI Views The ``@ObservedResults`` property wrapper used in the examples on this page is intended for use in a SwiftUI View. If you want to observe results in a view model instead, :ref:`register a change listener -`. +`. -Search a Realm Collection -------------------------- - -.. versionadded:: 10.19.0 +Search an SDK Collection +------------------------ -The Realm Swift SDK allows you to extend :apple:`.searchable +The Swift SDK allows you to extend :apple:`.searchable `. When you use :swift-sdk:`ObservedResults ` to query a realm, you can specify collection and keypath in the result set to mark it as searchable. -The collection is the bound collection represented by your ObservedResults +The collection is the bound collection represented by your ``ObservedResults`` query. In this example, it is the ``dogs`` variable that represents the -collection of all Dog objects in the realm. +collection of all Dog objects in the database. The keypath is the object property that you want to search. In this -example, we search the dogs collection by dog name. The Realm Swift +example, we search the dogs collection by dog name. The SDK's ``.searchable`` implementation only supports keypaths with ``String`` types. .. literalinclude:: /examples/generated/swiftui/FilterData.snippet.searchable.swift :language: swift -Filter or Query a Realm with ObservedResults --------------------------------------------- +Filter or Query a Database with ObservedResults +----------------------------------------------- The :swift-sdk:`@ObservedResults ` property wrapper -opens a realm and returns all objects of the specified type. However, you +opens a database and returns all objects of the specified type. However, you can filter or query ``@ObservedResults`` to use only a subset of the objects in your view. .. seealso:: - For more information about the query syntax and types of queries that Realm - supports, see: :ref:`Read - Swift SDK ` and :ref:`Filter - Data - Swift SDK `. + For more information about the query syntax and types of queries that the + SDK supports, refer to: :ref:`Read ` and + :ref:`sdks-filter-data-swift`. + +Query with the Swift Query API +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To use ``@ObservedResults`` with the :ref:`Swift Query API +`, pass a query in a closure as an argument to +``where``: + +.. literalinclude:: /examples/generated/swiftui/FilterData.snippet.type-safe-query-filter.swift + :language: swift Filter with an NSPredicate ~~~~~~~~~~~~~~~~~~~~~~~~~~ To filter ``@ObservedResults`` using the :ref:`NSPredicate Query API -`, pass an :apple:`NSPredicate +`, pass an :apple:`NSPredicate ` as an argument to ``filter``: .. literalinclude:: /examples/generated/swiftui/FilterData.snippet.nspredicate-filter.swift :language: swift -Query with the Realm Type-Safe Query API -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 10.24.0 - Use *where* to perform type-safe queries on ObservedResults. - -To use ``@ObservedResults`` with the :ref:`Realm Type-Safe Query API -`, pass a query in a closure as an argument to -``where``: - -.. literalinclude:: /examples/generated/swiftui/FilterData.snippet.type-safe-query-filter.swift - :language: swift - -Section Filtered Results ------------------------- - -.. versionadded:: 10.29.0 +Section Results +--------------- The :swift-sdk:`@ObservedSectionedResults ` -property wrapper opens a realm and returns all objects of the specified type, +property wrapper opens a database and returns all objects of the specified type, divided into sections by the specified key path. Similar to ``@ObservedResults`` above, you can filter or query ``@ObservedSectionedResults`` to use only a subset of the objects in your view: