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

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

Related

Core Data: How can one get updates to entities of a relationship?

I know that you can observe changes to entities you've fetched with #ObservedObject.
The problem is best illustrated with an example: If I had an entity school with a one to many relationship to students and each student has a property numberOfPencils then I want to recalculate the sum of all pencils within a school whenever a student in the school changes his numberOfPencils property.
If I access all the students through the schools entity (which is an #ObservedObject) then I only get updates to the sum if a new student is added but not when an existing student changes his numberOfPencils because student objects can only be observed one by one as far as I know.
The only thing I could come up with is setting up a NSFetchedResultsControllerDelegate with a fetchRequestPredicate that gets all the students associated with a school through some keyPath from student to school which uniquely identifies the school. But that seems like a hacky way to do it and there surely must be a better solution that is eluding me.
Basically I want to get notified of changes to any of the students associated with a school.
Derived properties is definely a good way to go.
But to clarify: #ObservedObject can also get updated on change of the item. You just have to manually trigger the update with objectWillChange.send()
Say your School cell looks like this:
struct SchoolCell: View {
#ObservedObject var school: School
var body: some View {
HStack {
let pencils = school.pupilsArray.reduce(0, {$0 + $1.numberOfPencils})
Text(school.name)
Spacer()
Text(verbatim: "\(pencils) pencils")
.foregroundColor(.secondary)
}
}
}
then the Pupil modify would work like this:
// ...
#ObservedObject var school: School
// ...
Button("+10 Pencils") {
school.objectWillChange.send()
pupil.numberOfPencils += 10
try? viewContext.save()
}
It is important though that SchoolCell IS an own struct, so it gets refreshed when its #ObservedObject school changes.

Swift 4 multi dimensional array or struct?

I'm creating a table viewcell. this viewcell will show hotel and relative rooms .
to do that I created 2 classes to define hotel and rooms. each of 2 classes represent the relative object. example:
hotel: services, position, lat, lang , description
room: bed_type, people, Availability
I know need now to create a structure or array or whatever that act like a three so basically so basically , for each hotel I can have more than one available room.
I was looking using an 2d array of any object as first solution but I don't know If this is best way to solve the problem.
ideally best would be to later can access object in an easy way...
my first second idea is to use a struct like this
struct SearchResults{
var Hotel: HotelModel
var Rooms: [RoomModel]
}
any suggestion is welcome
Here is how you can model your data.
Just create a Hotel model that contains an array of Room as its property.
struct Hotel {
var rooms: [Room]
}
struct Room {
//your properties...
}
Now, instead of using an extra SearchResults model, you can simply use a array of Hotel, i.e.
var results: [Hotel]

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
}

Should I create a separate Realm object for custom grouping?

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.

Understanding equality in RealmSwift

I have some items which I fetch from Realm:
let items = realm.objects(ShoppingListItem.self)
print("\(items.count) items") // 10 items
Each item has a subdepartment, and each subdepartment has a department:
let departments = items.flatMap({ $0.product?.subdepartment?.department })
print("\(departments.count) departments") // 10 departments
My goal is to find the unique Department objects from this list. The expected result is 4. My standard approach here is to use Set:
let uniqueDepartments1 = Set(departments)
print("\(uniqueDepartments1.count) unique departments via Set") // 9 unique departments via Set - but changes every time!?
I figure there must be something I'm missing related to the way Realm treats equality. But to check, I also attempt to get the unique departments via enumeration:
var uniqueDepartments2 = [Department]()
for department in departments {
if uniqueDepartments2.contains(department) {
continue
}
uniqueDepartments2.append(department)
}
print("\(uniqueDepartments2.count) unique departments via enumeration") // 4 unique departments via enumeration
This is indeed the expected result.
Why doesn't Set work here as I expected it to? And why is the count changing each time I run it?
Edit 2/27
Here are the models in play:
class ShoppingListItem: Object {
dynamic var product: Product?
convenience init(ingredient: Ingredient) {
self.init()
self.product = ingredient.product
}
}
class Product: Object {
dynamic var productID, subdepartmentID, title: String?
dynamic var subdepartment: Subdepartment?
}
class Department: Object {
dynamic var departmentID, title: String?
}
class Subdepartment: Object {
dynamic var subdepartmentID, departmentID, title: String?
dynamic var department: Department?
}
In short: in order for an Object subclass to be correctly hashable you must currently declare a property as the primary key.
Set is built on top of a hash table. This means it checks for the existence of a specific object by first computing the hash value of the object, mapping that hash value to a bucket within the hash table, and then checking each entry in that bucket for equality with the specified object.
The nature of this implementation means that for Set to work correctly on objects of a given type, both the hashValue property and == operator must obey specific rules. In particular, any two objects for which == returns true must return the same value from their hashValue property (the reverse is not required; it's completely valid for two unequal objects to have the same hashValue). Realm's implementation's of hashValue and == don't currently meet this criteria unless your class declares a property as the primary key. When no primary key is declared, Object's default hashValue calculation defers to -[NSObject hash], which simply returns the object's address in memory. Since Object.== allows two objects with different addresses to compare equal, this violates the relationship between hashValue and == I outlined above, leading to incorrect behavior when used with Set or as the key of a Dictionary. I've filed a bug against Realm requesting that the behavior of == be fixed to be compatible with the value returned by the hashValue property.