I would like to know what is the usual approach for syncing UI with Core Data, for example when you display data stored by Core Data in a table view, and you want to show additional info about an object when the user taps its cell in the table view, how would you link the table view cell to a specific object in Core Data ? I have seen many people not recommending to use IDs since Core Data wasn't designed in this way, but then what to use if we are not using an ID ? I would have wanted to do something like : "OK, the selected cell has this ID, let's go in Core Data fetch the object with the same ID" But if I'm not doing this, how can I do ?
Same question for other cases where you need this kind of link... in my case, I need a link between pins on a map and Core Data objects, so that I display additional info when the pin is tapped, a little bit like in Apple Maps for example...
What should I do ?
Thank you.
You can do that by using NSFetchedResultsController - you can subclass your UITableViewController from CoreDataTableViewController (.h .m) and the only method you need to override would be cellForRowAtIndexPath:. Then can access objects directly like this:
MyCustomObject *obj = [self.fetchedResultsController objectAtIndexPath:indexPath];
For your second question, when you are not using a table view there are two options. One you can fetch the object (assuming you are at the very top of the stack) or two, far more common, you have the NSManagedObject instance passed into you using simple dependency injection during the storyboard segue.
Any view that is below/after a table view should never need to fetch or call something like -objectWithID: as the object is already in memory and the controller that is giving you the ID already has the object. Just pass the object around like any other language.
Related
I am trying to understand how to correctly use the MVVM pattern in iOS. Lets assume we have a music player app with a playlist like in the following sketch:
I have a PlaylistViewController and a PlaylistViewModel. Furthermore the cells (SongTableViewCell) have a view model SongCellViewModel which holds the data (song name,..). If the user presses the X button on the TableViewCell I have to delete the song from the database. The cell is calling the following function in the SongCellViewModel:
func deleteSongFromPlaylist() {
DatabaseService.shared.deleteSong(self.song)
}
My question: Is it good to do it that way? It doesn't feel right to initiate database operations in the view model of the TableViewCell.
I suggest moving such operations to PlaylistViewModel and do all operations from there. It also simplifies implementation of later improvements like showing activity indicator if needed or reloading certain cells or views from there.
I think that SongCellViewModel should have a actionBlock which will take enum with associated values - for example .select(song) .delete(song) - and this way you can easily communicate between PlaylistViewModel and SongCellViewModel.
One more suggestion is to use Dependency Injections instead of using shared for all type of services.
In my application there are two view controllers that navigate to a DetailsViewController.
Right now, when the DetailsViewController appears, I fetch data from the server and display it on the UI. I dislike this because the UI is blank while the network request is going on. What I want is that the data be loaded in the previous view controllers and then passed to DetailsViewController.
Now the problem is that I have the exact same "load-data-and-then-push" code in two view controllers and I'm not sure what the most sensible way is to remove the repetition.
One idea is to have the two view controllers inherit from a common superclass which contains the loading/pushing method. I don't like this strategy because, supposing I have more ViewControllers like DetailsViewController down the line, I wouldn't like to write a loading superclass for each one.
Another idea would be to define a static method in the DetailsViewController which the two view controllers can invoke but this method contains UI related code (specifically, code to show an HUD Progressbar and a UIAlertView in case network fetch fails) which makes me uncomfortable.
I am very new to iOS and Objective-C so I might be missing something simple and obvious.
My favorite would be in this case to create a new class which handles the loading of the data (like http-request, etc.) and to create a delegate protocol for this class. This delegate callback might then be implemented in your two viewControllers which would then perform the push segue to your DetailsViewController when called. Delegation is a very nice and powerful feature, check out the documentation here: Delegation
Well, I'd better write it in the comments, but I have no reputation for that.
Imagine you are reading a json with several students information (name, year, etc.)
you can create a student object with the property that will be read and an object that will have a method that will run in the background that will be responsible for accessing your WS (JSON or whatever it is) and record this information to the student object. So if you have 10 students you will have an NSArray containing 10 students. This array is what you will for your next viewcontroller.
It is a lot of code, but you think examples easily.
If you use Storyboards you can use prepareForSegue: sender: to pass your data/model class to your DetailsViewController. If you use xib's you can do the same after instantiating the DetailsViewController and before pushing it.
If you need to load subsequent data from your server you should write a class that does this network stuff for you.
If DetailsViewController needs to load some additional data, you can use something like a loading view like Andy suggested. This is a widely used method.
I'm new to CoreData, and my problem is that i want to make an NSManagedObject from a ViewController Class that has a table view, basically i want to save an object inside a tableView "every time the user adds a cell", but that ViewController is presented from a tableview menu, "object at index", the question is, how can i save data for that
ViewController from my AppDelegate? or how can i access that ViewController From my AppDelegate...
Here's an image of my Storyboard and somehow explaining what I'm trying to achieve.
Thanks!!
I've read through your question ten times and i don't seem to be understanding it fully.
From what i understand you want to save the data into core data from the bottom tableviewcontroller which is a UIViewController with an UITableView as a subview.
I don't see how that should be difficult unless i understood the problem wrong.
The basic Xcode template for an iOS project using Core Data implements the initialization within the app delegate but relying on the app delegate as a connector is something i don't do. I always abstract that information into a singleton which can be accessed from any class that implements that logic.
Also the basic Xcode template for a core data project also contains logic for adding entries within the tableview so i would recommend looking at that.
If you want specific example source code i can do that but i recommend looking at the core data template.
I searched a bit but couldn't find the answer I was actually looking for. Simple scenario:
TableViewController with an NSArray of beers. If I select the cell, a detail-view with beer details should be displayed. Now the beers are stored using CoreData.
In the RootViewController I use NSFetchedResultsController to fetch all the beers.
Now my question is: Should I just set the beer property of the destination to the selected beer, or should I create a new NSFetchedResultsController and perform a whole new fetch with a set up NSPredicate?
Where is the difference?
Hmm...
For me I think the biggest factor would be whether the data changes or not.
NSFetchedResultsController is good for performing fetches etc... but it comes into its own when dealing with CoreData entities that change.
The NSFetchedResultsControllerDelegate methods are there to update the table/collection view when the CoreData model is updated on a background thread (i.e. from a network request etc...).
If your model doesn't change at all then I'd go with just passing the beer object in and using the properties of that object.
If the model does change then use the NSFRC and its delegate methods to perform a new fetch.
If you already hold the selected object in your RootViewController and it has all attributes that you will present in the detailViewController, then I see no point setting up the NSFetchedResultController for the detailVC. You can just pass the object to detail in prepareForSegue or so.
The point of using NSFetchedController is that you don't have to update your tableview datasource array and reload rows/table with each change in your list provided you use Apple's CoreDataTableViewController template.
Explanation
My app basically uses a mapview with an overlay of polygons that represent buildings, coupled with an annotation. So for this it imports a custom class called Annotation that handles the popup details when the annotation is tapped, meaning it store the building's name and address. At this time the callout (the blue disclosure button) loads an empty DetailViewController object (as there's not really any data to pass through).
I soon added a new feature in the form of a searchable table that loads custom objects of the Building class (with similar properties to Annotation, plus images and more details) that then loads in the aforementioned DetailViewController class with the building's details.
So to summarise, the MapVC contains multiple annotations, which when the relevant disclosure button is tapped open the DetailVC. SearchVC is accessed by a button on the MapVC and has a table of Building objects, which loads a DetailVC with the relevant data, like so:
Next Step
So now I want to implement functionality into the blue disclosure button on the callout, so when the user taps it it'll load the building details. At the moment all it has is the annotation's details. I could add the extra properties to the annotation to make it complete but I think it's much better to just work with one custom class called Building, which has a MKAnnotation nature. Then this Building class is loaded for the annotations and searchVC's table.
Question
So finally, what's the best way to go around this? I want all the data to be stored independently of any of the VCs in the diagram. I followed a tutorial from Apple (the BirdSighting one) which uses a separate Datacontroller class, which I'd then load into other classes. Is this the best approach?
You're talking about MVC, model-view-controller. This is a very smart way to handle things, and is a good practice to get into. The model stores the data, in this case, building names, etc. The view displays data. The controller is what connects the view to the model, updates the view, gets notifications from users and in turn updates the model.
I use a singleton pattern for model data. Only one instance of a singleton is ever present in an application. That way, the data is not bound to any one particular view controller. Data only gets updated in once place, the model. It's a lot easier to trouble shoot issues with a singular point of convergence for application data objects.
I have a macro that I define in my PCH file.
#import "DataController.h"
#define DATA() [DataController sharedInstance]
In my code I can easily get to my model class by calling
DataController *data = DATA();
All of my views and viewcontrollers access objects stored in DataController, so there are never two view controllers handling separate pieces of information independently. All references point to the same place.
Some people use the AppDelegate object to store data, but it can quickly turn into a 1000 line beast. I prefer to keep the AppDelegate clean :-)