I have been trying to figure out how to use Realm with SwiftUI. The problem is that SwiftUI and Realm both have a List type. When you import SwiftUI into your Realm model to make the class a BindableObject and try to create a Realm List property there is an error.
Is it possible to use an instance of the Realm object model and make it a BindableObject in SwiftUI?
Core Data is incredibly fast if you consider what it does under the hood to do its magic. But Realm is faster, much faster. Realm was built with performance in mind and that shows. As Marcus Zarra once said in a presentation, you don't choose Core Data for its speed.
Realm is an embedded, object-oriented database that lets you build real-time, offline-first applications. Its SDKs also provide access to Atlas App Services, a secure backend that can sync data between devices, authenticate and manage users, and run serverless JavaScript functions.
Sure, it's very simple, use the module identifier as prefix like this :
let members = RealmSwift.List<Member>()
Now to the second part of your question. It's easy to encapsulate a Realm object (or list, or resultset) in an BindableObject
:
final class DBData: BindableObject {
let didChange = PassthroughSubject<DBData, Never>()
private var notificationTokens: [NotificationToken] = []
var posts = Post.all
init() {
// Observe changes in the underlying model
self.notificationTokens.append(posts.observe { _ in
self.didChange.send(self)
})
self.notificationTokens.append(Message.all.observe { _ in
self.didChange.send(self)
})
}
}
If you "link" a DBData
instance to a SwiftUI View
by either using @ObjectBinding
or @EnvironmentObject
the UI will be refreshed and the new value for posts
(in our example here) will be available each time the underlying realm changes.
@JustinMiller I created a ChannelsData class that I use to listen for changes in my chat channels from a Realm collection. I then update the UI by making the ChannelsData an @EnvironmentObject in my view. Here is what works for me in Xcode 11 GM Seed:
final class ChannelsData: ObservableObject {
@Published var channels: [Channel]
private var channelsToken: NotificationToken?
// Grab channels from Realm, and then activate a Realm token to listen for changes.
init() {
let realm = try! Realm()
channels = Array(realm.objects(Channel.self)) // Convert Realm results object to Array
activateChannelsToken()
}
private func activateChannelsToken() {
let realm = try! Realm()
let channels = realm.objects(Channel.self)
channelsToken = channels.observe { _ in
// When there is a change, replace the old channels array with a new one.
self.channels = Array(channels)
}
}
deinit {
channelsToken?.invalidate()
}
And then I use an @EnvironmentObject to grab the channels for my view:
struct ChannelsContainerView: View {
@EnvironmentObject var channelsData: ChannelsData
var body: some View {
List(channelsData.channels.indexed(), id: \.1.id) { index, _ in
NavigationLink(destination: ChatView()) {
ChannelRow(channel: self.$channelsData.channels[index])
}
}
}
}
Don't worry about the indexed() function in the List. But if you're curious, it comes from Majid's clever approach to creating flexible SwiftUI data storage classes here: https://mecid.github.io/2019/09/04/modeling-app-state-using-store-objects-in-swiftui/
And if you're coming into the view from another view, be sure to add .environmentObject(ChannelsData()) to your view link (and also in your Previews) or it won't work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With