Reputation: 654
UINavigationControllerDelegate methods not called when poping to a view controller that don't support the current orientation.
I have a UINavigationController as root view controller of my app (initial view controller in my Storyboard).
Let say I push a ViewController A with this for orientation:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
Hence we don't support any landscape mode. On top of that let's push another ViewController with code:
@interface B : UIViewController <UINavigationControllerDelegate>
@end
@implementation B
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES; // any orientation supported
}
- (void)viewDidLoad {
[super viewDidLoad];
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
UINavigationController *nc = (UINavigationController*)appDelegate.window.rootViewController;
nc.delegate = self;
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
// not always called...
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
// not always called...
}
@end
Now if I have my iPhone in portrait and I push view controller B on top of A, via some touch event, then touch the "back" button in the navbar, everything is fine and the delegate methods are called (I do some stuff there).
But if, when I'm viewing view B, I rotate to landscape, and then hit the back button, those delegate methods are not called!! What's the point of setting a delegate for the UINavigationController if the methods are called sometimes but not other ? Surely I'm doing something wrong.
I try to put the delegate in other class, like AppDelegate or my MainView, nothing change.
Any ideas ?
demo code here: git://github.com/malaba/NavBarTest.git
From a basic Master-Detail template, modified just as above.
How to show my point ? Here is the step-by-step:
Now try to rotate the iPhone (or Simulator) in details view, then go "back" and see no log showing up?!
Upvotes: 5
Views: 9793
Reputation: 654
by Kaspar answer, here is my code in Obj-C.
.h:
@interface ProperNavigationController : UINavigationController
@end
@interface ProperNavigationControllerDelegate : NSObject <UINavigationControllerDelegate>
@property (assign, nonatomic) BOOL wasCalled;
@end
.m:
#import "ProperNavigationController.h"
@interface ProperNavigationController ()
@property (strong, nonatomic) id<UINavigationControllerDelegate> oldDelegate;
@property (strong, nonatomic) ProperNavigationControllerDelegate *myDelegate;
@end
@implementation ProperNavigationController
@synthesize oldDelegate = _oldDelegate;
@synthesize myDelegate = _myDelegate;
- (void)viewDidLoad {
[super viewDidLoad];
self.oldDelegate = self.delegate;
self.myDelegate = [ProperNavigationControllerDelegate new];
self.delegate = self.myDelegate;
}
- (UIViewController *)popViewControllerAnimated:(BOOL)animated {
self.myDelegate.wasCalled = FALSE;
UIViewController *vc = [super popViewControllerAnimated:animated];
if (!self.myDelegate.wasCalled) {
// if iOS did not call the delegate handler then we must do it
[self.myDelegate navigationController:self willShowViewController:self.topViewController animated:animated];
[self.myDelegate navigationController:self didShowViewController:self.topViewController animated:animated];
}
return vc;
}
@end
@implementation ProperNavigationControllerDelegate
@synthesize wasCalled = _wasCalled; // flag that we use to track whether iOS calls the handlers or we have to
- (id)init {
if (self = [super init]) {
_wasCalled = FALSE;
}
return self;
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
ProperNavigationController *nc = (ProperNavigationController *)navigationController;
[nc.oldDelegate navigationController:navigationController willShowViewController:viewController animated:animated];
self.wasCalled = TRUE; // signal that we have been called
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
ProperNavigationController *nc = (ProperNavigationController *)navigationController;
[nc.oldDelegate navigationController:navigationController didShowViewController:viewController animated:animated];
}
@end
it work.
What do you think ? And should we fill a bug report ?
Upvotes: 1
Reputation: 29
Hi this was a tough issue for me too until i saw this idea: http://www.hanspinckaers.com/custom-action-on-back-button-uinavigationcontroller
When both the NavigationController and the TopViewController have the same orientation then IOS creates the following call sequence using the below code(monotouch)
When the NavigationController and the TopViewController have different orientations then the NavigationController delegate is not called as you described. The call sequence is therefore:
The following code reproduce the ios call sequence by overriding the NavigationController' PopViewControllerAnimated:
public class MyNavigationControllerDelegate : UINavigationControllerDelegate {
public MyNavigationControllerDelegate( ) : base() {}
public bool WasCalled {get;set;} // flag that we use to track whether iOS calls the handlers or we have to
public override void WillShowViewController( UINavigationController navigationController, UIViewController viewController, bool animated ) {
Console.WriteLine( "WillShowViewController viewController: {0}", viewController.GetType() );
WasCalled = true; // signal that we have been called
//.….. do your stuff
}
public override void DidShowViewController( UINavigationController navigationController, UIViewController viewController, bool animated ) {
Console.WriteLine( "DidShowViewController viewController: {0}", viewController.GetType() );
//.….. do your stuff
}
}
public partial class MyNavigationController : UINavigationController {
MyNavigationControllerDelegate navigationControllerDelegate;
public override void ViewDidLoad() {
base.ViewDidLoad();
navigationControllerDelegate = new MyNavigationControllerDelegate( viewSelectionControl );
Delegate = navigationControllerDelegate;
}
public override UIViewController PopViewControllerAnimated( bool animated ) {
Console.WriteLine( "PopViewControllerAnimated TopViewController.GetType: {0}", TopViewController.GetType() );
navigationControllerDelegate.WasCalled = false; // reset flag before we start the popsequence
UIViewController ctrl = base.PopViewControllerAnimated( animated );
AppDelegate.MainWindow.BeginInvokeOnMainThread( delegate {
if( !navigationControllerDelegate.WasCalled ) { // if iOS did not call the delegate handler then we must do it
Delegate.WillShowViewController( this, TopViewController, animated );
navigationControllerDelegate.WasCalled = false; // reset flag to be used in the next DidShowViewController step of the popsequence
}
} );
Thread t = new Thread( () => RunPop(animated) );
tt.Start();
return ctrl;
}
void RunPop(bool animated) {
Thread.Sleep( 500 );
AppDelegate.MainWindow.BeginInvokeOnMainThread( delegate {
if( !navigationControllerDelegate.WasCalled ) { // if iOS did not call the delegate handler then we must do it
Delegate.DidShowViewController(this,TopViewController,animated);
}
} );
}
}
Upvotes: 2