Define and Use Class Projections - Swift SDK
On this page
Overview
New in version 10.21.0.
A class projection is a class that passes through or transforms some or all of your Realm Database object's properties. Class projection enables you to build view models that use an abstraction of your object model. This simplifies using and testing Realm Database objects in your application.
With class projection, you can use a subset of your object's properties directly in the UI or transform them. When you use a class projection for this, you get all the benefits of Realm Database'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
About These Examples
The examples in this page use a simple data set. The two Realm object
types are Person
and an embedded object Address
. A Person
has
a first and last name, an optional Address
, and a list of friends
consisting of other Person
objects. An Address
has a city and country.
See the schema for these two classes, Person
and Address
, below:
class Person: Object { var firstName = "" var lastName = "" var address: Address? var friends = List<Person>() } class Address: EmbeddedObject { var city: String = "" var country = "" }
Define a Class Projection
Define a class projection by creating a class of type Projection. Specify the Object
or EmbeddedObject base whose
properties you want to use in the class projection. Use the @Projected
property wrapper to declare a property that you want to project from a
@Persisted
property on the base object.
When you use a List or a MutableSet in a class projection, the type in the class projection should be ProjectedCollection.
class PersonProjection: Projection<Person> { Person.firstName) var firstName // Passthrough from original object (\ Person.address?.city) var homeCity // Rename and access embedded object property through keypath (\ Person.friends.projectTo.firstName) var firstFriendsName: ProjectedCollection<String> // Collection mapping (\}
When you define a class projection, you can transform the original @Persisted
property in several ways:
- Passthrough: the property is the same name and type as the original object
- Rename: the property has the same type as the original object, but a different name
- Keypath resolution: use keypath resolution to access properties of the original object, including embedded object properties
- Collection mapping: Project lists or
mutable sets of
Object
s orEmbeddedObject
s as a collection of primitive values - Exclusion: when you use a class projection, the underlying object's
properties that are not
@Projected
through the class projection are excluded. This enables you to watch for changes to a class projection and not see changes for properties that are not part of the class projection.
Use a Class Projection in a View
Query for a Class Projection
To query for class projections in a realm, pass the metatype instance
YourProjectionName.self
to Realm.objects(_:).
This returns a Results object
representing all of the class projection objects in the realm.
// Retrieve all class projections of the given type `PersonProjection` let people = realm.objects(PersonProjection.self) // Use projection data in your view print(people.first?.firstName) print(people.first?.homeCity) print(people.first?.firstFriendsName)
Don't do derived queries on top of class projection results. Instead, run a query against the Realm object directly and then project the result. If you try to do a derived query on top of class projection results, querying a field with the same name and type as the original object works, but querying a field with a name or type that isn't in the original object fails.
Change Class Projection Properties
You can make changes to a class projection's properties in a write transaction.
// Retrieve all class projections of the given type `PersonProjection` // and filter for the first class projection where the `firstName` property // value is "Jason" let person = realm.objects(PersonProjection.self).first(where: { $0.firstName == "Jason" })! // Update class projection property in a write transaction try! realm.write { person.firstName = "David" }
Use a Class Projection in a SwiftUI View
You can use class projection in SwiftUI views. Class projection works with SwiftUI property wrappers:
You can access the class projection's properties to populate UI elements.
For a complete example of using a class projection in a SwiftUI application, see the Projections example app.
React to Changes to a Class Projection
Like other realm objects, you can react to changes to a class projection. When you register a class projection change listener, you see notifications for changes made through the class projection object directly. You also see notifications for changes to the underlying object's properties that project through the class projection object.
Properties on the underlying object that are not @Projected
in the
class projection do not generate notifications.
This notification block fires for changes in:
Person.firstName
property of the class projection's underlyingPerson
object, but not changes toPerson.lastName
orPerson.friends
.PersonProjection.firstName
property, but not another class projection that uses the same underlying object's property.
let realm = try! Realm() let projectedPerson = realm.objects(PersonProjection.self).first(where: { $0.firstName == "Jason" })! let token = projectedPerson.observe(keyPaths: ["firstName"], { change in switch change { case .change(let object, let properties): for property in properties { print("Property '\(property.name)' of object \(object) changed to '\(property.newValue!)'") } case .error(let error): print("An error occurred: \(error)") case .deleted: print("The object was deleted.") } }) // Now update to trigger the notification try! realm.write { projectedPerson.firstName = "David" }