Docs Menu

Docs HomeDevelop ApplicationsAtlas Device SDK

React to Changes - SwiftUI

On this page

  • Observe an Object
  • Observe Query Results
  • Sort Observed Results
  • Observe Sectioned Results
  • Observe App State

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 {
@ObservedRealmObject 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)")
}
}
}

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 {
@ObservedResults(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.

The @ObservedResults property wrapper can take a SortDescriptor parameter to sort the query results.

struct SortedDogsView: View {
@ObservedResults(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.

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.

@ObservedSectionedResults(Dog.self,
sectionKeyPath: \.firstLetter) var dogs

You might use these observed sectioned results to populate a List view divided by sections:

struct SectionedDogsView: View {
@ObservedSectionedResults(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()
}
}
}

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.
@ObservedObject 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()
}
}
}
←  Configure and Open a Realm - SwiftUIPass Realm Data Between SwiftUI Views →