React to Changes - SwiftUI
On this page
Observe an Object
The Swift SDK provides the @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.
struct DogDetailView: View { var dog: Dog var body: some View { VStack { Text(dog.name) .font(.title2) Text("\(dog.name) is a \(dog.breed)") AsyncImage(url: dog.profileImageUrl) { image in image.resizable() } placeholder: { ProgressView() } .aspectRatio(contentMode: .fit) .frame(width: 150, height: 150) Text("Favorite toy: \(dog.favoriteToy)") } } }
Observe Query Results
The Swift SDK provides the @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
.
Note
The @ObservedResults
property wrapper is intended for use in a
SwiftUI View. If you want to observe results in a view model, register
a change listener.
struct DogsView: View { Dog.self) var dogs ( /// The button to be displayed on the top left. var leadingBarButton: AnyView? var body: some View { NavigationView { VStack { // The list shows the dogs in the realm. // The ``@ObservedResults`` above implicitly opens a realm and retrieves // all the Dog objects. We can then pass those objects to views further down the // hierarchy. List { ForEach(dogs) { dog in DogRow(dog: dog) }.onDelete(perform: $dogs.remove) }.listStyle(GroupedListStyle()) .navigationBarTitle("Dogs", displayMode: .large) .navigationBarBackButtonHidden(true) .navigationBarItems( leading: self.leadingBarButton, // Edit button on the right to enable rearranging items trailing: EditButton()) }.padding() } } }
Tip
See also:
For more information about the query syntax and types of queries that Realm supports, see: Read - Swift SDK and Filter Data - Swift SDK.
Sort Observed Results
The @ObservedResults property wrapper can take a SortDescriptor parameter to sort the query results.
struct SortedDogsView: View { Dog.self, ( sortDescriptor: SortDescriptor(keyPath: "name", ascending: true)) var dogs var body: some View { NavigationView { // The list shows the dogs in the realm, sorted by name List(dogs) { dog in DogRow(dog: dog) } } } }
Tip
You cannot use a computed property as a SortDescriptor
for @ObservedResults
.
Observe Sectioned Results
New in version 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.
var firstLetter: String { guard let char = name.first else { return "" } return String(char) }
Then, we can use the @ObservedSectionedResults property wrapper to observe the results set divided into sections based on the computed variable key.
Dog.self, ( sectionKeyPath: \.firstLetter) var dogs
You might use these observed sectioned results to populate a List view divided by sections:
struct SectionedDogsView: View { Dog.self, ( sectionKeyPath: \.firstLetter) var dogs /// The button to be displayed on the top left. var leadingBarButton: AnyView? var body: some View { NavigationView { VStack { // The list shows the dogs in the realm, split into sections according to the keypath. List { ForEach(dogs) { section in Section(header: Text(section.key)) { ForEach(section) { dog in DogRow(dog: dog) } } } } .listStyle(GroupedListStyle()) .navigationBarTitle("Dogs", displayMode: .large) .navigationBarBackButtonHidden(true) .navigationBarItems( leading: self.leadingBarButton, // Edit button on the right to enable rearranging items trailing: EditButton()) }.padding() } } }
Observe App State
If your app uses Atlas Device Sync, you can observe the 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 Realm caches user credentials on the device, your app can work
offline while it has an app.currentUser
.
/// This view observes the Realm app object. /// Either direct the user to login, or open a realm /// with a logged-in user. struct FlexibleSyncContentView: View { // Observe the Realm app object in order to react to login state changes. var app: RealmSwift.App var body: some View { if let user = app.currentUser { // Create a `flexibleSyncConfiguration` with `initialSubscriptions`. // We'll inject this configuration as an environment value to use when opening the realm // in the next view, and the realm will open with these initial subscriptions. let config = user.flexibleSyncConfiguration(initialSubscriptions: { subs in let peopleSubscriptionExists = subs.first(named: "people") let dogSubscriptionExists = subs.first(named: "dogs") // Check whether the subscription already exists. Adding it more // than once causes an error. if (peopleSubscriptionExists != nil) && (dogSubscriptionExists != nil) { // Existing subscriptions found - do nothing return } else { // Add queries for any objects you want to use in the app // Linked objects do not automatically get queried, so you // must explicitly query for all linked objects you want to include. subs.append(QuerySubscription<Person>(name: "people")) subs.append(QuerySubscription<Dog>(name: "dogs")) } }) OpenFlexibleSyncRealmView() .environment(\.realmConfiguration, config) } else { // If there is no user logged in, show the login view. LoginView() } } }