How to update NSFetchedResultsController from a different view/mangedObjectContext?' - swift

I have two view controllers, one is a tableView with a NSFetchedResultsController and the other is a regular viewController. My tableView is properly setup with NSFetchedResultsController delegates and is working perfect. I now have realized that just because I declare the same managed object context in each separate view that the tableView/NSFetchedresultsController will not monitor changes that I have made in the regular view controller.
This is how I declare it in each view
lazy var coreDataStack = CoreDataStack(modelName: "myApp")
I understand now that I need to merge my saved changes from my regular viewControllers context to my tableView/NSFetchedresultsController context so that the delegate methods can run but I am unsure how to do so.
When I change create or update an object in the regular VC I add notification observers like this
NotificationCenter.default.addObserver(self, selector: #selector(contextDidSave(_:)), name: Notification.Name.NSManagedObjectContextDidSave, object: nil)
#objc func contextDidSave(_ notification: Notification) {
let frc = ConversationsViewController().fetchedResultsController
frc.managedObjectContext.mergeChanges(fromContextDidSave: notification)
frc.managedObjectContext.processPendingChanges()
print("Object did save: \(notification)")
}
After I create or update an object the regular VC, notification prints successfully but no delegate methods are called in tableView/NSFetchedresultsController. I know that the objects are saving because when I restart the app they are shown in the tableView. I would greatly appreciate if anyone can point me in the right direction as to how I can notify my fetchedResultsController an object has saved. I have seen a similar question NSFetchedResultsController doesn't call controllerDidChangeContent: after update to non-fetched NSManagedObject but it is in Objective C

Related

How to update a value after it is loaded inViewDidLoad in another file (Swift)?

I have created all my views programatically and to the UIView I have created an extension. This extension is present in a different file called App+Extensions.swift
extension UIView {
func setupParentView() -> UIView {
//...
}
}
The setupParentView() gives me a view with my navbar and background colour. As the design is same everywhere I have used this function. This function is called everywhere in viewDidLoad. So, now in this function the navabar consists of points which need to updated every time the user has purchased/spent it.
So, now as this method is only called in viewDidLoad the function is not called again and the point value do not change in the navigation bar. So, how can I update the point value every time it is changed? I thought of using Combine, but this app will be available for iOS 12 users as well, and I am not using RxSwift, so any help?
Well ViewDidLoad won't get refreshed because your view is now in the stack hierarchy. Some override methods that do get called which have a relation to the view are viewWillAppear, viewDidDissapear, and viewDidAppear.
If your view is going back in forth and the point button needs to be updated everytime your view re-appears consider putting the "refresh" point number in one of the above methods.
Note if your sending info back and forth between two views its also best maybe implement a delegation pattern or an observer.
Use notifications.
In other words, have points be a property of a global object that lives off in "data space" as a singleton. Now just use a property observer so that whenever anyone changes that object's points property, it emits a notification through the NotificationCenter.
That way, any view or view controller that needs to update whenever the points property changes just has to register for that notification when it comes into existence.
[Basically, this is the mechanism, or one of the mechanisms, that you would be replacing or even using directly if you were using Combine or RxSwift.]
Create a custom view called NavBarView
This NavBarView has a property called point
var point = 0 { didSet { updateView() } }
You want to avoid singleton, single view object, so that not everything is coupled together.
You don't need RxSwift or notification to do this.

Where is the right place to remove notification observers in Swift 2?

I have this code, taken from this answer: https://stackoverflow.com/a/29099066/406322
extension NSNotificationCenter {
func setObserver(observer: AnyObject, selector: Selector, name: String?, object: AnyObject?) {
NSNotificationCenter.defaultCenter().removeObserver(observer, name: name, object: object)
NSNotificationCenter.defaultCenter().addObserver(observer, selector: selector, name: name, object: object)
}
}
Now, in my view controller, I am setting my observers in viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
setObservers()
}
func setObservers() {
NSNotificationCenter.defaultCenter().setObserver(self, selector: #selector(BaseController.handleComment(_:)), name: "newComment", object: nil)
}
However, even with using this extension, where the observer is removed before getting added, each time I exit the view controller, and return to it, I get multiple notifications (one extra each time).
How is this possible?
If you need this setObserver extension, you are very likely doing something wrong. You should be able to balance your registration and removal easily. If you can't, your notification management is very likely too complicated or in the wrong place.
Typically the correct place to add observations is in viewWillAppear (or viewDidAppear, either is fine), and remove them in viewDidDisappear (or viewWillDisappear). This ensures that you do not receive notifications while you are offscreen, even if the view controller still exists (which is common).
If your view controller requires that it receive notifications while it is offscreen, then you have a design problem. View controllers should only manage onscreen views. If they're doing anything else, you have put too much of the model into the controller.
As #rmaddy notes, your specific problem is likely that you have two instances of this view controller. That's may be fine or it might be a mistake (it depends on how the view controller works). But if you balance adding and removing your registration when going on and offscreen, that part will be fine.

What is the better approach to communicate between parent and child view controllers?

It may be a simple way, but I need some guidance from those who have been familiar with iOS.
If a parent view controller want to send one particular message to all child view controllers, what is the best way?, Still now I have wrote a method in each child view controller and informed whenever necessary, In a situation I want to notify to all childs? What should I do for this? I don't think I need to write the same method in all ViewControllers..
or
Do I need to go for subclassing.... thanks....
There are a lot of options for communicating between parent and child view controllers:
Parent -> child
Direct interaction: there is an instance in the parent
Child -> parent
Delegation [preferred]
Notification
Pass value via database, Plist file etc.
Store in an app delegate instance and pass the value (UIApplicationDelegate)
NSUserDefaults
The excellent detailed explanation
If you simply want to invoke a method on your child view controllers, you could use:
[[self childViewControllers] makeObjectsPerformSelector:#selector(nameOfSelector:) withObject:objectToSend];
or one of the other methods for passing objects along with your selector.
As #Gianluca Tranchedone suggested, you could use delegates or an observer pattern but it really depends on what you need to do and how your code is structured. Making your parent view controller conform to delegates of your child view controllers would allow you to structure your code in a much cleaner way.
Use the delegation pattern. You specify some methods that you want your delegate to implement as a protocol and have your parent view controller implement the protocol. Your children view controllers can have a property called 'delegate' of type 'id' where MyDelegateProtocol is the protocol you specify. Then, when you want your children view controllers to talk to the parent you can call the methods specified in the protocol. For example, if your protocol specify a method called 'myMethod', in you child view controller you can call [self.delegate myMethod].
Using this approach you can have your children asking information to the parent. If you want to notify children of something that has happened, instead, you better use notifications (NSNotification) or have your children view controllers to observe some property of their parent.
Checkout this guide from Apple called Working With Protocols for more information on how to use them.
You can override prepare segue from a parent view controller and talk to your children like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ToServiceController" {
//ToServiceController is your first child
secondVC = segue.destination as? ServiceDirectVC
}else if segue.identifier == "ToMapController" {
//ToMapController is your second child
let destVC = segue.destination as! MapVC
destVC.delegate = self //This is how you can access your child's local variables
}
}
The identifier is defined in your storyboard. Click on the arrows (segues) and look for an identifier tag.

When to observe model data when using Storyboards

Let's assume I have an address book App.
So from the address list I now push to a detail view like so:
User taps on cell in the master list
Segue showDetail is invoked
In prepareForSegue: I set the model object contact of my ContactDetailViewController
viewWillAppear I add an observer for self.contact
So now when the contact object changes in the background, the detail view will automatically be updated.
In viewWillDisappear I remove this observer (I guess it's clean because it is symetrical).
However, viewWillAppear: is called after I set the contact. So should I setup the KVO differently or simply call my updateView function in viewWillAppear which is a bit stupid because I want to get rid of those calls with KVO in the first place.
When you call addObserver:... you want to include the option NSKeyValueObservingOptionInitial. This will cause KVO to send the equivalent of a didChangeValueForKey: notification (i.e. an "Initial" notification) in the same call that adds the observation. This should cause your view to update at that time.

How to share a ManagedObjectContext when using UITabBarController with inner UINavigationControllers

I have an architectural question. My App uses a TabBarController right in the application window. The ApplicationDelegate creates the managedObjectContext, although it actually doesn't need it.
Each ViewController in the TabBarController is a NavigationViewController. The first view controller for each NavigationController are my custom views. All is createde an linked via Interface Builder.
Now, how do I pass the managedObjectContext around the right way? Actually I need my views to load the data as soon as possible so that when the user chooses a tab or navigates through the NavigationControllers, the data is already there.
So my questions are:
How to I pass the context properly?
When should I fetch my data, i.e. in which method? "viewDidLoad" or "viewDidAppear"?
Thanks for all ideas!
You should generally stay away from getting shared objects from the app delegate. It makes it behave too much like a global variable, and that has a whole mess of problems associated with it. And singletons are just fancy global variables, so they should be avoided unless really necessary, too.
I would add a managedObjectContext property to each of your view controllers and assign that when you're creating them. That way, your view controllers don't have a tight linkage with the app delegate.
As for when to fetch the data, you should do it lazily. Core Data is really fast, so I would wait until viewWillAppear: to do your fetching. If you wait until viewDidAppear:, the view is already on the screen and there will be a flicker when the data loads. Do be aware, though, that viewWillAppear: is called every time your view will become visible (e.g. when the user taps the back button on the navigation bar, or a modal view controller is dismissed) so you might want to track whether you've already loaded the data and skip the loading on subsequent calls.
I've ran into this same problem, i'll share my solution.
First you need a reference to the Nav Controller in the Tab Bar in the nib file, make sure you connect it up.
IBOutlet UINavigationController *navigationController;
Then, get the Controller as recommended in the support docs and send it the managedObjectContext:
SavedTableViewController *saved = (SavedTableViewController *)[navigationController topViewController];
saved.managedObjectContext = self.managedObjectContext;
Alex is right, "You should generally stay away from getting shared objects from the app delegate. It makes it behave too much like a global variable, and that has a whole mess of problems associated with it."
You can get it from the app delegate at any time like this:
myApp *d = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = d.managedObjectContext;
Or variations of the above.
Other than that you can add a property to all your viewcontrollers and pass it around or you can create a singleton and reference that globally.
Swift
You should not share a NSManagedObjectContext, but you can share the NSPersistentStoreCoordinator.
Thus, you can create a new managed object context for each view, each sharing the same store. It is the preferred method, and allows concurrent, multithreaded access. In the example below, I am assuming that your AppDelegate, *if created with a recent version of Xcode with Use Core Data checked*, has a property named persistentStoreCoordinator:
lazy var managedObjectContext:NSManagedObjectContext? = {
// This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate {
let coordinator = appDelegate.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}
}()