How do be notified when a ViewController is removed from a NavigationController - iphone

Is there a way to be notified when a ViewController is removed from a UINavigationController because the back button was pressed?

You can use viewWillDisappear: in the view controller that is disappearing. If the other view controller needs to be notified, you can use a delegate method to notify it:
//in the disappearing view controller, class MYViewController
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
//do stuff you need to do
if ([self.delegate respondsToSelector:#selector(myViewControllerDidDisappear:)])
[self.delegate myViewControllerDidDisappear:self]; //bottom view controller is delegate
}

In conjunction with eman's method, check
[navController.viewcontrollers count]
If it is one greater than before (you need to maintain a count) then something was pushed. If it is one less, and viewWillDisappear: was called, then the view controller was removed.

Related

Dismiss two modal (table)view controllers

I know there's like 3-5 similar questions here, but non of the answers solves my problem.
I have a ViewController that opens a modal (table)view controller, which opens another one. Both modal view controllers are in fact table view controllers. I'm trying to dismiss both of them from the second one. I tried every accepted answer on similar question, none of them worked for me.
I tried
[self dismissModalViewControllerAnimated:true]
[self.parentViewController dismissModalViewControllerAnimated:true]
[self.parentViewController.parentViewController dismissModalViewControllerAnimated:true]
[self.presentingViewController dismissModalViewControllerAnimated:true]
[self.presentingViewController.presentingViewController dismissModalViewControllerAnimated:true]
When I try options 2, 3 and 5, nothing happens at all. When I use options 1, and 4, I see dismiss modal view animation and the underlying view itself for a moment, and then everything goes back to the second modal view (this time without animation).
I'm starting to think that this have something with the fact that I use tableViewControllers for modal views.
Btw, I'm dismissing modal views in didSelectRowAtIndexPath.
Try this:-
When you dismiss your SecondView set a BOOL flag variable in app delegate file and check that variable in your FirstView's viewWillAppear method whether SecondView was open and close or not. If so, then [self dismissModalViewControllerAnimated:true]
typical model view controller behavior would suggest that you dismiss the modal view controller from the calling view controller rather than from self. not a hard and fast rule, but good practice.
to accomplish this, create a protocol:
#protocol MyModalViewControllerDelegate
- (void)modalViewControllerDidFinish;
#end
and make both the parentViewController and FirstModalViewController be implemntors of this protocol.
#interface FirstModalViewController <MyModalViewControllerDelegate>
then in both FirstModalViewController.h and SecondModalViewController.h, add:
#property id<MyModalViewControllerDelegate> modalViewControllerDelegate
in both parentViewController and FirstModalViewController, right before calling presentModalViewController:... , set the following:
modalViewControllerAboutToAppear.modalViewControllerDelegate = self;
[self presentModalViewController:modalViewControllerAboutToAppear animated:YES];
next, in the SecondModalViewController, in the code where you determine that the item needs to be dismissed, call
[self.modalViewControllerDelegate modalViewControllerDidFinish];
now, in FirstModalViewController, implement the following:
- (void)modalViewControllerDidFinish:(MyModalViewController*)controller {
[self dismissModalViewControllerAnimated:YES]
[self.modalViewControllerDelegate modalViewControllerDidFinish];
}
and finally, in the parent view controller, you should be able to perform:
- (void)modalViewControllerDidFinish:(MyModalViewController*)controller {
[self dismissModalViewControllerAnimated:YES]
}
Since I don't use delegate files, I did the following:
To FirstView add field
BOOL mClose;
To FirstView add method
- (void)close
{
mClose = YES;
}
To FirstView method viewDidAppear add
if (mClose)
{
[self dismissModalViewControllerAnimated:YES];
}
To FirstView method which opens SecondView add
[secondView closeWhenDone:self];
To SecondView add field
FirstView *mParent;
To SecondView add method
- (void)closeWhenDone:(FirstView*)parent
{
mParent = parent;
}
To SecondView method which closes it add
[mParent close];

How to pop a viewController and then Push a viewController through delegation

I have a UITableViewController that when a cell is pressed, I want the controller to pop itself, and then have the controller it pop's to, push another view controller onto the stack.
I am invoking this method because the popped-to viewController is the delegate of the tableViewController
I am currently invoking this method with a delay on it, because otherwise, everything gets screwed up waiting for the animation to end. Doing it this way seems a bit hacky and seems to me like it would fail if someone's device didn't pop the view in the allotted wait time I have given it.
Here is some of the code:
//**** code in my tableViewController ***//
[self.navigationController popViewControllerAnimated:YES];
[self.delegate cellPressedInTableViewControllerWithCalculationsModel:(id)anArgmentMyDelegateMethodTakes];
// **** Code in the viewController being popped to ****//
//CalculationsViewController is a subclass of UIViewController
CalculationsViewController *calcViewController = [[CalculationsViewController alloc] init];
//some customization code would go her
[self.navigationController performSelector:#selector(pushViewController:animated:) withObject:calcViewController afterDelay:0.75];
//this seems like the arbitrary part, the 0.75 second delay.
[calcViewController release];
There seems like there should be a better way to pop/push through delegation that will execute after the animation finishes. The wait time seems to me like it could cause unexpected problems.
I have also tried using:
performSelectorOnMainThread:withObject:waitUntilDone
But the code just executes immediately and the view hierarchy screwed up.
I have also looked at this question:
Delegation question
and it has gotten me this far, but I am curious to see if there is a better way to perform such a task,
Thanks.
edit: I have also tried wrapping the method in an instance of NSInvocation, and I couldn't get it to coordinate the method call until after the animation finished without arbitrarily setting the delay
The cleanest way to do more than a single push or pop of a view controller is to set the UINavigationControllers view controllers array.
For example, to pop and then push a view controller:
MyTableViewController *vc = [[MyTableViewController alloc] init];
NSMutableArray *controllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[controllers removeLastObject];
[controllers addObject:vc];
[self.navigationController setViewControllers:controllers animated:YES];
You should use a flag to overcome this situation. You set this flag in viewWillDisappear method of view controller being popped. When this flag is set then and then you can push another view controller on stack. Hope it's clear.
How about when you dismiss your UIViewController containing the table you send a NSNotifcation in your viewDidDisappear method like so:
- (void)viewDidDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"loadOtherVC" object:nil];
}
And in your parent view controller that will push a new view controller, you add an observer for that notification like so:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(LoadOtherVC:) name:#"loadOtherVC" object:nil];
You should have a method that matches the selector.
- (void) LoadOtherVC:(NSNotification *) notification
{
// load your other view controller you want here
}
Don't pop it from the top level controller. Call the delegate method that will pop the view off the navigation controller and then push a new one on.
I liked Alexandre's solution but if I were a delegate person, I wouldn't want to use notification. So in that case, we can just use the delegate in the viewDidDisappear method.
So, in the didSelectRowAtIndexPath method, you can pop the controller-
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[self.navigationController popViewControllerAnimated:YES];
}
and in the same View Controller, the ViewController you are popping, call the delegate method in the viewDidDisappear method like-
-(void)viewDidDisappear:(BOOL)animated{
[self.delegate cellPressedInTableViewControllerWithCalculationsModel:(id)anArgmentMyDelegateMethodTakes];
}
Then in the controller that is pushed can implement the delegate method and inside that delegate method, you can do whatever you want.

iPhone, need the IF for dismissModalViewControllerAnimated ELSE removeFromSuperview?

I need to add this to my dismiss button :-
[self dismissModalViewControllerAnimated:YES];
[self release];
else
[self.view removeFromSuperview];
I thought
if( self.navigationController.modalViewController ) {
would work be it nevers true
A couple of things:
1) You shouldn't ever release yourself in an object. If you're presenting a modal view controller, you should perform the release there since the view controller will now be retained by the view controller's .modalViewController property:
(In the parent):
UIViewController *someViewController = [[UIViewController alloc] init];
[self presentModalViewController:someViewController animated:YES];
[someViewController release];
2) The parent will store its child modal view controller in .modalViewController. The child will have its .parentViewController property set in this case. If the view has been added as a subview, its .superview property will be set. These are not mutually exclusive, however, so be careful. Generally speaking, UIViewControllers are intended to host full-screen views, and if you're adding the view as a subview, you should ask yourself if the view should just be a UIView subclass, and move the logic into the parent view controller.
That said, I suppose you could check your case (assuming you don't present modal view controller and add as a subview simultaneously):
if (self.parentViewController) {
[self dismissModalViewControllerAnimated:YES];
} else if (self.view.superview) {
[self.view removeFromSuperview]
}
In the latter superview case, the view controller will still be hanging around, so you'd need to let the other view controller know via delegate method or something to release you. In the first case, if you have released the presented view controller already as I described above, it will be released automatically when the parent view controller sets its .modalViewController property to nil.
Normally for a "dismiss" button I would call a method in the controller that presented the modal controller (use a delegate), not try to dismiss the modal view controller from within itself. I don't quite get what youre trying to do though, but that [self release] looks bad. I don't think you ever want to release self like that.
Try this in you modal viewcontroller:
- (IBAction)close:(id)sender {
[self.parentViewController dismissModalViewControllerAnimated:YES];
}
Then just connect the button's action to that method.

Perform an action when tabBarController changes

I have a tabbar application that has one screen that displays statistics based on the data presented in the tableviews of over tab screens. I would like to refresh this view once the stats view becomes selected again. I have implemented the tabbarcontrollerdelegate protocol to take an action when the viewcontroller.tabbaritem.title isequaltostring:#"foo". which works fine for my nslog statement but when i try and trigger the viewcontroller to execute the viewdidload method it never happens. And the code to refresh the stats view is in the viewdidload method.
From my AppDelegate
- (void)tabBarController:(UITabBarController*)tabBarController didEndCustomizingViewControllers: (NSArray*)viewControllers changed:(BOOL)changed
{
}
- (void)tabBarController:(UITabBarController*)tabBarController didSelectViewController:(UIViewController*)viewController {
if([viewController.tabBarItem.title isEqualToString:#"Summary"]) {
NSLog(#"didSelectViewController %#", viewController.tabBarItem.title);
[viewController viewDidLoad]; //FAIL
}
}
Never call viewDidLoad by yourself. That is a delegate method that is sent to the view controller after the view has loaded, you shouldn't call it manually.
In this case, view controllers that have views which are managed by a tab bar controller are sent the viewWillAppear:, viewDidAppear:, viewWillDisappear: and viewDidDisappear.
You should should use these methods to perform actions when your views are shown and hidden.
Example: implement viewDidAppear: and refresh your stats view.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated]; // don't forget to call super, this is important
// do your refreshing here
}

Update UIViewControllers after switching of the tabbar selection

I have two view controllers in a tabbar which can both edit data. Therefore, I need to call a reload_data function whenever the user makes a switch on the tabbar. How can I catch the switch or the appearance of the viewcontroller. Somehow viewDidAppear is not called on a tabbar switch. And I do not want to use the tabbarController delegate for this, because several viewControllers are affected (and I cannot set them all as delegate). What is a good way to solve this?
e.g. this didn't work:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];
[self reloadData];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:YES];
[self reloadData];
}
If you're using Interface Builder, make sure the class for the viewController your expecting to reload is defined (Select the ViewController in IB, then CMD-4, make sure class is defined to be the class you want viewWillAppear and viewDidAppear to be called in).
If you're not using IB, post your code for init/calling the viewController.