I have a parent view with a hidden button, and a method that unhides that button. That parent view has a modal view in which I need to call the method that hides the button.
ParentViewController.m
- (void)unhideButton {
myButton.hidden = NO;
NSLog(#"Unhide");
}
ModalViewController.m
- (void)levelComplete {
ParentViewController *controller = [[ParentViewController] alloc] init];
[controller unhideButton];
[controller release];
}
The NSLog message Unhide is successfully showing up in the console, but when I dismiss the modal view controller, the button is still hidden. What am I doing wrong?
Modal view controller's have an automatic reference to the view controllers that present using the parentViewController property. So you can directly say,
[self.parentViewController unhideButton];
in the levelComplete method.
But yeah creating a new instance and calling the method on it will not affect the original instance like Ryan said.
Why is the ModalViewController, which is presumably presented by an instance of ParentViewController, instantiating a new ParentViewController? I think what you need to do is pass a reference to the existing ParentViewController to the ModalViewController when you create it, then in ModalViewController it can set the hidden property on the parents button.
If you want to follow good design practices, the ParentViewController needs to delegate the management of its button to the ModalViewController. ParentViewController would conform to a simple protocol, exposing the button, and would set itself as the delegate of the ModalViewController before presenting it.
Related
I know this has been asked a million times, but I couldn't find a suitable answer in those many questions that I've examined.
I have a custom view controller, and I'm trying to display the view controller when the user taps a button (so no "infamous viewDidLoad problem" here).
Here is my code that runs when the user taps the button: (I have the NIB for the view controller, and I have a navigation controller)
ICLoginViewController *loginViewController = [[ICLoginViewController alloc] initWithNibName:#"ICLoginViewController" bundle:[NSBundle mainBundle]];
//assuming we have a navigation controller.
UINavigationController *navigationController= (UINavigationController*)[[UIApplication sharedApplication] keyWindow].rootViewController;
[navigationController.topViewController presentViewController:loginViewController animated:YES completion:nil];
I'm getting the Warning: Attempt to present <ICLoginViewController: 0xa08a810> on <UINavigationController: 0xa45de70> whose view is not in the window hierarchy! error when I try to present the view controller. Nothing happens on screen. If I tap multiple times I get the same error, and still nothing happens. I've set a breakpoint and verified that navigationController and navigationController.topViewController are not nil. I' using storyboard (if it helps) but not for the custom view controller that I'm trying to display. (I want to make it an app-independent library in the long run, so I'm not referencing any app-specific modules within) Why am I getting this error?
I've found the solution. The problem was, my modally displayed view controller was not the 'top' view controller in navigation controller. If I change the calling view controller to be pushed instead of being modal, then it becomes the top view controller and my app works well. Apparently, this had nothing to do with my custom view controller, but my navigation stack.
If its in an NSObject create a method inside the NSObject that takes your current viewController as an argument and present it there.
eg:
-(void)presentInViewController:(UIViewController *)controller{
ICLoginViewController *loginViewController = [[ICLoginViewController alloc] initWithNibName:#"ICLoginViewController" bundle:[NSBundle mainBundle]];
[controller presentViewController:loginViewController animated:YES completion:^(BOOL comp){}];
}
This way you can call that view controller wherever you want instead of trying to find your way through the navigation stack from UIApplication.
I have a UITableViewController. When you select a cell, it calls init on a UIViewController, which programmatically creates a bunch of views and adds them to the screen. Everything here works fine.
As the user interacts with the app, the views are moved or deleted. I want to have a button where the user can "Start Over" and the UIViewController will init and draw itself like new on the screen. Basically I want the same behavior as if the user went "back" to the UITableViewController and clicked on that same item again.
I can create the button and wire it up and everything. What I need to know is how to release and re-initialize the UIViewController.
How do I do that?
Create your UIViews in UIMyViewController controller.
and use the below code for pushing your view controller in navigation stack.
-(void) buttonClcked:(id) sender
{
//Create for pushing another view controller in navigation stack.
UIMyViewController *myViewController = [[UIMyViewController alloc] init];
//to push view controller in navigation stack.
[self.navigationController pushViewController:myViewController animated:YES];
// you could release it because now it's retained by your UINavigationController
[myViewController release];
myViewController = nil;
}
Well, seems to me, you have two choices:
You can exit the UITableViewController (via a delegate call to the parent) and have it destroy it and relaunch it. (or)
You can put your view building code into a separate routine (not a NIB or loadView or ViewDidLoad or even ViewDidAppear, and then release all the subViews of self.view and call the view builder again.
In my iOS app I'm creating and presenting a UINavigationController modally like so:
MyViewController *myvc = [[[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil] autorelease];
UINavigationController *navVC = [[[UINavigationController alloc] initWithRootViewController:myvc] autorelease];
[self presentModalViewController:navVC animated:YES];
In the MyViewController viewDidLoad I'm creating and setting toolbar items for the navigation controller's toolbar, like so:
self.navigationController.toolbar.items = [NSArray arrayWithObjects:(items...), nil];
the problem I'm having is that the items don't show up. If instead I call a method from MyViewController's viewDidLoad method that adds the toolbar items via performSelector:withObject:afterDelay:0, then it works perfectly. So there's obviously some race condition going on here with the initial presentation of the UINavigationController, initialization of its toolbar/navbar, and the initialization of the nav bar's specified initial root view controller.
I verified in the debugger that the root view controller's viewDidLoad is called after the UINavigationController's viewDidLoad method. In fact, the root view controller's viewDidLoad method is not called until presentModalViewController: is called, and the UINavigationController's viewDidLoad is called within initWithRootViewController, so doesn't that imply that the UINavigationController object should be "all ready to go", including its nav bar and toolbars?
I thought at first that the navigation controller's toolbar object may not exist yet at MyViewController's viewDidLoad time, but it clearly does. At least, NSLog shows that it is not nil during MyViewController's viewDidLoad method. In fact, the UINavigationController's toolbar object is identical at both times: in the root view controller's viewDidLoad, and in the "setupToolbar" method that I called with performSelector:withObject:afterDelay, so it's not getting "re-initialized" somehow.
So, what's going on here? Why aren't my toolbar modifications "sticking" in MyViewController's viewDidLoad, and why does performing them in the next iteration of the runloop (performSelector:withObject:afterDelay:0) make it work?
What is the "right" way of setting up initial navbar / toolbar items in code from the rootViewController of the UINavigationController?
Edit: well I figured out "what" is happening, although I still don't understand the "why": after MyViewController's viewDidLoad method returns (where I have verified that the navigation controller's toolbar's items array is non-nil) and sometime before/in the next runloop iteration (or whenever a performSelector:withObject:afterDelay:0 is called), the items property of the navigation controller's toolbar is set to nil!
Edit: same problem when calling [self.navigationController setToolbarItems:animated:]
Edit: solved, the right way is [self setToolbarItems:animated:], not [self.navigationController setToolbarItems:animated:], thanks!
Just a guess here, but have you tried using
[self setToolbarItems:[NSArray arrayWithObjects:(items...), nil] animated:NO];
from within MyViewController's viewDidLoad method? I'm not sure if it's the correct way to directly access the navigation controller's toolbar like you did. At least the API states
UIViewController Class Reference
...
- (void)setToolbarItems:(NSArray *)toolbarItems animated:(BOOL)animated
...
View controllers that are managed by a navigation controller can use this method to specify toolbar items for the navigation controller’s built-in toolbar. You can set the toolbar items for your view controller before your view controller is displayed or after it is already visible.
I have a view in my app that displays a UITableView. This view is created in a nib file and has a custom view controller. The UIViewController subclass for this view acts as the Datasource and Delegate for the UITableView.
My UITableView displays several rows based on my data. Then, the last row displays different text: "Add another...". If the last row is selected, I present a modal view controller (to allow the user to add more data). When I dismiss the modal view controller, I again see the original view (as expected) and all appears to be well. However, when I try to interact with this view, the app crashes.
From placing several NSLog() statements through the UIViewController (for the UITableView), I have determined that the -dealloc method is being called just after the modal view is dismissed. This explains the crash when I try to do something with that view. However, I have no idea why -dealloc is being called on this view controller.
To dismiss the modal view controller, I have:
[self dismissModalViewController:YES];
As the code in an IBAction method in the modal view controller's UIViewController. This action is tied to a cancel button in the corresponding nib file.
In addition, my understanding from the View Controller Programming Guide is that it's OK to dismiss the modal controller from within itself, but it's more robust to use delegates. I was initially using a delegate, but took the delegate out to simplify debugging. I just put the delegate back in to double-check, and the same behavior occurs when using delegates. The modal controller's action method calls is implemented as:
[[self delegate] myModalViewController:self didAddObject:obj];
The delegate implementation in the parent view controller is:
[self dismissModalViewController:YES]
If anyone has seen this before or has any suggestions of what could be happening or how to debug this, I would greatly appreciate it.
If -dealloc is being called, something is releasing the view controller. Try implementing -release in your view controller:
-(void)release {
NSLog(#"view controller released");
[super release];
}
so that you can use the debugger to inspect the call stack when this unexpected release message happens.
Its dangerous to call dismissModalViewController from the modal view controller itself (message will be forwarded to parent view controller), if you have not retained it elsewhere. Normally, the parent view controller is responsible for dismissing the modal view controller it presented.
I would like to show a Navigation Controller after clicking a button. Every tutorial assumes the navigation controller will be the first screen so it links it to the App Delegate, but App delegate only appears at MainWindow.xib.
How do you guys add a navigation controller to a view different than the MainWindow?
Thanks!
Here is some sample code to expand on Roger's answer. The following method is linked to some user interaction on the current view controller (to compose an email for example). This will give the compose view the navigation bar across the top instead of coding buttons inside your custom view.
-(void) composeButtonPushed: (id) sender {
ComposeViewController *controller = [[ComposeViewController alloc] initWithNibName:#"ComposeView" bundle:nil];
UINavigationController *composeNavController = [[UINavigationController alloc] initWithRootViewController:controller];
[self presentModalViewController:composeNavController animated:NO];
}
UINavigationController is to navigate a heirarchy of views with UIViewControllers. If you don't have a root UIViewController, it won't work (and doesn;t make sense). If you do have a UIViewController, you simply send a - (id)initWithRootViewController:(UIViewController *)rootViewController init message to a new navigation controller passing in your UIViewController.