I have two entities : EntityA and (<-->>) EntityB
and I am trying to fetch all EntityA sorted by EntityB.displayOrder
let request: NSFetchRequest<EntityA> = EntityA.fetchRequest()
context.perform {
do{
var data = try self.context.fetch(request)
let sort = NSSortDescriptor(key: "displayOrder", ascending: true)
for (index,dat) in data.enumerated() {
let entitiesB = (dat.entitiesB?.sortedArray(using: [sort]))
data[index].entitiesB! = NSSet(array: entitiesB!)
}
self.testListTableVC.testData2 = data
self.testListTableVC.tableView.reloadData()
}
}
}
sort seems to be working, but when setting it back to data it isn't sorted..
You are sorting entitiesB, and then setting the data[index].entitiesB equal to entitiesB casted as an NSSet. If you read about NSSets in the docs, you'll see that they are not sorted lists.
You're essentially doing superfluous work, because you're sorting this array and then casting it as an unsorted NSSet
I don't know how you have your model setup, but it looks like you need this data for your testListTableVC.testData2 property. And it seems like whatever kind of array data is, it has a property called entitiesB which is an NSSet. I would recommend making it so that you can use a Swift Array, which would stay organized.
Related
I need to query MongoDB Realm from synced iOS and Android app. In Swift I can write something like this:
let dictionary = realm?.objects(myReamlObject.self)
let results = dictionary?.where {
$0.senses.glosses.term == "the term I want"
}
or using predicate:
let results = dictionary?.filter("ANY senses.glosses.term == %#", "the term I want")
Both work well, but I don't want to check ALL senses.glosses.term.
Every entry has (or could have) many senses and many glosses.
I would like to check term of first senses in first glosses only.
Something I would write like this:
let results = dictionary?.where {
$0.senses[0].glosses[0].term == "the term I want"
}
But it gives error:
Referencing subscript 'subscript(_:)' on 'Query' requires that
'List<myRealmObject_senses>' conform to 'RealmKeyedCollection'
Any suggestion on how to query only first index of an array in MongoDB Realm? Thank you
Let me re-state the question
How to query a Realm objects' List property - but only query on the first
element in the List.
The answer is going to depend on the amount of results being worked with.
Here's how to do it in a way that's O.K. for small datasets but NOT RECOMMENDED
Your models were not included in the question so let me use a simplified model of a PersonClass that as a List of DogClass objects
class PersonClass: Object {
#Persisted var name = ""
#Persisted var dogList = List<DogClass>()
}
class DogClass: Object {
#Persisted var name = ""
}
The idea here is to use Swift high-level functions to only test the first item in each persons doglist for a match (this can be applied to other languages as well)
//get all the people
let peopleResults = realm.objects(PersonClass.self)
//use the high-level Swift function compactMap to return all of
// the people whose first dog is named "Spot"
let persons = peopleResults.compactMap { person -> PersonClass? in
if person.dogList.first?.name == "Spot" {
return person
}
return nil
}
The downside is that this code overrides a fundamental advantage of Realm - that Realm objects are lazily loaded if Realm functions are used e.g. as soon as a high-level Swift function is used, ALL of the objects are loaded into memory, potentially overwhelming the device.
A better option is to simply add a managed property to the PersonClass that also points to the 0'th element in the list.
class PersonClass: Object {
#Persisted var name = ""
#Persisted var dogList = List<DogClass>()
#Persisted var mainDog: DogClass?
func addMainDog(withDog: DogClass) {
self.dogList.append(withDog)
self.mainDog = withDog
}
}
as you can see, there's also a function to add that first dog to the list and also populates the mainDog property which points to the same object. It's one property so the overall impact is very low but the advantages for simple queries are very high.
From there the query becomes trivial
let peopleResults = realm.objects(PersonClass.self).where { $0.mainDog.name == "Spot" }
Expanding on this, you could save the 0th element of each List object in the Parent object or even have a property in each child object that points to the first element in it's respective list.
I would like to perform this filter method using NSPredicate, but I'm having trouble converting the syntax.
objectsCollection.filter { $0.stringIds.contains(id) }
Sample class
class Object {
let stringIds: [String]
}
let objectsCollection: [Object]
let id = "The id we want to look for"
objectsCollection.filter { $0.stringIds.contains(id) }
My attempt with NSPredicate
This is how I thought it would work, but seems like it doesn't.
let filter = NSPredicate(format: "%# IN stringIds", id)
objectsCollection.filter(filter)
Error
reason: 'Expected object of type (null) for property 'stringIds' on object of type 'Object', but received: 6011ea4dda6853a3af97376e'
There are a few issues that need to be addressed.
This
let stringIds: [String]
is not a realm object and will not be persisted in Realm.
That needs to be a List property and the object type of the list is another Realm object. Realm Lists do not support primitives (very well). Also, don't name your objects the same name as another object.
class MyClass: Object {
let myStringIdList = List<MyStringIdClass>
}
and
class MyStringIdClass: Object {
#objc dynamic var myId = ""
}
to then get all of the MyClass objects that had a certain stringId in their list
let results = realm.objects(MyClass.self).filter("ANY myStringIdList.myId == %#", idToFind)
The string inside .filter can also be an NSPredicate if needed.
You can also use LinkingObjects to navigate back to the MyClass objects as well.
One other thing, when you cast realm objects to an array, they loose their connection to realm and are no longer live updating objects.
Also, Realm objects are lazily loaded meaning that thousands of objects have very little memory impact. Casting them to an array however, loads ALL of that data and can overwhelm the device with a large data set.
I have the following code to fetch Person object from CoreData model:
let fr:NSFetchRequest<Person> = Person.fetchRequest()
let sd = NSSortDescriptor(key: "name", ascending: false)
fr.sortDescriptors = [sd]
let persons = try? dataController.context.fetch(fr)
This works fine, and I get an array of [Person] in the right order (by name).
let children = persons[0].children // children set is in random order
However, each person has multiple Child objects. These are fetched automatically with each Person, however, these Child objects are not sorted. They come in random order.
How can I control the order in which these Child objects are fetched?
Edit: I am aware that I can order the nested objects manually after being fetched from CoreData, but I am reluctant to do that since that would not be efficient and would cause faulting on all child objects. I am looking for a way that lets CoreData fetch the child objects in a certain order.
Im making a Fitness app to learn Core data, and I have found that I need to let the user store every performed workout as a WorkoutLog item, and in there, there should be a series of ExerciseLogs which represents performances of that exercise (it contains each lift and also a reference to the actual exercise design).
Problem is that after a while i realize that i need to have these ordered, so that the next time i want to show the user their workout, the order that the exercisese were performed should be the same.
So I checked "ordered" in the top right of the image there, and now my code is in dire need of an update. I have tried to read as much as I could about working with NSOrderedSet and how to fetch them from core data and then manipulate them, but I havent really found much of use to me. (I have no experice in objective-c)
For example my code that used to be:
static func deleteWorkoutLog(_ workoutLogToDelete: WorkoutLog) {
guard let exerciseLogsToDelete = workoutLogToDelete.loggedExercises as? Set<ExerciseLog> else {
print("error unwrapping logged exercises in deleteWorkoutLog")
return
}
I get the error: .../DatabaseFacade.swift:84:77: Cast from 'NSOrderedSet?' to unrelated type 'Set' always fails
So what ive learned about sets and core data no longer seems applicable.
Im far from an expert in programming, but im very eager to learn how to get access to the loggedExercises instances.
TLDR; Is there a way to cast NSOrderedSet to something I can work with? How do we usually work with NSManagedSets from core data? Do we cast them to Arrays or MutableSets? I would very much appreciate an example or two on how to get started with retrieving and using these ordered sets!
Thanks
For anyone else wondering how to get started with orderedSets in core data:
After setting my the WorkoutLog.loggedExercises "to-many" relationship to be ordered, I managed to access them through the mutableOrderedSetValue function like this:
static func deleteWorkoutLog(_ workoutLogToDelete: WorkoutLog) {
let orderedExerciseLogs: NSMutableOrderedSet = workoutLogToDelete.mutableOrderedSetValue(forKey: "loggedExercises")
let exerciseLogsToDelete = orderedExerciseLogs.array
for exerciseLog in exerciseLogsToDelete {
guard let exerciseLog = exerciseLog as? ExerciseLog else {
return
}
Works great so far.
And to rearrange the NSOrderedSet I ended up doing something like this:
// Swap the order of the orderedSet
if let orderedExerciseLogs: NSOrderedSet = dataSourceWorkoutLog.loggedExercises {
var exerciseLogsAsArray = orderedExerciseLogs.array as! [ExerciseLog]
let temp = exerciseLogsAsArray[indexA]
exerciseLogsAsArray[indexA] = exerciseLogsAsArray[indexB]
exerciseLogsAsArray[indexB] = temp
let exerciseLogsAsOrderedeSet = NSOrderedSet(array: exerciseLogsAsArray)
dataSourceWorkoutLog.loggedExercises = exerciseLogsAsOrderedeSet
}
I have a Set and now I'm trying to get an object in a index position:
var testItem: ExpandableCell
if let object = expandableCells[indexPath.row] {
testItem = object as! ExpandableCell
}
But I get an error.
UPDATE:
I just convert from SET to Array my collection class variable and now I have this:
let realItem : ExpandableCell?
if let testItem: AnyObject = expandableCells[indexPath.row] {
realItem = testItem as! ExpandableCell
print(realItem?.title)
}
It compiles without a problem but when i run the code and the index its outside of its boundaries then I get the following error:
So seems that my if let statement is not working.
Any clue?
Set is an unordered collection of unique objects. Because they're unordered, getting them by an Int index makes no sense.
Perhaps you meant to use an Array, an ordered collection of objects. Array doesn't guarantee the uniqueness of its objects, but it does preserver ordering, thus Int indexes make sense.
you can use loop Instead if/let condition
for testItem in expandableCells {
if let realItem = testItem as? ExpandableCell {
print(realItem?.title)
}
}