Objective C Communicating to one view controller that another has changed - objective-c

I have two view controllers - one "main" view controller which displays the main content and one "settings" table view controller which is held in a container within a slide-out view. The concept is this:
User taps an item on settings panel.
Settings TVC creates a "Button States" dictionary object with all of the button settings.
Button States dictionary is passed to a class that converts the button states to a dictionary of settings that the main model object can understand.
_______???
At this point, I need to inform the main view controller (which holds the instance of my main model object) that the settings have been updated and it needs to update the settings on its model object. How do I go about doing this? Should I have a class method on the main view controller and include the header file in my settings conversion class?

Contrary to the other answers, there's really no need to use a singleton here. All you need is an object that's shared between the main view controller and the settings view controller, or some way to pass information between them. Here are some options that don't require a new singleton class:
shared model: If you're using a model class to keep track of your settings, both your main view controller and settings view controller can access the model if you simply tell them both about the model. For example, if you create both view controllers when your app starts up, you can create the model at the same time and pass the model to both controllers:
MyModel *model = [[MyModel alloc] initWithFilePath:somePath];
MainViewController *mainVC = [[MainViewController alloc] initWithNibName:nil bundle:nil];
SettingsViewController *settingsVC = [[SettingsViewController alloc] initWithNibName:nil bundle:nil];
mainVC.model = model;
settingsVC.model = model;
shared object: If you don't need/want a whole model class that sticks around and you only need to communicate some changes between the two controllers, let them share just a simple data container, like a dictionary. Let's say, for example, that the main view controller is responsible for creating the settings view controller and then pushing it onto the navigation stack. It can easily say: "Here's a mutable dictionary; use this to get any settings you need, and to record any changes." The code in the main view controller would look something like this:
self.settings = [NSMutableDictionary dictionary];
// ...code to add all the settings to the dictionary...
SettingsViewController *settingsVC = [[SettingsViewController alloc] initWithNibName:nil bundle:nil];
settingsVC.settings = self.settings;
[self.navigationController pushViewController:settingsVC];
delegation: Consider the previous situations, but instead of passing a mutable dictionary or a model object to the settings view controller, pass the main view controller itself as the shared object. If the settings are properties of the main view controller, the settings view controller can access those properties (using accessors, please) to get and set the settings. Or turn it around and have the main view controller keep maintain a reference to the settings view controller so that it can ask for the values for any settings it needs. Either way, one view controller is acting as a helper for the other, and that's the delegate pattern in a nutshell.
NSUserDefaults: The shared user defaults object is already a singleton, so you don't really need a different one. Both view controllers can simply access the shared user defaults object to get/set the settings they need.
Whichever one you choose, you'll want to make sure that you read the settings in the -viewWillAppear for each controller and update the UI as necessary.
Your question is mostly about how to get two view controllers to talk to each other. There are many ways to do that, and a singleton is never required for any of them.

I have an app that does something similar. In my settings view, whenever a setting is modified, I have the settings view call a singleton object that keeps track of the settings in the settings view. Upon switching back to the main view, in the viewWillAppear method I check the singleton to see what settings were set and update my UI accordingly.

You can create singleton class for settings.
here is link how to do it Care and Feeding of Singletons
Let's say that yours singleton is called SharedSettings.
Each time you are changing settings in view you should change them in your SharedSettings singleton and tell view to apply this changes. To "tell the view" there are 2 most common methods in objective-c: delegate and NSNotificationCenter.
Here is tutorial for delegate Example for delegate
And here is the link for notifications NSNotificationCenter Tutorial
The difference between delegate and notifications is that delegate is used to notify one class e.g. like cellForRowAtIndexPath and notification is used to notify many observers with one notification e.g. let's assume that you have 5 view controllers and in the setting you change background from red to green and you need to notify all 5 views to change their colors.
also notifications are considered to be a bit slowlier than delegates.
If you will have questions about singleton, delegates or notifications feel free to ask.

You don't need to restrict the communication with your model to just the "main" view controller, your settings table view controller can directly access the model too! Funnelling all the model access through the one place doesn't usually get you much benefit, and instead demands you invent pointless parallel systems to communicate the model values to & from that main controller.
Make the model object(s) a singleton instead and allow access from any controller.
However if you really need to keep your architecture, you need a way of signalling back that the main view controller that the settings view is closing. Perhaps post a notification that the main view controller observes, the updated settings dictionary can be the notification object.

Related

Get WatchKit interface controller instances created with presentControllerWithNames:contexts:

I'm presenting a page based modal using [self presentControllerWithNames:self.controllerNames contexts:self.controllerContexts];, where controllerNames is just an NSArray of NSStrings containing my interface controllers names. The problem is that I would like to access the created controllers.
The documentations says that
WatchKit loads and initializes the new interface controllers and animates them into position on top of the current interface controller.
but I would like to have a reference to them, in order to call the becomeCurrentPage method from outside.
So, I would like to be able to save those controllers after they are created in a list, and programmatically change the page using something like [self.controllers[2] becomeCurrentPage].
Because you're allowed to provide a context when you present an interface controller, you can pass a reference to self. That way, you can establish a reference from the presented controller to its parent. Once that relationship exists, you can use things like delegation patterns to communicate.
I use this extensively in my own Watch app, and I've wrapped a lot of these mechanics in my JBInterfaceController subclass on GitHub.

How to remove object from NSPageController arrangedObjects?

How to add/remove objects in the page controller dynamically? All examples out there set the arrangedObjects property during the awakeFromNib method once during the application lifetime.
In my application I have NSMutableArray of NSObjects that I display in the main window using NSPageController. For each NSObject I create corresponding ViewController in the delegate's - (NSViewController *) pageController:(NSPageController *) pageController
viewControllerForIdentifier:(NSString *) identifier method.
During program execution objects will get added/removed asynchronously to/from the mutable array.
At every event I set the page controller's arrangedObjects to the mutable array to account for the change - display new objects and stop showing removed ones.
When objects are added it all works fine. But when removing objects the associated views/view controllers remain in memory.
How can I "reset" the page controller so that it forgets everything about the removed objects?
Looks like the page controller keeps references (snapshots) of the view controllers of removed objects. And when adding another objects their views are messed up.
Try using a NSPageController in History Mode as described here
How do you implement NSPageController to navigate through a webView's history
and here
https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSPageController_Class/
In this case you do NOT need to implement
pageController:identifierForObject:
and
pageController:viewControllerForIdentifier
and no need to set
arrangedObjects
But you NEED to call
navigateForwardToObject
each time you want to open a new page, this method makes NSPageController clear forward history and open a new page.
Also make something like adding a new view to your pageController.view with a new page in this method.
Also I made view replacement in
didTransitionToObject

iOS: Create copy of UINavigationController

It is possible, to create an exact object copy of a UINavigationController? I have seen examples of copying objects using copyWithZone:, but I am confused as to how I would use this to copy my UINavigationController.
Any help?
UINavigationController doesn't conform to the NSCopying protocol, so you can't use copyWithZone: or copy on it.
If you are looking to have a customised UINavigationController that you can use throughout the app then you should subclass it and then create a new instance of that subclass every time you need a new one, such as when you create a new modal view controller.
EDIT: If you want to keep the view controllers from a previous navigation controller then you can do something like this (use subclassed navigation controller if needed):
UINavigationController *newNavigationController = [[UINavigationController alloc] init];
[newNavigationController setViewControllers:oldNavigationController.viewControllers animated:NO];
This will do a shallow copy of the viewControllers, i.e. you will have references to the original navigation controller's view controllers, not copies. If you want to do a deep copy on the view controllers then that will be far more complicated and will require specific copying code for each view controller. (See here for more info).
You can do this by creating a category (or a subclass), make the category NSCoding compliant, and add the necessary encoding and decoding functions. You then need to determine what properties you want to encode - the types of view controllers it currently has in its array, and perhaps you'll need to make those objects be NSCoding compliant. You can see that this is not going to be a trivial thing to do, but its not impossible. You may find the solution to your problem is best done using some other techniques.
EDIT: If you want to "duplicate" it, what you really need to know is what viewControllers are in the array. So suppose you want to replicate "state", which in some sense is the same as the original answer but less rigorous. Add a category or method to each object and ask to to give you current state as a dictionary. For the navigationController, that might be just the classes of the objects currently on the stack.
For each of these objects on the stack, you get them to give you a dictionary of their state. By state, its means what text is in UITextFields, views etc, anything that that object would need to go from a startup condition and get back to where it is now.
You package this all up - the nav dictionary and array of the state ones. You can save this as a plist. When you want to construct where you were later, the nav controller can tell what objects to create by knowing their class, then as each one is created it can be sent its dictionary and told "get back to where you were". Once done, then push another controller on the stack.

bind the managedObjectContext outlet for a controller using Interface Builder?

I'm trying to set up a fairly simple view that presents a table to the user. This table is connected to an Array Controller, which I want to use to retrieve records from Core Data. For some reason, I can't seem to connect the 'managedObjectContext' outlet to anything else in my app. When I created my project, there was a property generated in my app delegate which returns the MOC I need, but I can't hook it up in Interface Builder, even after prepending "IBOutlet" to the declaration. Image below showing the available connection on both ends:
http://yada.im/uploads/image/screenshot/1108/7efebc90ca7187a537da9ae003dd5f3e.png
I'm sure that I'm missing some simple step here but I can't tell what piece of glue code I'm supposed to write that will allow me to hook this up more easily. For reference, I've tried dragging a line from the controller's moc outlet to every single source I could think of, and changed the "File's Owner" class to that of my application. Stumped here!
Typically in the template provided by XCode the managedObjectContext comes along with AppDelegate.
You have to bind the managedObjectContext reference of the array controller to the managedObjectContext in AppDelegate.
For this you have to make an object of AppDelegate inside the xib i.e., if its not already present.(Drag an object placeholder from your object library and make its class as AppDelegate)
This makes AppDelegate visible for binding inside that xib.
Next step is actually binding the managedObjectContext. Select your array controller and go to bindings inspector. In the parameters section select App Delegate from the drop down and check on "Bind to".
Fill the "Model Key Path" field with self.managedObjectContext. Now you will find the connection in the connections inspector also.
UPDATE:
The process of creating a new AppDelegate object is to be done only if it is not already present in the main nib file (but the stub generated always has the AppDelegate object in the main nib file).
For a non main nib file, if we follow the above approach, a new AppDelegate object will be created which won't be the NSApplication's delegate. Even though this can be solved by connecting delegate outlet of the application object proxy provided in each nib, the AppDelegate object still won't be the same.
The result is two different managedObjectContext talking to the same store. Although this might appear to work properly when the changes are saved at each step, this is not what we want.
To get the right AppDelegate object, i.e. the one used in the main nib file:
-instead of creating a new AppDelegate object, bind the managedObjectContext of the array controller directly through the application to its delegate. In other words the object to bind to will be the application object and the key path used will be self.delegate.managedObjectContext.
The way to add objects of your entity depends on the specific logic you want to implement.
The generic and easy solution would be, binding the fields for input to the array controller like you might have done for the table and then hooking up the array controller methods to the buttons inside the sheet.
Another option is sub-classing NSArrayController and over-riding the super class methods like add: to write your code (for opening your slide sheet maybe) before calling the super class method, [super add:sender] . Don't forget to specify this sub-class of NSArrayController as the class of your array controller in the xib.

Cocoa Touch UIViewController Properties and class design

I'm creating a custom ViewController. This VC needs to load some data that is known in the event that creates it and pushes it to the top of the NavigationController that it is going to be part of.
My question is, how should I pass data from the view that handles the custom ViewController's creation into that custom ViewController.
I've thought of four possible options, and I was hoping to get feedback on why each one is good or not for this functionality.
1) Expose public properties in the custom ViewController and set the UI elements in the view based on those properties in - (void) ViewDidLoad.
2) Expose the actual UI elements themselves and set their .text/.image/.whatever attributes as the ViewController is being created.
3) Create a custom constructor for the custom view and pass in the values I need to set up the UI elements
4) Create a custom model that both views have access to, set the data before the CustomView is created/pushed, and access that data in the ViewDidLoad event.
I'm still new to all of this, and I want to make sure that I understand the proper handling of these handoffs of data. It seems like something like this is probably a simple answer, but I'm still a little confused and its probably really important to do this right to avoid memory loss/leaks.
Also, in case anyone cares, I'm using Stanford's CS193p class on iTunes U and Mark/Lamarche's "Beginning iPhone Development" to teach myself cocoa for the iPhone. I'm working on an application with a NavigationController and a couple ViewControllers (Presence 1 if you're familiar with 193p).
Well, I believe there are advantages & disadvantages to each of those methods depending on your requirements...often it will require some combination of approaches. I believe the most common, for me anyway, is to do something like this where you give it enough to get started.
MyViewController *vc = [[MyViewController alloc] init]; // (or initWithNibName:bundle:)
// transfer vc values here
vc.value1 = aValue;
vc.value2 = anotherValue;
[self.navigationController pushViewController:vc animated:YES];
[vc release];
After your view controller is instantiated you have an opportunity to pass objects to it. Say MyViewController is a detail view then you'd give it the object it will be displaying the details for. Or, if it's a table view you can give it the NSArray it will need for display. Then in viewDidLoad or awakeFromNib or awakeFromCoder, or... you can fill out the view...so to speak.
#1 is fine, with or without #3 (these two are not mutually exclusive)
#4 is my preferred solution. For instance, if I had a UserViewController, I would probably also like to have a User object and create it this way:
User *user = [self.users objectAtIndex:someIndex];
UserViewController *uvc = [[[UserViewController alloc] initWithUser:user] autorelease];
#2 is not a good idea. Objects should not access the UI elements of other objects. Much trouble comes from this when you decide to change your UI around (and you will).