Skip to content

Instantly share code, notes, and snippets.

@Gujci
Last active March 9, 2021 14:12
Show Gist options
  • Save Gujci/8307ab63bec7143486cab8dfa258acfd to your computer and use it in GitHub Desktop.
Save Gujci/8307ab63bec7143486cab8dfa258acfd to your computer and use it in GitHub Desktop.
Wrapper to bridge realms auto updating behaviour with SwiftUIs value based immutability requirement. (Extract from https://stackoverflow.com/questions/57160790/index-out-of-bounds-when-using-realm-with-swiftui)
import RealmSwift
typealias Model = Object & Identifiable
struct ListKey<T: Model>: Identifiable {
let id: T.ID
}
extension Results where Element: Model {
subscript(key: ListKey<Element>) -> Element? {
Element.primaryKey().flatMap { self.filter("\($0) = %@", key.id).first }
}
var keyedEnumeration: [ListKey<Element>] {
guard let key = Element.primaryKey() else { return [] }
let keys = value(forKey: key) as! [Element.ID]
return keys.enumerated().map { ListKey(id: $0.1) }
}
}
@tigerraj32
Copy link

Hi, can you please send a full demo code for this. I could not figure out where to use this extension

@Gujci
Copy link
Author

Gujci commented Mar 19, 2020

@tigerraj32 the given Stackoverflow article should explain. When you use a Realm Results collection, the difference in SwiftUIs value based concept interferes with the reference based database magic that Realm does, which leads to crashes.

So, let's say we have a someCollection, which is a Realm results collection. Listing them safely should be like:

ForEach(someCollection.keyedEnumeration) { key in
    let value = self.someCollection[key]
    // ... render item with the value
}

@tigerraj32
Copy link

@Gujci when I tried to get value
let value = self.someCollection[key]

I got the following error which prevents me to do that.
Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols

@Gujci
Copy link
Author

Gujci commented Mar 20, 2020

First of all there was an extra ) in the example. Second, have you tried returning any View in the place of the // ... render item with the value comment?

@duncangroenewald
Copy link

duncangroenewald commented Apr 6, 2020

Hi, I am getting the following errors. Note that StoreR is the Realm object.

Cannot convert value of type "[ListKey\<StoreR\>]" to expected argument type "Range\<Int\>"
Property 'keyedEnumeration' requires that 'StoreR' conform to 'Identifiable'

Closure containing a declaration cannot be used with function builder 'ViewBuilder'
To fix this be sure to return View() in the ForEach closure.

@Gujci
Copy link
Author

Gujci commented Apr 6, 2020

The error states that StoreR is not Identifiable.

@duncangroenewald
Copy link

Ah - forgot to add Identifiable protocol to the StoreR class. It already had a var id: UUID().

@duncangroenewald
Copy link

@Gujci can you create an extension for RealmSwift.List - my attempt fails with en error on "value(forKey: key)" - ambiguous use but I don't really understand why.

extension RealmSwift.List where Element: Model {

    subscript(key: ListKey<Element>) -> Element? {
        Element.primaryKey().flatMap { self.filter("\($0) = %@", key.id).first }
    }

    var keyedEnumeration: [ListKey<Element>] {
        guard let key = Element.primaryKey() else { return [] }
        let keys = value(forKey: key) as! [Element.ID]
        return keys.enumerated().map { ListKey(id: $0.1) }
    }
}

@Gujci
Copy link
Author

Gujci commented Apr 7, 2020

@duncangroenewald that is because RealmSwift.List s value(forKey: key) is different from the Results s. It returns [AnyObject] instead of Any.

let keys: [AnyObject] = value(forKey: key) compiles but it requires a cast to occur later in return keys.enumerated().map { ListKey(id: $0.1) } like

        return keys.enumerated().map { ListKey(id: $0.1 as! Element.ID) }

I have not tried whether it works or not. I suppose the key is usually a String or Int, and none of them conforms to AnyObject so this might crash.

@Gujci
Copy link
Author

Gujci commented Apr 7, 2020

An other hacky solution, to convert the List to Results with a simple filtering. As far as I know realm handles this very well, so not much performance is lost.

    var keyedEnumeration: [ListKey<Element>] {
        guard let key = Element.primaryKey() else { return [] }
        let keys = filter("TRUEPREDICATE").value(forKey: key) as! [Element.ID]
        return keys.enumerated().map { ListKey(id: $0.1) }
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment