Sten Petrov
Sten Petrov

Reputation: 11040

Xamarin Forms Navigation without animation

I have an app where I want to show page A, from which the user can navigate to page B or C, from B back to A or to C, and from C only back to A, even if the user when through B to get to C Pages Navigation

Currently when I'm executing the B->C transition I first PopAsync to get back to A and then I do PushAsync to get to C, so that the '

The question is: is there a civilized way to set up this navigation scheme while still relying on the built-in Navigation to keep track of navigation stack - I don't want to do that myself and use PushModalAsync.

Note that (as reflected in the image) A and C aren't the end points of the whole navigation stack, there are pages before A and after C, so the stack has to be preserved.

Upvotes: 4

Views: 12849

Answers (5)

WitoldW
WitoldW

Reputation: 898

@Falko You now have the possibility to include a boolean parameter:

 Navigation.PushAsync (new Page2Xaml (), false);

Xamarin Documentation

Upvotes: 5

Caitlin
Caitlin

Reputation: 748

What I would do if I were doing this is push Page C on to your NavigationStack and then take page B off of the stack. That way when you pop from page C, you would go to page A.

// Push the page you want to go to on top of the stack.    
await NavigationPage.PushAsync(new CPage()));

// Remove page B from the stack, so when you want to go back next time 
//you will go to page A.   
Navigation.RemovePage(Navigation.NavigationStack[Navigation.NavigationStack.Count - 2] );

Alternatively, when even you pop from page C, you could remove all instances of type page B from the stack, and then pop back 1. In that case, page B would remain on the stack until you were about to move back from page C to page A.

Upvotes: 0

macawm
macawm

Reputation: 142

Here's a collection of snippets I whipped together along with some other niceties to improve NaviagationPage for iOS. Link to comment and code on xamarin forums.

Upvotes: 0

Falko
Falko

Reputation: 17932

On iOS the NavigationRenderer has virtual methods OnPopViewAsync and OnPushAsync (similar on Android):

protected override Task<bool> OnPopViewAsync(Page page, bool animated)
{
    return base.OnPopViewAsync(page, animated);
}

protected override Task<bool> OnPushAsync(Page page, bool animated)
{
    return base.OnPushAsync(page, animated);
}

They call the corresponding base method with two arguments, the page and whether to animate the transition. Thus, you might be able to enable or disable the animation using the following approach:

  1. Derive a custom navigation page.
  2. Add an "Animated" property.
  3. Derive a custom navigation renderer for your custom navigation page.
  4. Override the pop and push methods calling their base methods with the "Animated" property.

Note that I haven't tried this approach, yet, since it is quite some work to do. But disabling animations on all navigation pages did work this way.


Edit: It took me several hours to actually implement my solution for my own project. Therefore, I'll share some more details. (I developed and tested on Xamarin.Forms 1.2.3-pre4.)

The custom navigation page

Besides the above-mentioned Animated property my navigation page re-implements the two transition functions and adds an optional argument animated, which is true by default. This way we'll be able to keep all existing code and only add a false where needed.

Furthermore, both method will sleep for a very short time (10 ms) after pushing/popping the page. Without this delay we'd ran into trouble with consecutive calls.

public class CustomNavigationPage: NavigationPage
{
    public bool Animated { get; private set; }

    public CustomNavigationPage(Page page) : base(page)
    {
    }

    // Analysis disable once MethodOverloadWithOptionalParameter
    public async Task PushAsync(Page page, bool animated = true)
    {
        Animated = animated;
        await base.PushAsync(page);
        await Task.Run(delegate {
            Thread.Sleep(10);
        });
    }

    // Analysis disable once MethodOverloadWithOptionalParameter
    public async Task<Page> PopAsync(bool animated = true)
    {
        Animated = animated;
        var task = await base.PopAsync();
        await Task.Run(delegate {
            Thread.Sleep(10);
        });
        return task;
    }
}

The custom navigation renderer

The renderer for my custom navigation page overrides both transition methods and passes the Animated property to their base methods. (It's kind of ugly to inject a flag this way, but I couldn't find a better solution.)

public class CustomNavigationRenderer: NavigationRenderer
{
    protected override Task<bool> OnPopViewAsync(Page page, bool animated)
    {
        return base.OnPopViewAsync(page, (Element as CustomNavigationPage).Animated);
    }

    protected override Task<bool> OnPushAsync(Page page, bool animated)
    {
        return base.OnPushAsync(page, (Element as CustomNavigationPage).Animated);
    }
}

This is for iOS. But on Android it's almost identically.

An example application

To demonstrate the possibilities of consecutively pushing and popping pages, I wrote the following application.

The App class simply creates a new DemoPage wrapped into a CustomNavigationPage. Note that this instance must be publicly accessible for this example.

public static class App
{
    public static CustomNavigationPage NavigationPage;

    public static Page GetMainPage()
    {    
        return NavigationPage = new CustomNavigationPage(new DemoPage("Root"));
    }
}

The demo page contains a number of buttons that push and pop pages in different orders. You can add or remove the false option for each call to PushAsync or PopAsync.

public class DemoPage: ContentPage
{
    public DemoPage(string title)
    {
        Title = title;
        Content = new StackLayout {
            Children = {
                new Button {
                    Text = "Push",
                    Command = new Command(o => App.NavigationPage.PushAsync(new DemoPage("Pushed"))),
                },
                new Button {
                    Text = "Pop",
                    Command = new Command(o => App.NavigationPage.PopAsync()),
                },
                new Button {
                    Text = "Push + Pop",
                    Command = new Command(async o => {
                        await App.NavigationPage.PushAsync(new DemoPage("Pushed (will pop immediately)"));
                        await App.NavigationPage.PopAsync();
                    }),
                },
                new Button {
                    Text = "Pop + Push",
                    Command = new Command(async o => {
                        await App.NavigationPage.PopAsync(false);
                        await App.NavigationPage.PushAsync(new DemoPage("Popped and pushed immediately"));
                    }),
                },
                new Button {
                    Text = "Push twice",
                    Command = new Command(async o => {
                        await App.NavigationPage.PushAsync(new DemoPage("Pushed (1/2)"), false);
                        await App.NavigationPage.PushAsync(new DemoPage("Pushed (2/2)"));
                    }),
                },
                new Button {
                    Text = "Pop twice",
                    Command = new Command(async o => {
                        await App.NavigationPage.PopAsync(false);
                        await App.NavigationPage.PopAsync();
                    }),
                },
            },
        };
    }
}

Important hint: It cost me hours of debugging to find out that you need to use an instance of NavigationPage (or a derivative) rather than the ContentPage's Navigation! Otherwise the immediate call of two or more pops or pushes leads to strange behaviour and crashes.

Upvotes: 6

Miha Markic
Miha Markic

Reputation: 3250

Currently the Xamarin Forms navigation is very spartanic and I doubt there is a nice way to achieve that. Besides Doing and extra "Pop" when necessary.

Upvotes: 0

Related Questions