I am able to pass a variable forward from view controller to view controller by pushing its view controller onto the navigation stack. An example of how I do it would be this:
MyViewController *controller = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
controller.myString = stringToPass;
[self.navigationController pushViewController:controller animated:YES];
[controller release];
However, what do I do if I want to pass a variable BACK UP the navigation stack? Using popViewControllerAnimated rather than pushViewController does not pass the variable up like I thought it would.
I need to be able to access the variable several pops up from the view controller it is defined in.
Any help will be greatly appreciated :)
You're passing values, not variables.
A view controller should not be responsible for popping itself. With Apple's view controllers (e.g. UIImagePicker), it is the parent view controller's responsibility to do the popping; the parent VC can also obtain the current value. (Not entirely correct; it might access the value before a keyboard autocompletion is applied)
Alternatively, if it's a value that can be shared globally, you can store it in your application delegate.
You can get a hold of the navigation controller in the VC stack using self.navigationController. You can just call some method like
[self.navigationController setMyString:stringToPassUp];
There are several more ways, e.g. self.tabBarController for the tabbarcontroller up in the stack, or most simply
[self.parentViewController setMyString:stringToPassUp];
edit given the downvotes on the examples above, and nobody giving a better explanation, let's discuss the proper way to do this.
If you have some object (like your MyViewController *controller) and that object has something to tell you, the usual approach is this:
MyViewController gets a delegate property, of type (id)
the view controller instantiating the MyViewController, sets this delegate property, like so:
controller.delegate = self;
MyViewController will, when it has something to say, do something like:
[self.delegate delegateMessage:arg1]; to "pass the message up" as you put it.
To do it perfectly, you may want to create your own #protocol MyViewControllerDelegate, and declare the class which would be setting controller.delegate = self; to adopt this protocol, by putting <MyViewControllerDelegate> on the #interface line. The delegate property of MyViewController should then be declared id<MyViewControllerDelegate> delegate;, so that the [self.delegate ...] messages can be matched to the protocol specification.
Basically the whole Cocoa Touch API works like this. Just have a look around for ideas how to implement your interaction.
Related
I have an iPhone application that uses a UITabBarController for its primary interface. The application also makes heavy use of modal UINavigationControllers presented from the various tabs.
There is a ViewController that I need to present modally which can be triggered from a wide variety of locations within the application. It seems like a terrible idea to duplicate the code which creates and presents it between all the viewControllers that trigger it. I would like to have this code in a single place and trigger it from whichever viewController wants to present it.
Where should this centralised location be? My root ViewController is a UITabBarController so no good there, and I hate lumping view functionality into the AppDelegate.
I would create a new class file that has a function to present the view you want. That way you only need to write the code once to present the modal view and each view that needs to use it can call the function on the helper class with one line of code.
#interface ApplicationHelper : NSObject {
}
+(void)showMyModalView:(UIViewController *)parentViewController;
#end
Implementation:
#import "ApplicationHelper.h"
#import "ViewController.h"
#implementation ApplicationHelper
+(void)showMyModalView:(UIViewController *)parentViewController
{
ViewController *vc = [[ViewController alloc] init];
[parentViewController presentModalViewController:vc animated:YES];
}
#end
Then in each view controller import the ApplicationHelper and call the showMyModalView method
[ApplicationHelper showMyModalView:me];
This allows you to keep all the view handling code in a separate file to your application delegate.
Don't try and pass the reference around.
Just alloc] init]; a fresh instance where-ever you need to use it.
For example.
MyModalViewController *controller = [[MyModalViewController alloc] init];
[self presentModalViewController:controller animated:YES];
You can create one UIViewController and implement some delegate method for it. Then you can present the view controller like this:
[currentViewController presentViewController:yourViewController animated:YES];
And when an event is fired in your view controller, it will delegate to the caller.
In this case, you write the code only one time for "YourViewController", then reuse it where ever you want. You can also use pushviewcontroller also:
[self.navigationController pushviewcontroller:yourViewController animated:YES];
I call a View to be presented with the following code:
#import "infoView.h"
...
infoView *viewInfo = [[infoView alloc] initWithNibName:nil bundle:nil];
viewInfo.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:viewInfo animated:YES];
But when it is presented in run-time the view that is loaded turns out black.
Currently I am using storyboard, but I need to use this code, for it is a lot more efficient in my case, because I am dealing with multiple views!
It works fine if I connect it via StoryBoard.
I should be seeing 2 labels, 1 UITextView, and 2 UIButton.
The view was created using StoryBoard, when the .m and .h files for the view where created I did not add a .xib for it. And also it is linked through the "Custom Class" section in StoryBoard.
Thanks, hope someone can help!
It's generally pretty bad form to mock people who are taking the time and effort to help you.
Naming is important it makes your code easier to work with and allows other people to use it. Not following the conventions for the language you are working in is dangerous and means that your code is not compatible with other developers as things are interpreted differently.
If you look at the docuemntation for UIViewController you'll see this note in the initWithNibName:bundle: method description
If your app uses a storyboard to define a view controller and its associated views, your app never initializes objects of that class directly. Instead, view controllers are either instantiated by the storyboard—either automatically by iOS when a segue is triggered or programmatically when your app calls the storyboard object’s instantiateViewControllerWithIdentifier: method. When instantiating a view controller from a storyboard, iOS initializes the new view controller by calling its initWithCoder: method instead. iOS automatically sets the nibName property to a nib file stored inside the storyboard.
Therefore you are instantiating your controller wrong, the storyboard should be instantiating it. Which is done like this (naming corrected)
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:[NSBundle bundleForClass:[self class]]];
InfoViewController *infoViewController = [storyboard instantiateViewControllerWithIdentifier:#"InfoViewController"];
infoViewController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self infoViewController animated:YES];
Side note
infoView is a bad name for the class not only because you didn't start with a capital but also because it's completely deceiving. Anyone reading this would assume that InfoView is a subclass of UIView not UIViewController.
I have a UITabBarController, and one tab is a UINavigationController. I have a search bar that goes to a certain view within the UINavigationController. The problem is that if the first view is not pushed by the UINavigationController, than it crashes because my search doesn't recognize the visibleViewController from this call:
UINavigationController *navController = [self.MainTab.viewControllers objectAtIndex:1];
FirstViewController *fVC = [navController visibleViewController];
What I don't understand is, before this code, I do this:
self.MainTab.selectedIndex = 1;
This code on its own selects the viewController in that tab, where then the view gets loaded to my knowledge. So shouldn't this be enough for the [navController visibleViewController] to get the current viewController? Thanks.
Try topViewController instead of visibleViewController.
FirstViewController *fVC = [navController topViewController];
From what you explain in your question and comments, I understand that your code tries to access an object of type FirstViewController, supposedly the first view to be pushed on to your UINavigationController, when it has not yet been created.
On the other hand, if you first programmatically select the tab, the view is created and everything works fine. Indeed, that view is created in a viewDidLoad method that is run when the tab is selected.
The solution I would suggest is avoiding accessing the UINavigationController visibleViewController directly from your search tab; instead, let your search code access the model (as in Model-View-Controller) for your app and store there the result; then, from the mentioned viewDidLoad method again access the model to read the search result and update/show the UI.
This is the clean solution, IMO. If you want a sort of workaround to your current design, then check the fVC value you get back from visibleViewController and if it is not what expected, then instantiate the view properly.
I hope this helps.
I know this has been answered, but I found another solution that might be helpful. In my case I was handling rotation differently for some viewControllers within my NavigationController, I did the following:
Subclass UINavigationController, then where needed in your new subclass you can access the current visibleViewController's title like so:
- (BOOL)shouldAutorotate
{
if ([[self visibleViewController].title isEqualToString:#"Special Case"]) {
return NO;
}
return YES;
}
This is not specific to rotation, this is just what I used it for. The only thing you have to do is set your self.title for each of the viewControllers you are checking against in their viewDidLoad, if they are set in IB or are not set they will be nil.
I have this class (let's call it FooViewController) that's a subclass of UIViewController. It's supposed to act similarly to a UINavigationController, in that there's a rootController and you can add other UIViewControllers to it. And each UIViewController within FooViewController can create another UIViewController and push that new UIViewController to FooViewController.
Here's an example of what I mean. This is the auto-created code when you add a new UITableViewController to your project.
DetailViewController *detailViewController = [[DetailViewController alloc] initWithNibName:#"Nib name" bundle:nil];
// ...
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
Here's the question: How can I have a variable like navigationController in all of my UIViewControllers? Just like you can add methods to existing classes using categories, can you also add variables to existing classes? It sounds like Associative References is what I'm looking for, but it's only available on a Mac.
One solution that would work for me is to subclass all the UIViewControllers that I might use, and have my actual classes subclass off of those. For example, I might do:
#interface FooUIViewController : UIViewController {
FooViewController *fooViewController;
}
#interface FooUITableViewController : UITableViewController {
FooViewController *fooViewController;
}
So that I could do: [self.fooViewController pushViewController:detailViewController];. But this seems like a dirty way of doing it.
I feel this shouldn't be a difficult thing to do and maybe I'm thinking about it wrong. Any thoughts? Thanks!
No, you can't add an instance variable to UIViewController.
Subclassing is not a bad way of doing it, considering more things it can do in relation to FooViewController. It doesn't harm the design of your classes because they depend on FooViewController anyway (just as the same as UIViewController having navigationController property).
Another way of doing it is to access the FooViewController object via the application delegate. But I think this is a dirty way of doing it (because they now depend on your application delegate).
I was kind of scratching my head at this a week ago, and now with a little bit more Cocoa experience under my belt I feel like I have an inkling as to what might be going on.
I'm making an application that is driven by a UINavigationController. In the AppDelegate, I create an instance of this class, using "page 1" as the Root View Controller.
UINavigationController *aNavigationController = [[UINavigationController alloc]
initWithRootViewController:page1ViewController];
Now here's where I'm having the problem. From "page 1" I'd like to use a modal view controller that slides over the interface and then disappears once the user has made an edit. I do that using code like this, inside of Page1ViewController:
[self presentModalViewController:myModalViewController animated:YES];
When the Modal View Controller is gone, I want a value on "Page 1" to change based on what the user entered in the Modal View Controller. So, I wrote some code like this, which resides in the Modal View Controller:
[self.parentViewController dismissModalViewControllerAnimated:YES];
[self.parentViewController doSomethingPleaseWithSomeData:someData];
The update to page 1 wasn't happening, and it took me a long time to realize that the "doSomethingPleaseWithSomeData" message was not being sent to Page1ViewController, but the Navigation Controller.
Is this always to be expected when using Navigation Controllers? Did I perhaps configure something improperly? Is there an easy way to get at the View Controller that I want (in this case, Page1ViewController).
I would recommend using the delegation pattern to solve your problem. Create a property
#property (nonatomic, assign) id <MyModalViewDelegate> delegate;
And a corresponding protocol
#protocol MyModalViewDelegate
#optional
- (void)myModalViewControllerDidFinish:(MyModalViewController *)aModalViewController;
#end
When the user finishes with your view (e.g. taps the save button), send this message:
if ([self.delegate respondsToSelector:#selector(myModalViewControllerDidFinish:)])
[self.delegate myModalViewControllerDidFinish:self];
Now, set the delegate to the view controller that should manage the whole thing, and it will be notified when the view controller is finished. Note that you'll need your view controller to dismiss the modal view controller. But, logically, that makes sense, since it was the object that presented the modal view controller in the first place.
This is how Apple solves this problem in, for example, the UIImagePickerController and UIPersonPickerController.
There are a couple of ways you can handle this. The simplest is probably just to add a UIViewController property into myModalViewController and set it to page1Controller before you present it:
myModalViewController.logicalParent = self; //page1Controller
[self presentModalViewController:myModalViewController animated:YES];
Just make sure you add the appropriate instance variable #property, and #synthesize for logicalParent to myModalViewController, then you will have a way to communicate data back to the ViewController that triggered the modal dialog. This is also for passing data back and forth between different levels of navigation before you push and pop them on the stack.
The one important thing to worry about when doing this is that it is easy to get retain loops if you are not careful. Depending on exactly how you structure this you might need to use assign properties.
I just ran into this same problem. It definitely seems that if you put a UIViewController embedded in a NavigationController, then when, from that UIViewController you present another UIViewController modally, the presentee thinks that the presenter is the NavigationController. In other words, parentViewController is incorrect.
I bet this is a bug: either that, or the documentation seems incomplete. I will inquire.
Just ran into the same problem. I believe this is a bug. My scenario is the following:
A navigation hierarchy with A, B and C view controllers in this order. On C there's a button that would open a modal view controller called D. Once D is presented the navigation controller drops C from its hierarchy which is a terrible behavior. Once D gets dismissed, the navigation controller instantiates a new C type view controller and pushes it into its hierarchy to recover the original one. Terrible. My solution is hacking the navigation hierarchy this way (a very bad solution but works well. with a 2 dimension array you could implement stacking modals):
- (void)presentModalViewController:(UIViewController *)c {
[self.navigationHierarchy removeAllObjects];
[self.navigationHierarchy addObjectsFromArray:[navigation viewControllers]];
[navigation setViewControllers:[NSArray array] animated:YES];
[navigation presentModalViewController:c animated:YES];
}
- (void)dismissModalViewController {
[navigation dismissModalViewControllerAnimated:YES];
[navigation setViewControllers:[NSArray arrayWithArray:self.navigationHierarchy] animated:YES];
}
These two methods are defined where I maintain the main navigation hiererchy: the app delegate. navigation and navigationhierarchy are defined this way:
NSMutableArray *navigationHierarchy;
UINavigationController *navigation;