Forum Xamarin.iOS

Pushing to a new view within the ViewDidLoad() method using a UIStoryboardSegue

TimothyTimothy USUniversity ✭✭

I've been trying to figure out how to push to a view in the ViewDidLoad() method using a UIStoryboard Segue. So far I haven't had any luck getting it to work. I seem to keep getting an error stating "nested push animation can result in corrupted navigation bar" and "Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted". I looked around and found something talking about turning off the pop animation but I haven't been able to find any more information about it. A post similar to what I need can be found here: http://forums.xamarin.com/discussion/10992/best-approach-for-conditional-loading-of-views-from-root-controller#latest but no answers where ever posted.

If you are wondering why I would want to push to another view in the ViewDidLoad() method it is because I'm checking to see if the user already has some of the user defaults set. If they do then there is no need to grab the same information from them so I want to push them on to the next view.

Any push in the right direction would be useful.

Posts

  • adamkempadamkemp USInsider, Developer Group Leader mod

    ViewDidLoad is a bad place to out something like that. The reason is that this method is called whenever the View property is accessed for the first time. You have no idea when that will happen, and in this case it is happening within a call to PushViewController. As the error states, you can't recourse in PushViewController.

    You can try ViewWillAppear or ViewDidAppear. The difference would be that in the latter case you will briefly see this view before the new one appears, but if you use ViewWillAppear and push a new view controller without animation then it might have the visual effect you want.

    A better approach altogether would be to just push the second view controller from outside the first view controller. This kind of situation is one of many reasons I don't use storyboards. They're just not suited for so many use cases, and when you run into one for the first time you often have to choose to hack around it or abandon them and reimplement a lot of code. They're just not worth that trouble. You're really not saving much effort.

  • TimothyTimothy USUniversity ✭✭

    Well I attempted to utilize the ViewWillAppear method and this produced the same error I had before.

    Do you have to create a segue programmatically before you can push to another view or can you just push to another view? I was thinking I might be able to remove the segue from my first view to the second view(which is the one I'm currently trying to bypass based on if the user defaults are set.). When the button is pressed in the first view I could run my checks and the push to either the 2nd view or to the 3rd view bypassing the second view all together. I believe this is the gist of what you were stating could be done?

  • GuillermoGutierrezGuillermoGutierrez ESMember ✭✭✭

    The problem is that you are pushing a new view to the UINavigationController while the first animation is still in place. If you move the Segue invocation to ViewDidAppear you would solve the crash.

    My recommendation is that you perform the check before the first segue, and if you don't even need to show that screen, directly show the second one invoking a different segue. This way you will avoid the animation of the first view controller at all.

    If this view controller is the root controller, I'd load the view controller conditionally in the AppDelegate. First, remove the 'Main Interface' property from the plist file, as we are going to load it programmatically. Add a 'Storyboard Id' to your destination view controller so it can be instantiated programmatically.

    Then, add the following code to your AppDelegate:

    public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
    {
        Window = new UIWindow(UIScreen.MainScreen.Bounds);
    
        var storyboard = UIStoryboard.FromName("MainStoryboard", NSBundle.MainBundle);
    
        bool skipFirstStep = false; // Your conditional load here
        UIViewController rootViewController;
        if (skipFirstStep)
            rootViewController = (UIViewController)storyboard.InstantiateViewController("YourViewControllerId");
        else
            rootViewController = (UIViewController)storyboard.InstantiateInitialViewController();
    
        Window.RootViewController = rootViewController;
        Window.MakeKeyAndVisible();
    
        return true;
    }
    

    I have a working sample if you need to.

    Cheers!

  • TimothyTimothy USUniversity ✭✭

    I was able to get it working using the ViewDidAppear function. I haven't tried checking before even starting the Segue process. This does appear to be a better method. I would like to try and implement that at some point but for now it is working.

  • BharatAroraBharatArora USMember

    Can Anyone Send me This example.i am not able to push one view to another view with Xamarin.IOS.

    here is my code

    public override void ViewDidLoad()
    {
    base.ViewDidLoad();

            // Perform any additional setup after loading the view, typically from a nib.
    
            //to declare button programatically
            UIButton btnLogin = new UIButton(UIButtonType.RoundedRect);
            btnLogin.SetTitle("Login", UIControlState.Normal);
            btnLogin.Frame = new RectangleF(50, 50, 50, 50);
    
    
            btnLogin.TouchUpInside += (object sender, EventArgs e) =>
            {
    
                var username = txtUsername.Text;
                var Password = txtPassword.Text;
    
                if (username != null && Password != null)
                {
                    UIStoryboard Storyboard = UIStoryboard.FromName("MainStoryboard_iPhone", null);
                    TemplateViewController templaviewclass = this.Storyboard.InstantiateViewController("TemplateViewClass") as TemplateViewController;
                    this.NavigationController.PushViewController(templaviewclass, true);
                }
            };
            this.View.AddSubview(btnLogin);
        }
    

    //TemplateViewClass is my View Name for push.

  • jalesjales USMember

    public override void LoadView()
    {
    base.LoadView ();
    if (IsViewLoaded) {
    PerformSegue ("segueLogin", this);
    }
    }

  • adamkempadamkemp USInsider, Developer Group Leader mod

    Under no circumstances should you use the code in @jales's post! Let me explain again.

    First, LoadView is called when something tries to access the View property and it is null. This is guaranteed to be before your view controller is on screen, and it will be at an arbitrary point in time. It depends on how the code that loads this view controller decides to use it.

    Second, the value of IsViewLoaded in this case should always be true unless your view failed to load entirely, which is not really a circumstance you should care to try to handle. So that if condition is useless.

    LoadView is an even worse place to do this than ViewDidLoad, which I warned against above.

    I again question the intent of having a view controller which exists solely to redirect to some other view controller. If what you intend is to have a view controller that redirects somewhere else when it is first shown (and only then) then you should use ViewDidAppear (or ViewWillAppear) and keep some state that tracks whether it's been shown before. If you want it to happen every time then why does this view controller even exist? That doesn't make sense. Something is wrong with your architecture if you think you need that.

  • TimothyTimothy USUniversity ✭✭
    edited November 2015

    I perform this only in the ViewDidAppear function. Let me give some reasoning as to why this was done.

    We connect to several web services in order to retrieve various user information for our end customers. This stuff takes time and iOS doesn't allow you to take longer than a few seconds to finish up any processes on the main activity (which is when the initial splash screen is displayed). This forced us to push to another view which displays a loading overlay. (Think of it as a login screen that automatically logs a given user in and determines what steps to take next.) At this point the application reaches out to the service and pulls the user data down from the cloud. The app then has 3 other possible views it can go to based upon the user. This one application services 3 different types of end customer with many similar bits of functionality but also some unique parts based upon the type. This is why we have a kind of loading/in between screen. It's important to note that this view is not entirely bypassed every time. If the application cannot retrieve the user data, or there isn't an internet connection, this view is used to display and of those errors and allows the user to retry the process.

    Hopefully this does a bit better job of explaining why this was being done.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    Starting a process in ViewDidAppear is entirely reasonable. Unconditionally switching to another view controller immediately doesn't make sense, though. The code in ViewDidAppear should be asynchronous. It's still not a good idea to actually block the UI thread for any prolonged amount of time, even if you do it outside of FinishedLaunching.

  • TimothyTimothy USUniversity ✭✭

    I believe all the data fetching is performed on a background thread. The only part that runs on the UI Thread is the loading overlay being presented and then the segue after the data has been received.

    I can see the point you're making about automatically switching views immediately without any processing. I'm not sure why you would need to immediately switch if no processing was being performed at all. It would seem that a segue could be made to go around that view in those circumstances.

  • madavermadaver Member

    @GillermoGutierrez you are a crack, I used your solution for a DeviceNotificationResponse, for send the correct Tab great.

    [Export("userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:")]
    public void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler)
    {
    Console.WriteLine("Handling push notificaiton interaction");

            string action = response.Notification.Request.Content.CategoryIdentifier;
    
            Console.WriteLine("Accion:  " + action);
    
            UIStoryboard board = UIStoryboard.FromName("Main", null);
            UITabBarController ctrl = (UITabBarController)board.InstantiateViewController("TabBarController");
    
            switch (action)
            {
                case "Notification":
                    ctrl.SelectedIndex = 1;
                    break;
                case "Inbox":
                    ctrl.SelectedIndex = 2;
                    break;
                default:
                    ctrl.SelectedIndex = 0;
                    break;
            }
    
            Window.RootViewController = ctrl;//.PresentViewController(ctrl, true, null);
            Window.MakeKeyAndVisible();
    
            completionHandler();            
    
        }
    
Sign In or Register to comment.