Core Data Detail View from To-Many relationship - ios

I'm struggling with some aspects of Core Data, namely setting up a UITableView to list data from a to-many relationship.
I have three entities, Teams, TeamDetails and Players:
In the first view, I list the names of all the teams in the Teams entity, then tapping each cell segues to an intermediate view with buttons to either edit a team's details or edit a team's players. Tapping on a button segues to another UITableView that lists the Team's details or Players.
Listing the TeamDetails works, since it is a one-to-one relationship and a static cell table.
I'm trying to set up a UITableViewController that lists all the players that are associated with the selected team. So I pass the ManagedObjectContext etc to the table view controller via the segue as shown below:
else if ([segue.identifier isEqualToString:#"ShowPlayersSegue"]){
NSLog(#"Setting ShowPlayersTVC as a delegate of EditPlayerTVC");
ShowPlayersTVC *showPlayerTVC = segue.destinationViewController;
showPlayerTVC.delegate = self;
showPlayerTVC.managedObjectContext = self.managedObjectContext;
showPlayerTVC.team = self.team;
showPlayerTVC.player = self.team.playerDetails;
}
So, in my showPlayerTVC I want to get the set of players for that specific team, then have a row for each one that shows the playerName attribute as the cell textlabel.text.
I've been reading tutorials and playing around for ages without getting much success. I think I need to create an array of Player objects from the NSSet, which I can do, but I can't get the UITableview to list the objects. I'm probably missing something fundamental here, but any suggestions would be appreciated.

First, there are some issues with your data model.
The one-to-one to details I do not understand - why not just add attributes to the Team entity? Also, you may want to transform some of these into more flexible relationships, such as a Trainer entity, etc.
Also, your naming is flawed and will lead to programming errors or at least make your code difficult to read. Note the singular / plural confusion. Here is my suggestion for naming your entities / relationships:
Team - players <--------------->> team - Player
To display data in an a table view you should use NSFetchedResultsController. Let the FRC fetch the Player entity and give its fetch request the following predicate:
[NSPredicate predicateWithFormat:#"team = %#", teamObject];
Your segue code is almost correct. Give the new view controller a team attribute and use this in the above predicate of its fetched results controller. You do not need any player or "playerDetails" information (they are linked to the team anyway).

Related

Why can't I use a relationship in a NSManagedObject subclass computed property? (CoreData, swift)

I'm using CoreData and I have a Book entity and a ReadingSession entity. Each Book has many ReadingSessions.
If I add this computed property to the Book class, it works:
var sessions: [ReadingSession] {
let request = NSFetchRequest(entityName: "ReadingSession")
let predicate = NSPredicate(format: "book = %#", self)
request.predicate = predicate
return try! DataController.sharedInstance.managedObjectContext.executeFetchRequest(request) as! [ReadingSession]
}
But if I add this one, it doesn't:
var sessions: [ReadingSession] {
return readingSession?.allObjects as! [ReadingSession]
}
This last example sometimes returns the correct array and sometimes just returns an empty array.
I tried the same thing with other relationships inside computed properties and the results are the same.
Why is that? Is there a reason why I shouldn't try doing this? Is my first example a valid workaround, or will it cause me problems later on? Should I give up using computed properties for this and just repeat the code when I need it?
Thanks in advance!
Daniel
Answering my own question, as Wain pointed out in the comments I should be able to use relationships inside computed properties, and my problem was actually somewhere else.
If you're interested in the details read the next paragraph, but, long story short, if you're having the same problem you should look into your relationships and make sure they're set properly as To One or To Many. Also check if you're setting all your properties in the right places and only when necessary.
I edited my question to remove lots of unnecessary details and make it more readable, but in the end the problem was that I had a User entity with a selectedBook property which was set when a user selected a row. I had set it up as a To Many relationship, but a user can have only one selectedBook at a time, so it should have been a To One relationship there. Also when I created a book I set user.selectedBook to it, but the selectedBook property should only be set when a user selected a book from a row. So I was setting and trying to access some of my relationships at all the right times. I tried to access a user.selectedBook before a user had even selected a row, for instance, and then it obviously returned nil, which messed up many other computed properties. Now I fixed all that and I'm able to access all my relationships from within computed properties without any issues.

How to "save" (core-data) for "master/parent" entity while in "detail" entity view?

In my app I have a model as shown:
A Playlist can have none or multiple Items. On creation of a Playlist in the master view, it is stored with default data and the view segues to the Item List [detail] view where items can be added/deleted. This works fine.
However, while in the detail (item list) view there is a property (targetEndTime) that I wish to set that should be stored in the referenced Playlist object. I want this to store implicitly while on the detail view and not when I return to the Master View.
How do I achieve this?
If it is best practice to refactor out the core-data out of the two view controllers, how would this be done, and how would I save to the particular object reference?
Many thanks for your help and input.
UPDATE
TargetEndTime is a set property, from which the start time is calculated. It is a 'backtimer' - I want to know the time to start the playlist set so that it ends at the targetEndTime. So, when targetEndTime is set, I want to store this in the parent entity.
how in code might I achieve this? I am passing the playlist:
self.playlistViewController = (BXPlaylistViewController *)segue.destinationViewController;
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
Playlist *playlist = nil;
playlist = [self.fetchedResultsController objectAtIndexPath:indexPath];
self.playlistViewController.playlist = playlist;
As described by the comments you can access the playlist of an item directly via its relationship.
so assuming you keep a reference to the list in its detail view.
e.g #property Item *focusedItem
-(void)updateTargetEndTime:(NSTimeInterval)timeInterval {
Playlist *playlist = [self.focusedItem valueForKey:#"playlist"];
[playlist setValue:#(timeInterval) forKey:#"targetEndTime"];
}
Thats the most primitive version , but as you advance with coredata you'll want to use tools like mogenerator or simply Xcode
to create shim classes for your managed objects which will allow you to express it like this.
-(void)updateTargetEndTime:(NSTimeInterval)timeInterval {
item.playlist.targetEndTimeValue = timeInterval;
}

iOS: Design pattern for populating asynchronously fetched data

I am developing an app that fetches data from the web and displays it to the user. Assume that the data is reviews of a restaurant and one review is displayed on one view. The user can swipe left or right to go to the prev/next review. The data is fetched asynchronously (one thread for each review).
Here is the problem statement - Assume that 5 reviews have been fetched and the user is looking at the 3rd one currently. Now, the 6th review is fetched and I want to display it as the 4th review to the user (because the publish date of the 6th review is more recent than the 5th review). How should my model class inform the view controller?
I have considered some options -
Provide an array to the view controller and then send NSNotifications about new items to be inserted in-between the array at a specific index
Use an NSFetchedResultsController (this is a bit tricky because I am not using it with a table view controller)
View controller always asks for the next review to be displayed (from the model) and does not have a array of reviews with it
Are there any established design patterns that are employed in such a scenario? Other suggestions apart from the 3 above are welcome!
Just use an NSFetchedResultsController. When using NSIndexPaths just ignore the section. It's basically a glorified NSArray with free notifications.
Here's how I think I'd do it:
Make sure that the NSFetchRequest for your NSFetchedResultsController is sorted by publish date.
Handle NSFetchedResultsControllerDelegate methods.
When the NSFetchedResultsController updates, save the current object, reload the collection view, and then scroll to the saved object without any animation. This will appear to the user as if nothing happened to the current page.
While there is no perfect design pattern for every programming problem, the closest I can think of that relates to your problem is a combination of the Command and Observer patterns.
https://en.wikipedia.org/wiki/Command_pattern
The observer pattern is used in the NSNotification center.
While it's unclear as to why you'd want to skip a review, you could have two arrays to store them when fetched. The first holds all reviews that you have fetched. The second holds all reviews that are displayed.
Then you can get the last review in the fetched array, as if it were a stack. This way you always have the last one loaded displayed to the user.
I am confused why the order of display is different than the true order, ie why the 6th review comes before the 5th, but you asked about patterns to help.
Apart from MVC and observer, which are in the other answers and comments, I'd suggest using lazy loading with a virtual proxy. When reviews have been fetched, you can just display their proxy (eg with a "loading..." Message until they're fully in memory).
See more here: http://en.wikipedia.org/wiki/Proxy_pattern
I would recommend using the observing pattern to inform your controller than new data as been fetched. When receiving the signal, your view controller could update its array of "restaurant review" (either by adding the old one and reordering it according to some sort descriptors of your flavor or by querying the DAO directly).
Let's say you are fetching your data from internet and populating a CoreData entity with the results. Once you got your downloaded data you can populate your core data "Review" entity.
In order to "listen" at the change happening in core data, your controller should, in the viewDidLoad body, register itself as an observer for the NSManagedObjectContextDidSaveNotification.
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(updateInfo:) name:NSManagedObjectContextDidSaveNotification object:nil];
Then in your updateInfo, you can get the changes
- (void) updateInfo:(NSNotification *)notification
{
self.reviews = [self.managedObjectContext performRequest:myFetchRequest error:nil];
}

Inserting one entity into another one

I don't even know how to title this one:
Lets say I have a manufacturer entity and a model entity, with a one-to-many relationship.
Each manufacturer can have multiple models (just using these as an example).
manufacturer has a tableview and its independent fetchedResultsController, then when you press on a manufacturer cell you go to models viewcontroller that also has its own tableview and fetchedResultsController, ofc showing the relevant added models.
Let's say I would like to take one of the models and copy them or cut them into another manufacturer, I was thinking of a method styled like:
-(void)copyThis:(Model*)model toThat:(Manufacturer*)manufacturer
I am grabbing the right manufacturer object and the right model object but how can I implement the insertion of one to another?
To copy
Model *newModel = [NSEntityDescription insertNewObjectForEntityForName:#"Model" inManagedObjectContext:self.context];
newModel.property = model.property; //For every property
model.relationShipName = manufacturer;
[self.context insertObject:copyModel];
To cut
model.relationShipName = manufacturer;
(I assume that you have an xcdatamodeld and have generated an NSManagedObjectSubclass of your Model and Manufacturer entities)
What do you want to achieve with copying? Do you want a 'new' model with the exact parameters added to the other manufacturer, or do you want the relationship to be with the same model object?
Assuming you want to keep a single instance of the Model object:
Manufacturer *fromManufacturer = ...
Model *model = [[fromManufacturer models] objectAtIndex:...];
Manufacturer *toManufacturer = ...
[toManufacturer insertModelObject:model];
if (isCut) [fromManufacturer removeModelObject:model];
To get the insertModelObject and removeModelObject methods automatically, you can use Xcode to generate NSManagedObject subclasses for you automatically. It's under the Editor menu when you're looking at the CoreData Model file. Note that the names of the methods and objects may be different depending on the CoredData model structure and relationship names you've created.

How can I save an object that's related to another object in core data?

I'm having difficulty with a one to one relationship. At the highest level, I have a one to many relationship. I'll use the typical manager, employee, example to explain what I'm trying to do. And to take it a step further, I'm trying to add a one to one House relationship to the employe.
I have the employees being added no problem with the addEmployeesToManagereObject method that was created for me when I subclassed NSManagedObject. When I select an Employee in my table view, I set the currentEmployee of type Employee - which is declared in my .h.
Now that I have that current employee I would like to save the Houses entities attributes in relation to the current employee.
The part that I'm really struggling with is setting the managedObjectContext and setting and saving the houses attributes in relation to the currentEmployee.
I've tried several things but here's my last attempt:
NOTE: employeeToHouse is a property of type House that was created for
me when I subclassed NSManagedObject
House *theHouse = [NSEntityDescription
insertNewObjectForEntityForName:#"House"
inManagedObjectContext:self.managedObjectContext];
// This is where I'm lost, trying to set the House
// object through the employeeToHouse relationship
self.currentEmployee.employeeToHouse
How can I access and save the houses attributes to the currentEmployee?
since House is setup as an Entity it can be considered a table within the data store. If that truly is the case, you need to setup a 1 to 1 relationship between Employee and House in your data model.
If you have already done so, then it is as simple as calling. Although I'm not as familiar with one to one relationships with Core Data as I am with to-many. In either case, try one of the following
[self.currentEmployee addHouseObject: theHouse];
or
self.currentEmployee.employeeToHouse=theHouse;
then to the save to the managedObjectContext:
NSError *error=nil;
if (![self.managedObjectContext save:&error]{
NSLog(#"Core Data Save Error: %#", error);
}
Also, I'm not sure about your particular situation, but your self.managedObjectContext should already be the same as the one pointed to by self.currentEmployee.managedObjectContext.
Good luck,
Tim

Resources