Should I create a separate Realm object for custom grouping? - swift

I have been using Realm in an app and love it. Thank you! I have a question I would like to run by you folks and get some advice.
Lets say you have a realm object that contains a date field (simplified example):
class Appointment: Object {
dynamic var type = ""
dynamic var date = Date()
}
Now, suppose you have saved thousands of Appointments, and you are going to display these on a tableview or collectionview, grouped by week for example. So the datasource for your view would be something like this.
struct AppointmentsInWeek {
var startDate: Date?
var endDate: Date?
var appointments: [Appointment]
}
So, I have two options in mind I am thinking through with various pros and cons:

A) Make AppointmentsInWeek a subclass of Object, save it in the Realm, and use that as the datasource.
PROS:
Data for table will be lazy loaded from Realm
Simple to use at the moment it is needed.
CONS:
Keeping this up to date seems like a challenge. I would probably have some kind of observable looking at the Appointment in Realm and as any are added put them in the appropriate AppointmentWeek
B) Upon loading the screen with the tableview, fetch all appointments, or a subset of them, group them by their appropriate start and end date, and create an AppointmentsInWeek struct to use as the datasource.
PROS:
AppointmentsInWeek will always be up to date because it is created on the fly as needed
CONS:
We would have to keep all of this in memory, limiting the amount of appointments we could realistically display at once.
I started with option B but I am thinking now it might be better to go with option A. If I do, the biggest issue is making sure the Realm is always up to date when new appointments are added.
Questions
Are there other options I did not consider?
Which sounds like a better option?
Assuming I go with option A, would it make sense to have a class, that lives throughout the life of the app, in charge of observing the Appointments in Realm and when one is added (or changed), add it also to the appropriate AppointmentWeek?

Both options are fine, but I suggest option A. There are a few ways to approach option A.
First of all, you don't need to keep Appointment and AppointmentsInWeek in sync manually. You can use some combination of object properties, list properties, and linking objects properties to model connections between Appointments and AppointmentsInWeeks. Exactly how you might implement this depends on your app's specific needs, but here's one possibility:
class Appointment : Object {
dynamic var type = ""
dynamic var date = NSDate()
// Link to the week this appointment lives in, if desired
var week: AppointmentsInWeek? = nil
}
class AppointmentsInWeek : Object {
dynamic var startDate = NSDate()
dynamic var endDate = NSDate()
// Add appointment to List whenever it's created
let appointments = List<Appointment>()
}
A second possibility is to not use relationships at all, but to use queries instead. Realm supports queries through the Results class. You could add an ignored property on your AppointmentsInWeek class that queries the Realm for all appointments that fall within its date range:
class Appointment : Object {
dynamic var type = ""
dynamic var date = NSDate()
}
class AppointmentsInWeek : Object {
dynamic var startDate = NSDate()
dynamic var endDate = NSDate()
lazy var appointments: Results<Appointment> = {
// This implementation assumes you can get a reference
// to the Realm storing your Appointments somehow
return appointmentsRealm.objects(Appointments.self).filter(NSPredicate(format: "(date >= %#) AND (date < %#)", self.startDate, self.endDate))
}()
override static func ignoredProperties() -> [String] {
return ["appointments"]
}
}
In either case Realms, lists, and results all update automatically whenever the runloop of the thread they are on (usually the main thread) runs. Realm also supports a variety of notifications in case you need to react to changes manually.

Related

RealmSwift - how can I query a distinct array of objects

I have the following (simplified) model
class Taxonomy: Object {
var id: ObjectId // Primary Key
var parent: Taxonomy?
var name: String
var isSelected: Bool
var children = List<Taxonomy>()
var assets = List<Asset>()
}
class Asset: Object {
var id: String // Primary Key
var name: String
let taxonomies = LinkingObjects(fromType: Taxonomy.self, property: "assets")
}
The taxonomy can be an infinite hierarchy with each item containing zero or more children. A leaf would have no children. Assets can be classified as one or more 'leaf' taxonomies.
For example Animals->Birds->"White-faced Pigeon"
There could be multiple assets, let's say images ( but could be documents, recording, etc.) that have this classification with the references stored in Taxonomy.assets array.
If the user selected 'Birds' in the hierarchy then I want to retrieve a distinct list of all the assets from the SELECTED taxonomies and all their children's assets and the children's children etc.
In general only 'leaf' taxonomy items will contain assets - but not necessarily so since some assets may for example be as yet unclassified birds and simply associated with a higher level group until further refinement is done.
For example:
let selectedTaxonomies = realm.objects(Taxonomy.self).filter("isSelected = true")
let children = selectedTaxonomies.flatMap({$0.children})
let childrensChildren = children({$0.children})
etc... (how to traverse a hierarchy of arbitrary depth)
let assets = allSelectedTaxonomies.flatMap({$0.assets}).distinct(by: "id")
Is there any way to do this in some efficient way.
EDIT:
This seems to work but not sure it is very efficient
extension Taxonomy {
var allChildren: [Taxonomy] {
return Array(children)+Array(children.flatMap({$0.allChildren}))
}
}
and then
let selectedTaxonomies = realm.objects(Taxonomy.self).filter("isSelected == true")
let allChildren = selectedTaxonomies.flatMap({$0.allChildren})
let allTaxonomies = Array(selectedTaxonomies) + Array(allChildren)
assets = assets.filter("ANY taxonomies IN %#", allTaxonomies)
Example
Here the user could select images and drag and drop them onto one or more of the classifications on the left. So the same item could appear in more than one place. For example one of the eagle images might also appear in another hierarchy called Best Of -> Birds for example. Right now I am doing a super expensive fetch of the selected taxonomy hierarchy and am trying to establish if there is a way to build a Realm query maybe using Predicates that would be faster/more efficient.

Changing the displayed data based on a user selected Date in Swift?

I am new to Swift and am building a nutrient tracking application for a project. I need the users foods to be saved to the day that they add them to their food diary section which displays the foods added to each meal.
So the user adds an apple for example, and this would be saved to today, and once the day passes, the displayed data is stored to the date.
I currently have the food items saving with a Date using CoreData and this is displayed in a list:
func saveBreakfast() {
let newBreakfastItem = BreakfastItem(context: self.moc)
newBreakfastItem.id = UUID()
newBreakfastItem.name = self.item.name
newBreakfastItem.calories = Int32(self.totalCalories)
newBreakfastItem.carbs = Int32(self.totalCarbs)
newBreakfastItem.protein = Int32(self.totalProtein)
newBreakfastItem.fat = Int32(self.totalFats)
newBreakfastItem.date = self.dateAdded
do {
if self.mocB.hasChanges { // saves only if changes are made
try? self.mocB.save()
}
}
}
ForEach(self.BreakfastItems, id: \.id) { newBreakfastItems in
HStack{
Text(newBreakfastItems.name ?? "Unknown")
.font(.headline)
.font(.headline)
HStack {
Text("Cal: \(Int32(newBreakfastItems.calories))")
.fontWeight(.light)
.foregroundColor(Color("Green Font"))
Text("F: \(Int32(newBreakfastItems.fat))")
.fontWeight(.ultraLight)
Text("P: \(Int32(newBreakfastItems.protein))")
.fontWeight(.ultraLight)
Text("C: \(Int32(newBreakfastItems.carbs))")
.fontWeight(.ultraLight)
However, Im not sure how to view the food saved to past days, as currently the app is just adding to the same list all the time, regardless of the day its saved on.
Would some form of ForEach Date statement work to cycle through views based on the day selected from the calendar?
Thanks in advance!
If you want to fetch the items for a special day, you could just use a predicate to your FetchRequest with a specific date. However, then you might have many fetch requests for each day. (not sure how you display it in your app)
Second approach, you can just fetch all your items, and filter them based on a date and show them. Here is an example filtering your items in the ForEach.
ForEach(self.BreakfastItems.filter { 
$0.date == Date()
}, id: \.id) { newBreakfastItems in
This will only display items, where the date is equals the current Date(). You might need to check same date only, regardless of the time.
You can add more filters based on the current date you want to show.

Core Data Cross Reference Table (Swift 4)

Imagine I have an app with two Core Data entities: ingredients and cookies.
Ingredients have a title, a type, an image, and a few other properties.
Cookies have a title, a type, an image and a few other properties.
A cookie is comprised of several ingredients. Ingredients can be used in multiple different cookies. The Cookie entity and the Ingredient entity have an inverse relationship which allows me to see which cookies have which ingredients and which ingredients are used in which cookies.
All of this is working fine.
Here is my problem:
In addition to several other ingredients of varying quantities, chocolate chip cookies require one egg but oatmeal raisin cookies require two eggs.
If I was designing a MySQL database to model all this, I'd have a Cookie table (similar to my Cookie Core Data entity), an Ingredient table (similar to my Ingredient Core Data entity), and a cross-reference table CookieIngredient, which would include a cookieID, an ingredientID, and a quantity.
I've tried to do this in Core Data, but I can't seem to get the entity for the cross-reference table created correctly. I create an Entity named CookieIngredient with Integer 16 fields of cookieID, ingredientID, and quantity, but then when I attempt to create an NSManagedObject Subclass, the class which is created -- unlike the other two classes -- displays numerous errors telling me "extensions cannot contain stored properties", which doesn't seem to have anything to do with anything else in the universe.
None of the many "recipe" Swift tutorials I've found online use Core Data, and the ones that do don't use a cross-reference table.
Any suggestions appreciated. (Am I doing this the wrong way?)
do{
let fetchRequest : NSFetchRequest<Ingredient> = Ingredient.createFetchRequest()
fetchRequest.predicate = NSPredicate(format: "title == %#", title)
let fetchedResults = try the_context.fetch(fetchRequest)
if let anIngredient = fetchedResults.first {
let the_recipe = CookieIngredient(context: the_context)
the_recipe.quantity = amount
the_recipe.unit = unit
the_recipe.mutableSetValue(forKey: "recipeIngredient").add(anIngredient)
the_recipe.mutableSetValue(forKey: "recipeCookie").add(Cookie)
}
}
catch{
print( "unable to update ingredients" )
}
Assuming I have a Cookie object, I should be able to do this. But the error I'm getting is:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSManagedObjects of entity 'CookieIngredient' do not support -mutableSetValueForKey: for the property 'recipeIngredient''
Is mutableSetValue not the correct way to create the relationship?
Note: mutableSetValue was not the correct way to do it.
extension CookieIngredient {
#nonobjc public class func createfetchRequest() -> NSFetchRequest<CookieIngredient> {
return NSFetchRequest<CookieIngredient>(entityName: "CookieIngredient")
}
#NSManaged public var quantity: String
#NSManaged public var unit: String
#NSManaged public var recipeCookie: Cookie
#NSManaged public var recipeIngredient: Ingredient
}

Querying in Firebase by child of child

I have a structure of objects in Firebase looking like this:
-KBP27k4iOTT2m873xSE
categories
Geography: true
Oceania: true
correctanswer: "Yaren (de facto)"
languages: "English"
question: "Nauru"
questiontype: "Text"
wronganswer1: "Majuro"
wronganswer2: "Mata-Utu"
wronganswer3: "Suva"
I'm trying to find objects by categories, so for instance I want all objects which has the category set to "Oceania".
I'm using Swift and I can't really seem to grasp the concept of how to query the data.
My query right now looks like this:
ref.queryEqualToValue("", childKey: "categories").queryOrderedByChild("Oceania")
Where ref is the reference to Firebase in that specific path.
However whatever I've tried I keep getting ALL data returned instead of the objects with category Oceania only.
My data is structured like this: baseurl/questions/
As you can see in the object example one question can have multiple categories added, so from what I've understood it's best to have a reference to the categories inside your objects.
I could change my structure to baseurl/questions/oceania/uniqueids/, but then I would get multiple entries covering the same data, but with different uniqueid, because the question would be present under both the categories oceania and geography.
By using the structure baseurl/questions/oceania/ and baseurl/questions/geography I could also just add unique ids under oceania and geography that points to a specific unique id inside baseurl/questions/uniqueids instead, but that would mean I'd have to keep track of a lot of references. Making a relations table so to speak.
I wonder if that's the way to go or? Should I restructure my data? The app isn't in production yet, so it's possible to restructure the data completely with no bigger consequences, other than I'd have to rewrite my code, that pushes data to Firebase.
Let me know, if all of this doesn't make sense and sorry for the wall of text :-)
Adding some additional code to Tim's answer for future reference.
Just use a deep query. The parent object key is not what is queried so it's 'ignored'. It doesn't matter whether it's a key generated by autoId or a dinosaur name - the query is on the child objects and the parent (key) is returned in snapshot.key.
Based on your Firebase structure, this will retrieve each child nodes where Oceania is true, one at a time:
let questionsRef = Firebase(url:"https://baseurl/questions")
questionsRef.queryOrderedByChild("categories/Oceania").queryEqualToValue(true)
.observeEventType(.ChildAdded, withBlock: { snapshot in
print(snapshot)
})
Edit: A question came up about loading all of the values at once (.value) instead of one at at time (.childAdded)
let questionsRef = Firebase(url:"https://baseurl/questions")
questionsRef.queryOrderedByChild("categories/Oceania").queryEqualToValue(true)
.observeSingleEventOfType(.Value, withBlock: { snapshot in
print(snapshot)
})
Results in (my Firebase structure is a little different but you get the idea) uid_1 did not have Oceania = true so it was omitted from the query
results.
Snap (users) {
"uid_0" = {
categories = {
Oceania = 1;
};
email = "dude#thing.com";
"first_name" = Bill;
};
"uid_2" = {
categories = {
Oceania = 1;
};
"first_name" = Peter;
};
}
I think this should work:
ref.queryOrderedByChild("categories/Oceania").queryEqualToValue(true)

Best Practice for saving many to one in order using NSSet?

I am new to Swift and was wondering if anyone can give me some tips on how to store data in a Core Data NSSet sorted for an item that has the relationship one to many in order. I have a book class (the many) and I have a shelf class (the one) that has an NSSet of books in it.
class Shelf: NSManagedObject {
#NSManaged var books: NSSet
}
class Book: NSManagedObject {
#NSManaged var title: String
}
I can add books to the shelf and that works, but the order seems to be random every time I try to access them instead of the order I added them. I want the result in the order I added it to be retained. Is there a way to do this without adding another class like bookOnShelf to store the position?
var booksInOrder = myShelf.books.AsAnyObject as [Book]
for(var i = 0; i < mySlelf.books.count; i++)
{
output(booksInOrder[i])
}
Update your CoreData model to enable the "Ordered" attribute of the relationship.
You'll also want to change the property declaration in your code to:
#NSManaged var books: NSOrderedSet