malaba
malaba

Reputation: 654

UINavigationControllerDelegate methods not called (demo code on github)

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:

  1. Add some line in Master View (the + top right)
  2. then touch a line to go to details view
  3. See Log in output showing up
  4. Hit back button (labeled "Master"), again log showing up.

Now try to rotate the iPhone (or Simulator) in details view, then go "back" and see no log showing up?!

Upvotes: 5

Views: 9793

Answers (2)

malaba
malaba

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

Kaspar
Kaspar

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)

  • SomeTopViewController ViewWillDisappear
  • WillShowViewController viewController: the new TopViewController
  • SomeTopViewController ViewDidDisappear
  • DidShowViewController viewController: the new TopViewController

When the NavigationController and the TopViewController have different orientations then the NavigationController delegate is not called as you described. The call sequence is therefore:

  • SomeTopViewController ViewWillDisappear
  • SomeTopViewController ViewDidDisappear

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

Related Questions