Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using System.Threading.Tasks;
using CoreGraphics;
using Foundation;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
Expand Down Expand Up @@ -42,8 +43,9 @@ public class NavigationRenderer : UINavigationController, IPlatformViewHandler
ViewHandlerDelegator<NavigationPage> _viewHandlerWrapper;
bool _navigating = false;
VisualElement _element;
bool _uiRequestedPop; // User tapped the back button or swiped to navigate back

[Preserve(Conditional = true)]
[Internals.Preserve(Conditional = true)]
public NavigationRenderer() : base(typeof(MauiControlsNavigationBar), null)
{
_viewHandlerWrapper = new ViewHandlerDelegator<NavigationPage>(Mapper, CommandMapper, this);
Expand Down Expand Up @@ -780,7 +782,7 @@ void UpdateToolBarVisible()
TopViewController?.NavigationItem?.TitleView?.LayoutSubviews();
}

internal async Task UpdateFormsInnerNavigation(Page pageBeingRemoved)
async Task UpdateFormsInnerNavigation(Page pageBeingRemoved)
{
if (NavPage == null)
return;
Expand All @@ -789,9 +791,24 @@ internal async Task UpdateFormsInnerNavigation(Page pageBeingRemoved)

_ignorePopCall = true;
if (Element.Navigation.NavigationStack.Contains(pageBeingRemoved))
{
await (NavPage as INavigationPageController)?.RemoveAsyncInner(pageBeingRemoved, false, true);
if (_uiRequestedPop)
{
NavPage?.SendNavigatedFromHandler(pageBeingRemoved);
}
}

_ignorePopCall = false;
_uiRequestedPop = false;
}

[Export("navigationBar:shouldPopItem:")]
[Internals.Preserve(Conditional = true)]
internal bool ShouldPopItem(UINavigationBar _, UINavigationItem __)
{
_uiRequestedPop = true;
return true;
}

internal static void SetFlyoutLeftBarButton(UIViewController containerController, FlyoutPage FlyoutPage)
Expand Down Expand Up @@ -959,7 +976,9 @@ void SetupLines()

class MauiNavigationDelegate : UINavigationControllerDelegate
{
bool _finishedWithInitialNavigation;
readonly WeakReference<NavigationRenderer> _navigation;

public MauiNavigationDelegate(NavigationRenderer navigationRenderer)
{
_navigation = new WeakReference<NavigationRenderer>(navigationRenderer);
Expand All @@ -974,6 +993,12 @@ public override void DidShowViewController(UINavigationController navigationCont
{
pvc.UpdateFrames();
}

if (r.Element is NavigationPage np && !_finishedWithInitialNavigation)
{
_finishedWithInitialNavigation = true;
np.SendNavigatedFromHandler(null);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ namespace Microsoft.Maui.Controls
/// <include file="../../../../docs/Microsoft.Maui.Controls/NavigationPage.xml" path="Type[@FullName='Microsoft.Maui.Controls.NavigationPage']/Docs/*" />
public partial class NavigationPage : INavigationPageController
{
internal async Task<Page> PopAsyncInner(
async Task<Page> PopAsyncInner(
bool animated,
bool fast,
bool requestedFromHandler)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the code was just passing false to requestedFromHandler so I removed it.

bool fast)
{
if (NavigationPageController.StackDepth == 1)
{
Expand All @@ -27,16 +26,15 @@ internal async Task<Page> PopAsyncInner(
var page = (Page)InternalChildren.Last();
var previousPage = CurrentPage;
SendNavigating();
var removedPage = await RemoveAsyncInner(page, animated, fast, requestedFromHandler);
var removedPage = await RemoveAsyncInner(page, animated, fast);
SendNavigated(previousPage);
return removedPage;
}

internal async Task<Page> RemoveAsyncInner(
async Task<Page> RemoveAsyncInner(
Page page,
bool animated,
bool fast,
bool requestedFromHandler)
bool fast)
{
if (NavigationPageController.StackDepth == 1)
{
Expand All @@ -53,7 +51,7 @@ internal async Task<Page> RemoveAsyncInner(
var removed = true;

EventHandler<NavigationRequestedEventArgs> requestPop = _popRequested;
if (requestPop != null && !requestedFromHandler)
if (requestPop != null)
{
requestPop(this, args);

Expand All @@ -74,15 +72,14 @@ internal async Task<Page> RemoveAsyncInner(
return page;
}


Task<Page> INavigationPageController.PopAsyncInner(bool animated, bool fast)
{
return PopAsyncInner(animated, fast, false);
return PopAsyncInner(animated, fast);
}

Task<Page> INavigationPageController.RemoveAsyncInner(Page page, bool animated, bool fast)
{
return RemoveAsyncInner(page, animated, fast, false);
return RemoveAsyncInner(page, animated, fast);
}

EventHandler<NavigationRequestedEventArgs> _popRequested;
Expand Down Expand Up @@ -207,6 +204,20 @@ async Task PushAsyncInner(Page page, bool animated)
Pushed?.Invoke(this, args);
}

#if IOS
// Because iOS currently doesn't use our `IStackNavigationView` structures
// there are scenarios where the legacy handler needs to alert the xplat
// code of when a navigation has occurred.
// For example, initial load and when the user taps the back button
internal void SendNavigatedFromHandler(Page previousPage)
{
if (CurrentPage.HasNavigatedTo)
return;

SendNavigated(previousPage);
}
#endif

void PushPage(Page page)
{
InternalChildren.Add(page);
Expand Down
7 changes: 0 additions & 7 deletions src/Controls/src/Core/NavigationPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -377,13 +377,6 @@ protected override bool OnBackButtonPressed()
return base.OnBackButtonPressed();
}

internal void InitialNativeNavigationStackLoaded()
{
SendNavigated(null);
}



void SendNavigated(Page previousPage)
{
previousPage?.SendNavigatedFrom(new NavigatedFromEventArgs(CurrentPage));
Expand Down
16 changes: 10 additions & 6 deletions src/Controls/tests/Core.UnitTests/PageLifeCycleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ namespace Microsoft.Maui.Controls.Core.UnitTests

public class PageLifeCycleTests : BaseTestFixture
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public void NavigationPageInitialPage(bool useMaui)
[Fact]
// This test isn't valid for non handler based
// navigation because the initial navigated event
// fires from the legacy code instead of the
// new handler code
// We have device tests to also verify this works on
// each platform
public async Task NavigationPageInitialPage()
{
var lcPage = new LCPage();
NavigationPage navigationPage = new TestNavigationPage(useMaui, lcPage);
navigationPage.InitialNativeNavigationStackLoaded();
var navigationPage = new TestNavigationPage(true, lcPage);
await navigationPage.NavigatingTask;
Assert.Null(lcPage.NavigatingFromArgs);
Assert.Null(lcPage.NavigatedFromArgs);
Assert.NotNull(lcPage.NavigatedToArgs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public async Task<bool> SendBackButtonPressedAsync()
return result;
}


public Task NavigatingTask => Handler?.NavigatingTask ?? Task.CompletedTask;
}

public class TestNavigationHandler : ViewHandler<NavigationPage, object>
Expand All @@ -54,24 +54,36 @@ public static PropertyMapper<IStackNavigationView, TestNavigationHandler> Naviga

public NavigationRequest CurrentNavigationRequest { get; private set; }

TaskCompletionSource _navigationSource;

public Task NavigatingTask => (_navigationSource?.Task ?? Task.CompletedTask);

public void CompleteCurrentNavigation()
public async void CompleteCurrentNavigation()
{
if (CurrentNavigationRequest == null)
throw new InvalidOperationException("No Active Navigation in the works");

var newStack = CurrentNavigationRequest.NavigationStack.ToList();
CurrentNavigationRequest = null;

var source = _navigationSource;
_navigationSource = null;

if ((this as IElementHandler).VirtualView is IStackNavigation sn)
sn.NavigationFinished(newStack);


await Task.Delay(1);
source.SetResult();
}

async void RequestNavigation(NavigationRequest navigationRequest)
{
if (CurrentNavigationRequest != null)
if (CurrentNavigationRequest != null || _navigationSource != null)
throw new InvalidOperationException("Already Processing Navigation");


_navigationSource = new TaskCompletionSource();
CurrentNavigationRequest = navigationRequest;

await Task.Delay(10);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,43 @@ await CreateHandlerAndAddToWindow<WindowHandlerStub>(new Window(navPage), async
});
}

[Fact]
public async Task InitialPageFiresNavigatedEvent()
{
SetupBuilder();
var page = new ContentPage();
var navPage = new NavigationPage(page) { Title = "App Page" };

await CreateHandlerAndAddToWindow<WindowHandlerStub>(new Window(navPage), async (handler) =>
{
await OnNavigatedToAsync(page);
Assert.True(page.HasNavigatedTo);
});
}

[Fact]
public async Task PushedPageFiresNavigatedEventOnInitialLoad()
{
SetupBuilder();

bool pageFiredNavigated = false;
var page = new ContentPage();
page.NavigatedTo += (_, _) => pageFiredNavigated = true;

var page2 = new ContentPage();

var navPage = new NavigationPage(page) { Title = "App Page" };
await navPage.PushAsync(page2);

await CreateHandlerAndAddToWindow<WindowHandlerStub>(new Window(navPage), async (handler) =>
{
await OnNavigatedToAsync(page2);
Assert.True(page2.HasNavigatedTo);
});

Assert.False(pageFiredNavigated);
}

#if !IOS && !MACCATALYST
[Fact(DisplayName = "Back Button Visibility Changes with push/pop")]
public async Task BackButtonVisibilityChangesWithPushPop()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,41 @@
using Xunit;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Platform;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Controls.Handlers.Compatibility;
using Microsoft.Maui.DeviceTests.Stubs;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Hosting;
using UIKit;
using Xunit;

namespace Microsoft.Maui.DeviceTests
{
public partial class NavigationPageTests : ControlsHandlerTestBase
{
[Fact]
public async Task NavigatingBackViaBackButtonFiresNavigatedEvent()
{
SetupBuilder();
var page = new ContentPage();

var navPage = new NavigationPage(page) { Title = "App Page" };

await navPage.PushAsync(new ContentPage());
await CreateHandlerAndAddToWindow<WindowHandlerStub>(new Window(navPage), async (handler) =>
{
await OnNavigatedToAsync(navPage.CurrentPage);
var navController = navPage.Handler as UINavigationController;

Assert.False(page.HasNavigatedTo);
navController.NavigationBar.TapBackButton();
await OnNavigatedToAsync(page);
Assert.True(page.HasNavigatedTo);
});
}
}
}