Docs Menu

Define and Use Class Projections - Swift SDK

On this page

  • Overview
  • About These Examples
  • Define a Class Projection
  • Use a Class Projection in a View
  • Query for a Class Projection
  • Change Class Projection Properties
  • Use a Class Projection in a SwiftUI View
  • React to Changes to a Class Projection

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

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 {
@Persisted var firstName = ""
@Persisted var lastName = ""
@Persisted var address: Address?
@Persisted var friends = List<Person>()
}
class Address: EmbeddedObject {
@Persisted var city: String = ""
@Persisted var country = ""
}

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.

Note

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> {
@Projected(\Person.firstName) var firstName // Passthrough from original object
@Projected(\Person.address?.city) var homeCity // Rename and access embedded object property through keypath
@Projected(\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 or EmbeddedObject 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.

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)
Tip

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.

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"
}

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.

Tip
See also:

For a complete example of using a class projection in a SwiftUI application, see the Projections example app.

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 underlying Person object, but not changes to Person.lastName or Person.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"
}
←  React to Changes - Swift SDKModify an Object Schema - Swift SDK →
Give Feedback
© 2022 MongoDB, Inc.

About

  • Careers
  • Investor Relations
  • Legal Notices
  • Privacy Notices
  • Security Information
  • Trust Center
© 2022 MongoDB, Inc.