Skip to content

Commit

Permalink
fix: Ensure iOS NavigationBar renders when inside of AutoLayout (#525)
Browse files Browse the repository at this point in the history
* fix: Ensure iOS NavigationBar renders when inside of AutoLayout
  • Loading branch information
kazo0 committed Mar 24, 2023
1 parent 930f0d5 commit 403194e
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Windows.Input;
using Uno.Toolkit.Samples.Entities;
using Uno.Toolkit.Samples.ViewModels;
using Uno.UI;
using Windows.Foundation;
using Windows.Foundation.Collections;
#if IS_WINUI
Expand All @@ -26,21 +27,16 @@
using Windows.UI.Xaml.Navigation;
#endif

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238

namespace Uno.Toolkit.Samples.Content.NestedSamples
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class M3MaterialNavigationBarSample_Primary : Page
{
public M3MaterialNavigationBarSample_Primary()
{
this.InitializeComponent();
}
public sealed partial class M3MaterialNavigationBarSample_Primary : Page
{
public M3MaterialNavigationBarSample_Primary()
{
this.InitializeComponent();
}

private void NavigateBack(object sender, RoutedEventArgs e) => Shell.GetForCurrentView().BackNavigateFromNestedSample();

}
private void NavigateBack(object sender, RoutedEventArgs e) => Shell.GetForCurrentView().BackNavigateFromNestedSample();
}
}
44 changes: 44 additions & 0 deletions src/Uno.Toolkit.RuntimeTests/Tests/NavigationBarTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,50 @@ public async Task MainCommand_In_Popup_With_Page_With_BackStack(MainCommandMode
popup.IsOpen = false;
}
}

#if __IOS__
[TestMethod]
public async Task NavigationBar_Does_Render()
{
var frame = new Frame { Width = 200, Height = 200 }; ;
await UnitTestUIContentHelperEx.SetContentAndWait(frame);

frame.Navigate(typeof(NavBarSimplePage));

await UnitTestsUIContentHelper.WaitForIdle();

AssertNavigationBar(frame);
}

[TestMethod]
public async Task NavigationBar_Does_Render_Within_AutoLayout()
{
var frame = new Frame { Width = 200, Height = 200 };;
await UnitTestUIContentHelperEx.SetContentAndWait(frame);

frame.Navigate(typeof(NavBarAutoLayoutPage));

await UnitTestsUIContentHelper.WaitForIdle();

AssertNavigationBar(frame);
}

private static void AssertNavigationBar(Frame frame)
{
var page = frame.Content as Page;
var presenter = frame.FindChild<NativeFramePresenter>();
var navBar = page?.FindChild<NavigationBar>();

var renderedNativeNavItem = navBar?.GetRenderer<NavigationBar, NavigationBarNavigationItemRenderer>(null)?.Native;
var renderedNativeNavBar = navBar?.GetRenderer<NavigationBar, NavigationBarRenderer>(null)?.Native;

Assert.IsNotNull(presenter);
Assert.IsFalse(presenter!.NavigationController.NavigationBarHidden);

Assert.AreSame(renderedNativeNavItem, presenter.NavigationController.TopViewController.NavigationItem);
Assert.AreSame(renderedNativeNavBar, presenter.NavigationController.NavigationBar);
}
#endif
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Page x:Class="Uno.Toolkit.RuntimeTests.Tests.TestPages.NavBarAutoLayoutPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Uno.Toolkit.RuntimeTests.Tests.TestPages"
xmlns:utu="using:Uno.Toolkit.UI"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<utu:AutoLayout>
<utu:NavigationBar Content="Testing" />
</utu:AutoLayout>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Uno.Toolkit.UI;

#if IS_WINUI
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
#else
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
#endif

namespace Uno.Toolkit.RuntimeTests.Tests.TestPages
{
public sealed partial class NavBarAutoLayoutPage : Page
{
public NavBarAutoLayoutPage()
{
this.InitializeComponent();
}
}
}
12 changes: 12 additions & 0 deletions src/Uno.Toolkit.RuntimeTests/Tests/TestPages/NavBarSimplePage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Page x:Class="Uno.Toolkit.RuntimeTests.Tests.TestPages.NavBarSimplePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Uno.Toolkit.RuntimeTests.Tests.TestPages"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:utu="using:Uno.Toolkit.UI"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<utu:NavigationBar Content="Testing" />
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Uno.Toolkit.UI;

#if IS_WINUI
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
#else
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
#endif

namespace Uno.Toolkit.RuntimeTests.Tests.TestPages
{
public sealed partial class NavBarSimplePage : Page
{
public NavBarSimplePage()
{
this.InitializeComponent();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,20 @@ public PageViewController(Page? page)
NavigationBarHelper.PageCreated(this);
}


public override void ViewDidAppear(bool animated)
{
try
{
base.ViewDidAppear(animated);

NavigationBarHelper.PageDidAppear(this);
}
catch (Exception e)
{
this.Log().Error($"{e.Message}", e);
}
}
public override void ViewWillAppear(bool animated)
{
try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public static void PageWillAppear(UIViewController pageController)
var topNavigationBar = pageController.View.FindTopNavigationBar();
if (topNavigationBar != null)
{
EnsureNavigationItem(topNavigationBar, pageController);

if (topNavigationBar.Visibility == Visibility.Visible)
{
SetNavigationBar(topNavigationBar, pageController.NavigationController!.NavigationBar);
Expand Down Expand Up @@ -97,6 +99,53 @@ public static void PageWillAppear(UIViewController pageController)
}
}

// In some cases the NavigationBar may not be rendered yet when PageCreated/PageWillAppear is called
// This can be the case when the NavigationBar is part of an AutoLayout
// since the AutoLayout is delayed in materializing its Children
public static void PageDidAppear(UIViewController pageController)
{
var topNavigationBar = pageController.View.FindTopNavigationBar();
if (topNavigationBar != null)
{
EnsureNavigationItem(topNavigationBar, pageController);

if (topNavigationBar.Visibility == Visibility.Visible)
{
SetNavigationBar(topNavigationBar, pageController.NavigationController!.NavigationBar);

// When the NavigationBar is visible, we need to call SetNavigationBarHidden
// AFTER it has been rendered. Otherwise, it causes a bug introduced
// in iOS 11 in which the BackButtonIcon is not rendered properly.
pageController.NavigationController.SetNavigationBarHidden(hidden: false, animated: false);
}
else
{
// Even if the NavigationBar should technically be collapsed,
// we don't hide it using the NavigationController because it
// automatically disables the back gesture.
// In order to visually hide it, the NavigationBarRenderer
// will hide the native view using the UIView.Hidden property.
pageController.NavigationController!.SetNavigationBarHidden(hidden: false, animated: false);
SetNavigationBar(topNavigationBar, pageController.NavigationController.NavigationBar);
}
}
else // No NavigationBar
{
pageController.NavigationController!.SetNavigationBarHidden(true, true);
}
}

private static void EnsureNavigationItem(NavigationBar topNavigationBar, UIViewController pageController)
{
// The Native view from the renderer may have been set to a dummy NavigationItem if we were not able to find
// a NavigationBar in the PageCreated method. If that is the case, set it to the pageController's NavigationItem
var nativeItem = topNavigationBar.GetRenderer(() => (NavigationBarNavigationItemRenderer?)null)?.Native;
if (!ReferenceEquals(nativeItem, pageController.NavigationItem))
{
SetNavigationItem(topNavigationBar, pageController.NavigationItem);
}
}

/// <summary>
/// When a page <see cref="UIViewController" /> did disappear, disconnects the <see cref="NavigationBar" /> from the navigation controller.
/// </summary>
Expand Down
5 changes: 3 additions & 2 deletions src/Uno.Toolkit.UI/Controls/NavigationBar/Renderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,13 @@ internal static class RendererHelper
{
private static readonly WeakAttachedDictionary<DependencyObject, Type> _renderers = new WeakAttachedDictionary<DependencyObject, Type>();

public static TRenderer GetRenderer<TElement, TRenderer>(this TElement element, Func<TRenderer> rendererFactory)
public static TRenderer GetRenderer<TElement, TRenderer>(this TElement element, Func<TRenderer>? rendererFactory)
where TElement : DependencyObject
{
return _renderers.GetValue(element, typeof(TRenderer), rendererFactory);
}
public static TRenderer ResetRenderer<TElement, TRenderer>(this TElement element, Func<TRenderer> rendererFactory)

public static TRenderer ResetRenderer<TElement, TRenderer>(this TElement element, Func<TRenderer>? rendererFactory)
where TElement : DependencyObject
{
return _renderers.GetValue(element, typeof(TRenderer), rendererFactory);
Expand Down

0 comments on commit 403194e

Please sign in to comment.