How to use Realm with SwiftUI

后端 未结 2 1279
没有蜡笔的小新
没有蜡笔的小新 2020-12-13 16:53

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

相关标签:
2条回答
  • 2020-12-13 17:03

    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.

    0 讨论(0)
  • 2020-12-13 17:03

    @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.

    0 讨论(0)
提交回复
热议问题