Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to display Realm Results in SwiftUI List?

I have been able to save data in a Realm database, but have been unable to show the results in a SwiftUI List.

I know I have the data and have no problem printing the results in the console.

Is there a way to convert Realm Result into a format that can be displayed on a SwiftUI List?

import SwiftUI
import RealmSwift
import Combine

class Dog: Object {
    @objc dynamic var name = ""
    @objc dynamic var age = 0

    override static func primaryKey() -> String? {
        return "name"
    }
}

class SaveDog {
    func saveDog(name: String, age: String) {
        let dog = Dog()
        dog.age  = Int(age)!
        dog.name = name

        // Get the default Realm
        let realm = try! Realm()

     print(Realm.Configuration.defaultConfiguration.fileURL!)

        // Persist your data easily
        try! realm.write {
        realm.add(dog)
        }

        print(dog)
    }
}

class RealmResults: BindableObject {
    let didChange = PassthroughSubject<Void, Never>()

    func getRealmResults() -> String{
        let realm = try! Realm()
        var results = realm.objects(Dog.self) { didSet 
 {didChange.send(())}}
        print(results)
        return results.first!.name
    }
}

struct dogRow: View {
    var dog = Dog()
    var body: some View {
        HStack {
            Text(dog.name)
            Text("\(dog.age)")
        }
    }

}

struct ContentView : View {

    @State var dogName: String = ""
    @State var dogAge: String = ""

    let saveDog = SaveDog()
    @ObjectBinding var savedResults = RealmResults()
    let realm = try! Realm()

    let dogs = Dog()

    var body: some View {
        VStack {
            Text("Hello World")
            TextField($dogName)
            TextField($dogAge)
            Button(action: {
                self.saveDog.saveDog(name: self.dogName, 
                age:self.dogAge)
//                self.savedResults.getRealmResults()
            }) {
                Text("Save")
            }
            //insert list here to show realm data

            List(0 ..< 5) { 
             item in
                Text(self.savedResults.getRealmResults())
            } //Displays the same thing 5 times
        }
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

Some of the code probably may not make sense because I was attempting several approaches to see if anything would work.

This line, for example, will display the result in the List View.

return results.first!.name

If I just return results, nothing displays in the List Text View.

As I have commented below I will attempt the ForEach approach when I have time. That looks promising.

like image 725
K. Law Avatar asked Jun 23 '19 01:06

K. Law


People also ask

Does realm work with SwiftUI?

Realm objects have bindings to SwiftUI controls and, much like toggling the bought property, they already start a realm transaction to write the changes in the database whenever you change those values.

Should I use Core Data or realm?

If your project requires encryption or speed, then Realm is an obvious choice. If your project has a complex data model that changes frequently, then Core Data might be a better choice.

What is realm database in Swift?

The Realm Database Swift SDK enables mobile applications to access data stored in local realms. Optionally, interact with Atlas App Services features such as Functions, MongoDB Data Access, and authentication. The Realm Swift SDK supports Swift and Objective-C, and provides SwiftUI-friendly property wrappers.


1 Answers

The data that you pass in List or a ForEach must conform to the Identifiable protocol.

Either you adopt it in your Realm models or you use .identified(by:) method.


Even with that, the View won't reload if the data changes.

You could wrap Results and make it a BindableObject, so the view can detect the changes and reload itself:

class BindableResults<Element>: ObservableObject where Element: RealmSwift.RealmCollectionValue {

    var results: Results<Element>
    private var token: NotificationToken!

    init(results: Results<Element>) {
        self.results = results
        lateInit()
    }

    func lateInit() {
        token = results.observe { [weak self] _ in
            self?.objectWillChange.send()
        }
    }

    deinit {
        token.invalidate()
    }
}

And use it like:

struct ContentView : View {

    @ObservedObject var dogs = BindableResults(results: try! Realm().objects(Dog.self))

    var body: some View {
        List(dogs.results.identified(by: \.name)) { dog in
            DogRow(dog: dog)
        }
    }

}
like image 140
Matteo Pacini Avatar answered Sep 20 '22 06:09

Matteo Pacini