UITableViewController with a separate data source object - ios

I have a separate object for the data source of a UITableViewController. I want to modify the data source dynamically but I don't want to have a direct reference of the tableView object inside the data source. How can I notify the controller about the changes in the data source?
Currently I use the notification center but I don't believe this is the right choice.

It would be appropriate to give the data source a reference to the table view to be refreshed or to add a property which is a block that the data source calls when some data has been updated (this block is supplied by the view controller and just reloads the table view).
The block approach is a little more generic and allows your data source to more easily be used with table / collection views.
In both cases, the relationship you're looking at is direct and 1 to 1. This is not a suitable place to use notifications. Notifications are for non-direct, broadcast type 1 to many communication requirements.

It is ok to send an NSNotification telling the UITableView that the model has changed, read the data again and reload the table.

I got struct at the thing, One alternative I fount was KVO but I did not get much success implementing KVO, So I am also thinking to use Block. And you preferred which approach ?

Related

What is a good approach to sharing a data model between a table/collection view controller and its associated diffable data source subclass?

I have a UITableViewController with a model property declared in it. When migrating from UITableViewDataSource protocol conformance to a subclass of UITableViewDiffableDataSource outside of the table view controller, the model is no longer accessible in the data source.
What would be a recommended approach to sharing a data model between the two, so say a cell deletion delegate callback in the diffable data source can reflect the change in the model property and CloudKit database.
I believe it's really up to architecture of your choice.
But it general assuming you have ViewModel(in MVVM) or Presenter(in MVP/VIPER) you should keep your model there, and then bind it to DataSource.
When deletion delegate will be hit, you need to call a delete func on your VM/Presenter, which will update this model, save it to CloudKit, and then trigger DataSource update
If you want to have a more code-oriented answer, please share an example of your code, and i'll be glad to help you out with it.

Updating Struct instance

I'm Use structures by default when possible,
what's the best practice to update the original struct instance a copy of it was passed to another variable.
let's say I have a Post struct
struct Post {
let title: String
let likes: Int
let viewsCount:Int
var comments:[Comment]
}
and we have a simple Master-Details Screens
when pushing the details screen we are copying the post to the details scene, and there the data may be changed as likes increments, added comments and so on,
so when pop This ViewController, to the new data model to update the original model in the Master ViewController.
what are the possible solution and the best practices for it?
assuming we are using MVC or MVP
You could create a common shared model class that is responsible for holding the Post data and that both view controllers could use and observe.
This really has nothing to do with structs; it is simply the usual issue of passing data into a view controller on creation and passing data back from that view controller on destruction. It happens that in your case you are positing that this is the "same" data — i.e. you pass a Post to the detail view controller and you pass a Post back from the detail view controller — but that is just a contingent fact.
So, to answer the question as formulated, you would use any of the usual techniques. The detail view controller would need to pass the modified Post back to the master view controller. It could use a Notification or (more directly) you could use the standard protocol-and-delegate architecture.
On the other hand, if Post is the basis of your app's data, it would not be unreasonable to argue that the premise itself is flawed: this should have been a class all along, not a struct, exactly so that the data can be maintained in a central place and references to it can be maintained in different places.
Indeed, if things are more complex, you might have the app's data (including the Post) live in some third location off in model-data-space, and have all view controllers send a notification up to the data when they change it and have the data send a notification down to all view controllers in response (that is what Ralf Ebert's answer quite reasonably suggests). That sort of thing is a lot easier nowadays because (in iOS 13) we have observable objects and the Combine framework.
I would create helper functions and make them mutating. e.g.
mutating func incrementLikes() {
likes += 1
}
Make sure your properties are var.
Ideally classes are best for the role you described as object maintain identity.
Each post has an identity.
According to apple docs "use classes when you need to control the identity of the data you're modeling"
When you share a class instance across your app, changes you make to that instance are visible to every part of your code that holds a reference to that instance. Use classes when you need your instances to have this kind of identity.
Classes vs strut

what's the best way to get notified when entity updates (when saved) in Core Data?

There's an article object that I am trying to keep monitoring in one ViewController. From my research there are couple of ways to achieve this but I am not sure which one is the most suitable one:
1, let the ViewController (or repository or viewModel if we are talking about clean architecture) be an observer to listen to the notification when core data saves.
2, in my Core Data abstract layer, add a completion block callback for when save() is called.
3, Use NSFetchedResultsController. I know this is designed for UITableView and UICollectionView, but I have seen people use this just to do the monitoring.
Among these 3 paths I am incline towards the third one but I am not 100% sure if that's the best practice since most people use it 1 to 1 on UITableView or UICollectionView.
If you want to observe when the object is changed, 1 and 2 will not necessarily help you.
You could do 3 - NSFetchedResultsController is very powerful - but you could also do the same thing that NSFetchedResultsController does internally, and it might be simpler:
You could register as an observer of NSManagedObjectContextObjectsDidChange.
It's posted once per pass through the run loop, if changes have been made, after the side-effects of such changes have been worked out. So it's safe to use if many changes are expected -- you'll only be notified once -- and double-ended relationships will be consistent.
It has a rich userInfo dictionary. Your task could be as simple as: check the userInfo's NSUpdatedObjectsKey and NSRefreshedObjectsKey for your object, and if it's there, refresh the views. Then check NSDeletedObjectsKey and NSInvalidatedObjectsKey too, and if your object is there, dismiss the view controller or return it to an "empty" state.

What's the difference between passing data using prepareForSegue and using Core Data?

Is there anything different between the two? I'm trying to figure out which one is better to use because they seem to do the same thing, or at least to me. I'm trying to pass data between multiple views.
Core Data is an object database that usually has an SQL relational database as it's backing store. It is used for storing and manipulating complex data-sets, and presenting them to the user.
PrepareForSegue is a method that gets called on a view controller before a new view controller is invoked from a segue. You can use prepareForSegue() as a way to pass data to the new view controller that is about to be displayed.
The two things have very little in common.
Since Core Data is a way to store app-wide persistent data, you can have multiple view controllers read and write data to a shared Core Data database, and in that way communicate information between view controllers, but that's about as far as the comparison goes.
To use an analogy, Core Data is a filing cabinet that everybody in an office has access to. If a clerk files some information in the filing cabinet, at any future time another employee can find that file and get the information, and so can anyone else in the company who has access to the cabinet. PrepareForSegue is a text message between 2 specific phone numbers. It transmits a burst of transient information, once and only once, between specific people.
EDIT:
There are other options for passing information between view controllers as well.
Check out this question/answer thread I created on the subject:
How do you share data between view controllers and other objects in Swift?
If you want the data to persist then you must use SQLite3, Core Data or something like realm.
If you just want to pass the data around from one viewController to another then you would do it through something like prepareForSegue
PrepareForSegue would be to pass (tempory) data between two viewControllers.
CoreData is a library to allow you to store data inside your app and re-use it whenever you want wherever you want.
PrepareForSegue allows you to get a reference to the destination view so you can pass the variables you want.
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
let viewController = segue!.destinationViewController as ViewController
//here you can access view variables as viewController.variable
}
CoreData instead allows you to persist data to a SQL Database, maybe not what you want.
But beware, you can only pass data from de origin view to the destination one.

Core Data NSManagedObject Changes

I am using a simple Master Detail scenario where the master uses a fetched results controller to populate its tableview. When a row is selected, the respective NSManagedObject is passed to the detail view controller where it is used to populate a few UILabels. I added an EditorViewController to allow some of the fields to be updated by the user. On save, I dismiss the EditorViewController and go back to the Detail view controller. The save uses the context assigned to the NSManagedObject.
What is the best way to know that the object in question changed so I can update the UI in the Detail VC? Currently, if I try to update the UILabels in the Detail VC, I don't see any of the new values for the object's properties. I know a delegate/protocol scenario would likely work but I am curious if there something already in place that I can use – a notification I can look for to update the object or if I should have a fetched results controller on the details screen? Not sure the proper path to take here.
NSFetchedResultsController is a most common solution to this problem. It listens for changes in the context and updates the table view's content accordingly. Your question is rather general, so I'll leave a link to a very good article showing how to use it properly.
You can use NSFetchedResultsController and register fetch request for that object (I think it is good solution). Alternatively you can listen for notifications like NSManagedObjectContextDidSaveNotification, but I would not recommend it.
Or you can pass an object to your EditorViewController and update properties there directly.
It's hard to say without seeing any code what you are going for, but the other common pattern for working with CoreData makes use of the Model-View-Controller(MVC) paradigm to stage info from your data store in a class variable, then your interface consumes from that class variable. You worry about saving, updating and refreshing the data between CoreData and your class variable separately from your interface consuming that data.
A typical workflow would then be:
//User does something that creates new info
//Store that info permanently in CoreData
//Refresh the class Variable by loading from CoreData (you can also skip this by updating the class variable simultaneously while saving to CoreData, but this is really sloppy and problematic. Better to save then re-fetch)
//Update your interface from the class Variable
Alternately, you could try Key Value Observing? but I'm not sure it's right for your situation.

Resources