What's the best practice to pass value/parameter between NavigationControllers? - iphone

Prior to asking this question here, I have googled aroung. In general, people suggest two ways to pass values/parameters between NavigationControllers:
Via properties in the root ViewController
Via Data Models
I know already that the first choice may not be the best practice. A lot of people seem to adopt this practice. However, I still don't understand how the second practice may be implemented. Does anyone happen to know any tutorial?
Moreover, is it possible to pass the value/parameter downward through constructors? I guess the only problem with that is to get back value/parameter from sub-viewcontrollers.

This file defines the delegate protocol:
#protocol VCDelegate
- (void)notifyParent:(NSString*)someString;
#end
You can include it in the .h of any view controller you define. In that view controller you declare an ivar:
id<VCDelegate> delegate;
In the view controller in which you create the child view controller you include your child view controller's .h as usual. However you add
<VCDelegate>
to indicate that it implements the protocol you have defined, just as you would if you were indicating that it implemented UITableViewDelegate - you're defining a delegate that works just the same way.
When you create your child view controller:
MyChildViewController* myCVC = [[MyChildViewController alloc] initWithString:(NSString*)someString];
myCVC.delegate = self;
So now the child view controller has a delegate which is the parent view controller, the one you are creating the child in and the one that will push it on the nav stack. You have to implement the delegate function in the parent view controller of course:
Incidentally here's where you can pass information down the stack - just set ivars after creation, same as you do the delegate ivar. You'll notice there's an initWithString that is passing a string to a custom init method, that's another way to pass information. You still do all the normal init things, just pass data additionally.
- (void)notifyParent:(NSString*)someString
{
NSLog(#"Child view controller says %#", someString);
}
And then in the child view controller you can do
[self.delegate notifyParent:#"Hello"];
presto - parent VC gets data from child VC.

This seems like the job for NSNotificationCenter. Have a look at this.
sending data to previous view in iphone

Related

identify the name of previous UIView

I was wondering if it possible to find which view called the following function
- (void)viewWillAppear:(BOOL)animated {
//find here the name of the calling view
}
Is there any way to find which view called the new view?
In viewWillAppear directly not. If it's pushed on a UINavigationController, you can get the viewControllers and get the previous one.
if (self.navigationController){
NSArray* viewControllers = self.navigationControllers.viewControllers;
UIViewController* lastViewController = [viewControllers objectAtIndex:([viewControllers count] - 1)];
NSLog(#"%# is my last ViewController before navigationg to this ViewController", lastViewController);
}
Well if are using the navigation controller you can get the array of viewControllers which are pushed by:
NSArray *array = self.navigationController.viewControllers;
but this will give you the view controllers which has been pushed it will fail if are coming back from a view ie popped from navigation stack as in both case your
- (void)viewWillAppear:(BOOL)animated {
//find here the name of the calling view
}
will be called.
You can use presentingViewController for this, but the problem is this will return the memory address of the view controller rather than the name of the pointer.
One solution would be to assign a tag to the view property of the presenting view controller and then ask for that tag in your second controller:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(#"%i",[[[self presentingViewController] view] tag]);
}
In your first view controller:
[[self view] setTag:(someNSInteger)];
Needless to say, "views" don't call this, but rather iOS will call this when your view appears. And unfortunately, this is complicated because you might get viewWillAppear because some other view controller presented this view controller's view, or you might get this when a view controller presented by this view was dismissed or popped (depending upon modal vs push).
We can probably outline all sorts of sophisticated and complicated ways of solving this problem, but we should probably first step back and ask why you need to do this. What are you really trying to achieve? If you're just trying to coordinate interaction between view controllers, there are far better ways of doing that (e.g. delegates, setting view controller properties, etc.).
Update:
If you're trying to figure out whether the data has changed, rather than relying upon some "where did I come from" logic, I'd personally lean towards some mechanism where those data-modifying controllers or processes bear the responsibility for notifying your view controller of this fact.
The simplest way of doing that would be to employ a delegate design pattern, where your child view controller would have a delegate property, which is a pointer to your controller that needs to know about the data change, and the child controller would simply invoke that method when data has changed. In slightly more complicated scenarios, you might combine this delegate pattern with a formal delegate protocol (so that the child view controller doesn't need to know anything about the parent controller other than the fact that it conforms to a particular protocol), but some may say that this is not needed when just communicating between two specific and well-known view controllers. See Using Delegation to Communicate with Other Controllers in the View Controller Programming Guide.
In complicated situations (e.g. data could be changing in a variety of places or even asynchronously, for example during updates via a web service), I'll use the notifications design pattern, in which the view controller will add itself as an observer of a particular notification to be sent by the NSNotificationCenter and whenever the data is updated, the notification center will be told to post that particular notification, which will, in turn, be received by the observer, your view controller.

passing data back to a previously allocated UIViewController

I have a view controller that then has a button that passes to an option menu.
When options are set they need to be past back to the previously allocated viewcontroller.
How is this possible without 'alloc and init another' instance of the object?
You can achieve this by using a delegate protocol. First view controller should become the delegate of the second view controller and then you can call this delegate method in your first view controller once the selection is done.
You can lookup google for implementation of delegates in objective-c. Its pretty simple. Add a
#protocol <delegatename>
<declare delegate method>
#end
Create a member variable in the second view controller for assigning the delegate. And define the method in the class implementing the delegate.
When you init your option viewController, pass it a reference to its parent.
I.E.
[[OptionViewController alloc] initWith...: parent:];
Use a #property or a method or somesuch to call on the parent to pass the data back.
You could use a shared singleton?
http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html
Or save the parameters to nsuserdefaults and read them back in in your first viewcontroller
or some other temporary store such as your appdelegate
I've used all three of the above approaches before.

How do runtime-created sub views communicate with the view controller?

In my iPhone project I have a UIViewController in which I add an instance of a subclass of UIView. From this instance I need to communicate back an integer to my view controller. My current solution is to send a message from my instance to my App Delegate which sends a message to the view controller. It works, but it feels a bit messy.
Is there anyway I can send a message straight back to the view controller?
I would love to be able to do something like [super doSomething:123];
Any ideas?
Thanks
This is the kind of thing that NSNotificationCenter was provided for. Once you get handy with sending and receiving notifications, your message-passing gets a WHOLE lot simpler.
One of the classic things people confront is how to get a pointer to the object they want, in order to tell it about something. How do I, for instance, tell the ViewController two slots back up the UINavigationController stack that the user just changed this data field? So you dig into the stack, offset back by some magic number of elements in the stack, build public setters on the fields you want talk to... It's super cumbersome.
Compared to registering as a notification receiver in one place, and then firing a notification in some complete other place when the data changes. It's kind of magical, after doing all the "dig through the view hierarchy" work.
Um, I'm not sure I understand your problem correctly. You have a class derived from UIView which needs to send a message to another class derived from a UIViewController. It sounds like you are creating the UIView instance programmatically. Is there any reason my you could not have a property on the UIView which refers to the UIVIewController and just use that to send it a message directly.
You cannot use [super ...] because the super of your UIView derived class would be UIView.
Or am I miss-understanding the issue :-)
If I understand correctly, you want to send a message from your subclass of UIView to the view controller.
That means your subclass of UIView needs to have a property or ivar which is the view controller. The easiest way to do this is to add it as an outlet and connect it to the view controller in the nib file.
Generally you should not go via the app delegate. Having a typed pointer link is also less than ideal.
The optimal way of communicating - Apple does it like this as well - is to create a delegate protocol. When creating the view controller you pass a pointer to the delegate as id . Then when it gets to sending the message you ask the delegate:
if ([delegate respondsToSelector(didFinishSomething:)])
{
[delegate didFinishSomething:info_to_pass];
}
If you want to be extra-sophisticated then you can also add a pointer to the calling class instance. Like:
[delegate myViewController:self didFinishSomething:info_to_pass];
This way you always know what kind of class the message is coming from.
If there is more than one place that needs to be notified of a change, then instead of delegation you will use notifications.
In my iPhone project I have a
UIViewController in which I add an
instance of a subclass of UIView.
This implies that you have both a reference to the instance of the UIView subclass and the UIViewController in the same scope. I.e. something equivalent to:
UIViewControllerSubclass *myViewController;
UIViewSubclass *myView;
(It doesn't matter if they are actually instance variables or, even, globals)
And once those two variables are initialized, somewhere you do something like:
myViewController.view = myView;
In your UIViewSubclass, add a property that points back to your UIViewControllerSubclass:
#property(assign) UIViewControllerSubclass *myController;
Then, when you do the above assignment, add:
myView.myController = myViewController;
From there, messaging your controller from your view is easy:
[self.myController yoManHereIsAnInt: 42];
Note that I used assign instead of retain because the controller already retains the view. If the view were to also retain the controller, you would have a cycle that would eventually lead to a leak.
No super about it. super is entirely related to the inheritance hierarchy of your Objective-C classes. What you are asking has nothing to do with inheritance and everything to do with how the various instances of objects in your application are connected together.
Simply add an outlet to your UIView subclass, connect it to its view controller in Interface Builder, and call your method on that. Here’s how that might look:
MyUIView.h:
#interface MyUIView : UIView
{
UIViewController *viewController;
}
#property (assign) IBOutlet UIViewController *viewController;
#end
MyUIView.m:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[[self viewController] setTitle:#"Hello from MyUIView"];
}

How to reference object values from subview?

I have a UIViewController with a XIB and want to add programmatically another subview.
During the initialization of the subview (initWithFrame) i want to set some attributes to values according to attributes that belong to another Object which holds data (actually a ViewControllers Child-Object, but not a view).
-(id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// The following is kind of what i want
self.myAttribute = [self.viewController.otherObject otherValue];
}
return self;
}
I want to conform to the Model-View-Controller paradigm and try to seperate my data from the subview and don't know how to access the data from within the subview.
Thanks in advance for any answers and comments how to improve the question.
EDIT: All three answers are useful for me to understand that my design is somehow the wrong way of doing the thing. As far as i understand the subview properties should be modified by the controller instead of trying the subview making to get the information. I will accept Jasons answer for his effort explaining this to me.
If you put this view in place using something like a UINavigationViewController you can use the parentViewController property. If not--and really, just in general--you can create properties that need to be set on your new view controller, and just set them in the parent or whoever else might create it.
Getting data passed around a view hierarchy can be tricky. You have a few options for global-like data:
Actual global variables (hosted in one .m file and declared in a shared .h file). Not recommended, except in rare cases where you have e.g. static data that the following approaches seem silly to use with.
A shared (singleton) controller object that owns the shared data. Then you could do, say, [[AppController sharedController] otherValue] and access it from anywhere in your application. This is good for what you might call overall properties or settings across your application. You wouldn't use this to pass around view-specific information, generally.
If the data is view-specific, you might have it "ride along" with your view controller hierarchy, by passing it from one view controller into the next as you create and push the controllers. Then when you create the views themselves, as above, don't look for the property in the initWithFrame method, but set up a property on the view that you can set to push in the data immediately after creating the view.
A simple solution for the general problem of initializing a subview with attributes is to write a custom initializer in your subclass.
-(id) initWithFrame:(NSRect) aFrame andAttribute:(SomeClass *) anAttribute{
if (self=[super initWithFrame:aFrame]) {
self.attribute=anAttribute;
}
return self;
}
You would initialize the object like so:
MySubviewClass *msc=[[MySubviewClass alloc] initWithFrame:frame andAttribute:[self.viewController.otherObject otherValue]];
This will work fine if your talking about a subview controlled by the the same controller i.e. it is a subview of the controllers.view. If you loading another view, then you need to go the data-model/navigation-controller route.

Architecting a multiview application on the iPhone

I have an app with a root view controller, a primary view controller, and a secondary view controller. I would like to be able to send a message to the primary view controller from the secondary view controller. How can I get a reference to the primary so that I can send messages to it? Is there a better way to architect this?
The short answer: you can get back to your application delegate like this:
YourAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
You likely already have a pointer to your root view controller in your application delegate class. And you probably have pointers to your primary and secondary view controllers in the root controller object. So, you could write code like this:
SecondaryViewController *primary = delegate.rootController.primaryController;
You can then send messages to it to your heart's content. No outlets required; just properties to each view controller.
There are many longer answers and also a discussion about why this practice might be questionable since it introduces potentially unwanted linkages between objects. In a "pure" object oriented design, you'll follow a clean design pattern with clear linkages between objects in various directions allowing you to better reuse the code.
Another option is to pass in pointers to the objects the class will need at initialization time. Implement a new initWithSomethingOrOther for your view controller classes and pass objects in as parameters. Cache these pointers you need (don't forget to retain them) for later use.
The clean way to do it is to define a protocol for a delegate for the secondary controller which lists the methods it needs the primary controller to provide:
#protocol SecondaryControllerDelegate <NSObject>
- (void)secondaryController:(SecondaryController*)secondaryController
frobFooWithBar:(Bar*)myBar;
- (BOOL)secondaryController:(SecondaryController*)secondaryController
shouldTwiddleBaz:(Baz*)currentBaz;
#end
Now add a delegate property to the SecondaryController:
#interface SecondaryController : UIViewController {
id <SecondaryControllerDelegate> delegate;
...
}
// delegates are one of the few places you don't retain an object
#property (assign) id <SecondaryControllerDelegate> delegate;
...
In SecondaryController's implementation section, synthesize the delegate property. (Do not release it in the destructor.) When SecondaryController needs to communicate with the PrimaryController, it should call the appropriate method on the delegate.
Now make your PrimaryController implement the SecondaryControllerDelegate protocol:
#interface PrimaryController : UIViewController <SecondaryControllerDelegate> {
...
Implement the delegate methods in PrimaryController.
Finally, have your PrimaryController set itself as the SecondaryController's delegate. Exactly how you do this will depend on whether you create SecondaryController in a nib or not. If you do, make the connection there; if not, make it just after you allocate and init the SecondaryController.
Why do you do this song and dance? Well, when you have to introduce another controller between the Primary and Secondary, or use the Secondary elsewhere in the app, or even use the Secondary in another app (I have one controller that gets used in three of my four apps), you don't have to change SecondaryController at all; you just change whatever class should now be its delegate. This is an incredible time saver in the long run.
If the controllers are loaded from a NIB, you could define an outlet on the secondary controller and connect it to the primary controller in interface builder.
Use NSNotificationCenter for decoupled communication between objects.