Realm: sorting by property in child object - ios

My Show object looks like this:
class Show: RLMObject {
dynamic var venue: Venue?
}
and my Venue object:
class Venue: RLMObject {
dynamic var title = ""
}
I need to be able to sort my Show objects by their Venue object's titles. I tried the following but got an error:
allShowsByLocation = Show.allObjects().sortedResultsUsingProperty("venue.title", ascending: true)
The error is: Invalid sort column', reason: 'Column named '(null)' not found.

Realm doesn't yet support sorting RLMResults by a sub-property. As a work-around, you could query for Venues and return its linking object for each index:
allVenues = Venue.allObjects().sortedResultsUsingProperty("title", ascending: true)
func showAtIndex(index: UInt) -> Show {
return (allVenues[index] as Venue).linkingObjectsOfClass("Show", forProperty: "venue")
}
Or you could simply add a venueTitle property to your Show model which would then allow your query to work:
allShowsByLocation = Show.allObjects().sortedResultsUsingProperty("venueTitle", ascending: true)
You can also subscribe to GitHub issue #1199 to follow our progress on supporting sub-property sorting.

I'm very sad when Realm not support this feature.
I try another solution for this issue and it work well
copy all object in RLMResults to an array
Sort sub property of object in Array using compare

Related

Realm: property declared as origin of linking objects property does not exist

I'm using Realm Swift for the following code:
class Item: Object {
#Persisted(primaryKey: true) var name = ""
#Persisted(originProperty: "items") var collection: LinkingObjects<SomeCollection>
}
class SomeCollection: Object {
#Persisted(primaryKey: true) var name = ""
let items = List<Item>()
}
On app startup when I initialize the Realm instance I got:
Property 'SomeCollection.items' declared as origin of linking objects property 'Item.collection' does not exist" UserInfo={NSLocalizedDescription=Schema validation failed due to the following errors:
Interestingly, if I change the line for LinkingObjects to:
let collection = LinkingObjects(fromType: SomeCollection.self, property: "items")
it then works fine, however, after adding an Item instance to the items list of SomeCollection, the collection property of Item instance doesn't show any linked SomeCollection, which I thought should be linked automatically?
What am I doing wrong here?
Thanks for help!
This
let items = List<Item>()
Needs to be this
#Persisted var items = List<Item>()
or
#Persisted var items: List<Item>
depending on the use case.
The let syntax is how we used to do it.
That's called and Inverse Relationship and is covered in the documentation a bit further.

Filtering Realm with nested subqueries

My app has data that looks like this.
class ShelfCollection: Object {
let shelves: List<Shelf>
}
class Shelf: Object {
let items: List<Item>
}
class Item: Object {
var name: String
let infos: List<String>
}
I'm trying to get all shelves in a shelf collection where any items match the query either by name or by an element in their infos list. From my understanding this predicate should be correct, but it crashes.
let wildQuery = "*" + query + "*"
shelfResults = shelfCollection.shelves.filter(
"SUBQUERY(items, $item, $item.name LIKE[c] %# OR SUBQUERY($item.infos, $info, info LIKE[c] %#).#count > 0).#count > 0",
wildQuery, wildQuery
)
It complies as a NSPredicate, but crashes when Realm is attempting to parse it, throwing me
'RLMException', reason: 'Object type '(null)' not managed by the Realm'
I suspect the nested subquery might be what fails, but I don't know enough about NSPredicate to be sure. Is this an acceptable query, and how can I make it.. work?
This is an answer and a solution but there's going to be a number of issues with the way the objects are structured which may cause other problems. It was difficult to create a matching dataset since many objects appear within other objects.
The issue:
Realm cannot currently filter on a List of primitives
EDIT: Release 10.7 added support for filters/queries as well as aggregate functions on primitives so the below info is no longer completely valid. However, it's still something to be aware of.
so this Item property will not work for filtering:
let infos: List<String>
However, you can create another object that has a String property and filter on that object
class InfoClass: Object {
#objc dynamic var info_name = ""
}
and then the Item class looks like this
class Item: Object {
var name: String
let infos = List<InfoClass>()
}
and then you filter based on the InfoClass object, not it's string property. So you would have some objects
let info0 = InfoClass()
info0.info_name = "Info 0 name"
let info1 = InfoClass()
info1.info_name = "Info 1 name"
let info2 = InfoClass()
info2.info_name = "Info 2 name"
which are stored in the Item->infos list. Then the question
I'm trying to get all shelves in a shelf collection...
states you want to filter for a collection, c0 in this case, shelves whose items contain a particular info in their list. Lets say we want to get those shelves whose items have info2 in their list
//first get the info2 object that we want to filter for
guard let info2 = realm.objects(InfoClass.self).filter("info_name == 'Info 2 name'").first else {
print("info2 not found")
return
}
print("info2 found, continuing")
//get the c0 collection that we want to get the shelves for
if let c0 = realm.objects(ShelfCollection.self).filter("collection_name == 'c0'").first {
let shelfResults = c0.shelves.filter("ANY items.infoList == %#", info2)
for shelf in shelfResults {
print(shelf.shelf_name)
}
} else {
print("c0 not found")
}
I omitted filtering for the name property since you know how to do that already.
The issue here is the infos could appear in many items, and those items could appear in many shelf lists. So because of the depth of data, with my test data, it was hard to have the filter return discreet data - it would probably make more sense (to me) if I had example data to work with.
Either way, the answer works for this use case, but I am thinking another structure may be better but I don't know the full use case so hard to suggest that.

Sorting Realm results collection on computed property?

I'm using Realm in my Swift iOS app and I have a model that I'm trying to add a computed property to. Here's what the class looks like:
class Conversation: Object {
#objc dynamic var conversationId: String = generateConversationId()
#objc dynamic var createdTime = Date()
#objc dynamic var title: String = ""
let messages = List<Message>()
var lastUpdated: Date {
if let newestMessage = self.messages.sorted(byKeyPath: "timestamp", ascending: false).first {
return newestMessage.timestamp
} else {
return Date(timeIntervalSince1970: 0)
}
}
}
As you can see, this object represents a conversation which has several properties one of which is a list of messages that belong to the conversation. The computed property that I've added (lastUpdated) should just return the timestamp of the most recent message in the messages list or, when the list is empty, it would return a default date value.
In one of my view controllers, I'm creating a results collection to access the Conversation objects like this:
var conversations: Results<Conversation> = realm.objects(Conversation.self)
This works and I can access the lastUpdated property on each Conversation as expected. However, I'd like to have this collection sorted by the lastUpdated property so I tried modifying my results collection like this:
var conversations: Results<Conversation> = realm.objects(Conversation.self).sorted(byKeyPath: "lastUpdated", ascending: true)
When I do this, the app throws the following exception:
Terminating app due to uncaught exception 'RLMException', reason: 'Cannot sort on key path 'lastUpdated': property 'Conversation.lastUpdated' does not exist.'
I think this might be happening because the lastUpdated computed property isn't persisted on the Conversation object (I would use Realm Studio to quickly verify this but my Realm is encrypted). I'm not explicitly ignoring this property but I assume this is the case because lastUpdated is not decorated with #objc dynamic like all of my other properties. I tried adding that, wiping my app install, recompiling and testing again but I still end up with the same exception.
Maybe it's not possible to persist a computed property (because, well, it's computed!) but is there another way to specify that my Conversation results collection should be sorted by this property?
As you suspect, you cannot persist a computed property and Realm's sorted(byKeyPath:,ascending:) method only works with persisted properties. There is no way to sort a Results instance based on a computed property while keeping the auto-updating Results.
However, there is a workaround if you don't need the auto-updating nature of Results. You can use Swift's sorted method, which takes a closure as its sorting parameter, which will result in a return value of type [Conversation]:
let conversations = realm.objects(Conversation.self).sorted(by: {$0.lastUpdated < $1.lastUpdated})

Retrieving object properties with relation to their parent categories

I am new to realm and iOS development so I apologize in advance if something isn’t explained properly or is just incorrect.
I have 2 Realm Object classes:
class Category: Object {
#objc dynamic var name: String = ""
#objc dynamic var color: String = ""
let trackers = List<Tracker>()
}
and
class Tracker: Object {
#objc dynamic var timeSegment: Int = 0
var parentCategory = LinkingObjects(fromType: Category.self, property:
"trackers")
}
I’m able to store new timeSegment properties consistently; however, the issue is that I cannot retrieve & display a collection of timeSegment values relating to their parentCategory. setting
var entries : Results<Tracker>?
produces all results for every category, which is the only result i'm able to pull so far after testing.
Any help is appreciated, and can follow up with any additional details. Thanks
You need to call objects on your Realm object with a filter for fetching only results that match a predicate. The realm object in this code is an instance of the Realm class.
func getTrackersWithName(_ name: String) -> Results<Tracker> {
return realm.objects(Tracker.self).filter("name = \"\(name)\"")
}
This tells Realm to fetch all objects that match the filter predicate. In this case, the filter predicate matches any object where the value of the "name" property matches the string that is passed into the method.

Swift - Sort the content of NSSSet

I am using Core Data to fetch the list of clinics, the clinics has relations named doctors mapped to Doctor entity.
My issue is, every time I fetch doctors, which is of type NSSSet, the record is fetched randomly, I want to sort it alphabetically using Doctor.name
I tried the following
self.doctorList = clinic.doctors?.sortedArray(using: [NSSortDescriptor(key: "name", ascending: true)])
Where am I going wrong?
Thanks.
(NS)Set is an unordered collection type.
To order a Set convert it to an array with the allObjects property and sort the array.
self.doctorList = (clinic.doctors!.allObjects as! [Doctor]).sorted(by: { $0.name < $1.name })
You can even sort the set directly but the result is always an array
self.doctorList = (clinic.doctors as! Set<Doctor>).sorted(by: { $0.name < $1.name })
And if the set is declared as native Set<Doctor> anyway you can omit the type cast.
I recommend to declare the doctors relationship as non-optional. If there is no associated doctor the set is empty. But a clinic without any doctor is very unlikely ;-)

Resources