How to remove object from NSPageController arrangedObjects? - objective-c

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

Related

Objective C Communicating to one view controller that another has changed

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.

How do I prevent NSMutableArray from losing data?

The first view of my app is a UITableView.
The user will choose an option and the next view will be another UITableView. Now the user can tap on an "add" button to be taken to another UIViewController to enter text in a UITextField. That text will then appear in the previous UITableViewCell when saved.
The issue I am having: if I back out to the main view and then go back to where I previously was, that inputed text is now gone.
How can I make sure that text is not being released or disappears like this?
You might want to store this array somewhere else in your project, like in an MVC (data model). You could create a new class for it that passes the information through the classes and stores the array in one place. Then once you add to the array, you could reference that class and call a method in that class to store the text in the array and whenever you load the table view it loads with that array in the class.
In my case, I would do this, but I would make everything class methods (where you cannot access properties or ivars) and just store the array in the user defaults / web service or wherever you need and retrieve and add/return it like this:
+ (NSMutableArray *)arrayOfSavedData {
return [[NSUserDefaults standardUserDefaults] objectForKey: #"savedData"];
}
+ (void)addStringToArray: (NSString *)stringFromTextField {
[[[[NSUserDefaults standardUserDefaults] objectForKey: #"savedData"] mutableCopy] addObject: stringFromTextField];
}
The mutableCopy part is important because arrays don't stay mutable after you store them into the user defaults
The reason the text is gone, is probably because you're instantiating new controllers when you go back to where you were. You can keep a strong reference to your controllers, and only instantiate one if it doesn't exist yet. Exactly how to do this depends on how you're moving between controllers -- whether you're doing it in code, or using a storyboard for instance.
This kind of issue is very frequent. When you move around multiple controllers and views.
Each time you load a new view and controllers are alloc+init, new set of values are assigned and previous values are not used!!!.
You can use SharedInstance/SingletonClass so that it is allocated & assigned once and does not get re created with new set of values.

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.

Change UIView Dynamically

Working on cs193p (stanford online videos of iphone programming), I am working on Presence1 exercise
I am trying to change a UIView which is a subview of another view dynamically.
For those of you who are not aware of the assignment posted on the course, Basically the architecture of the app is as follows:
Nav Controller -> (Root) View Controller - (VC)1 for 1st screen -> calls Detail View controller VC2 for next screen
Now 1st screen has images and text that I want to load dynamically from my model (which is an object I instantiate in App delegate before pushing the 1st VC on navigation stack. I have defined a parameter in initWithNibName method to pass this model object while initializing the nib for VC1. And I also try to set the image from the model over here and in viewDidLoad and viewDidAppear methods. Its not working.
When I debugged, I saw that the model object being passed is empty.
If you understand the problem, please lemme know what am I missing.
I can post the code if required but will have to post the whole thing to make any sense.
Nailed - I was releasing my model object when not required - I was creating a "reference" to one of my objects present in the model (array containing a list of objects) . Note - I was not creating a new object by using alloc/copy. I was then releasing it (even though after the push on to the navigation stack) - Obviously causing my object memory space to get cleared and that reflected in my view controller that was receiving that model object as a parameter in its init method. This was giving the illusion that the object is not getting passed to the view controller!
Lesson Learned earlier - Be very mindful about releasing objects.
Lesson Learned Today - Be very cautious when releasing objects. Don't over release objects - make sure you are releasing an object only if you are calling alloc/copy or retaining it explicitly - memory management 101 - revisited :)
Thanks all for not replying to this question. In a way it forced me to scrutinize my code at a very micro level and I am sure I wont forget this for rest of my life! :)