Fix iOS so if you remove more than one page it's able to remove them successfully (#14383)

* Fix iOS Shell when removing multiple views

* - ui tests and fix navigate args

* - improve logic

* Update ShellSection.cs

* - update UI Tests
This commit is contained in:
Shane Neuville 2021-07-05 09:42:25 -07:00 коммит произвёл GitHub
Родитель 0cc0a37a5d
Коммит 86db84805e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
15 изменённых файлов: 473 добавлений и 192 удалений

Просмотреть файл

@ -390,7 +390,7 @@
</PackageReference>
<PackageReference Include="Xam.Plugin.DeviceInfo" Version="3.0.2" />
<PackageReference Include="Xamarin.Insights" Version="1.12.3" />
<PackageReference Include="Xamarin.TestCloud.Agent" Version="0.22.1" />
<PackageReference Include="Xamarin.TestCloud.Agent" Version="0.22.2" />
</ItemGroup>
<ItemGroup>
<InterfaceDefinition Include="Resources\LaunchScreen.storyboard" />

Просмотреть файл

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
#if UITEST
using Xamarin.UITest;
using NUnit.Framework;
using Xamarin.Forms.Core.UITests;
#endif
namespace Xamarin.Forms.Controls.Issues
{
[Preserve(AllMembers = true)]
[Issue(IssueTracker.Github, 13916, "[iOS] iOS Application crashes on Back press when navigated to using GoToAsync with \"//\" or \"///\" route if 2 or more things are removed from the navigation stack",
PlatformAffected.iOS)]
#if UITEST
[NUnit.Framework.Category(Core.UITests.UITestCategories.Github5000)]
[NUnit.Framework.Category(UITestCategories.Shell)]
#endif
public class Issue13916 : TestShell
{
static int pageCount = 1;
protected override void Init()
{
Routing.RegisterRoute(nameof(Issue13916SuccessPage), typeof(Issue13916SuccessPage));
AddFlyoutItem(CreateContentPage(), "Push Me");
}
public class Issue13916SuccessPage : ContentPage
{
public Issue13916SuccessPage()
{
StackLayout layout = new StackLayout();
Label label = new Label()
{
Text = "Success",
AutomationId = "Success"
};
layout.Children.Add(label);
Content = layout;
}
}
ContentPage CreateContentPage()
{
StackLayout layout = new StackLayout();
Button button = new Button()
{
Text = "Click Me",
AutomationId = $"ClickMe{pageCount}",
Command = new Command(async () =>
{
if (Navigation.NavigationStack.Count >= 3)
{
await GoToAsync($"../../{nameof(Issue13916SuccessPage)}");
}
else
{
await Navigation.PushAsync(CreateContentPage());
}
})
};
pageCount++;
layout.Children.Add(button);
return new ContentPage()
{
Content = layout
};
}
#if UITEST
[Test]
public void RemovingMoreThanOneInnerPageAndThenPushingAPageCrashes()
{
RunningApp.Tap("ClickMe1");
RunningApp.Tap("ClickMe2");
RunningApp.Tap("ClickMe3");
RunningApp.WaitForElement("Success");
}
#endif
}
}

Просмотреть файл

@ -21,6 +21,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Issue13126.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue13126_2.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue13551.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue13916.cs" />
<Compile Include="$(MSBuildThisFileDirectory)RadioButtonTemplateFromStyle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ShellSearchHandlerItemSizing.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ShellWithCustomRendererDisabledAnimations.cs" />

Просмотреть файл

@ -48,7 +48,7 @@
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="Xam.Plugin.DeviceInfo" Version="3.0.2" />
<PackageReference Include="Xamarin.UITest" Version="3.0.14" />
<PackageReference Include="Xamarin.UITest" Version="3.1.0" />
<PackageReference Include="NUnit3TestAdapter">
<Version>3.17.0</Version>
</PackageReference>

Просмотреть файл

@ -0,0 +1,211 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Core.UnitTests
{
[TestFixture]
public class ShellNavigatedArgsTests : ShellTestBase
{
[TearDown]
public override void TearDown()
{
base.TearDown();
Routing.Clear();
}
[Test]
public async Task RemoveInnerPagesNavigatingArgs()
{
Routing.RegisterRoute("SecondPageView", typeof(ContentPage));
Routing.RegisterRoute("ThirdPageView", typeof(ContentPage));
Routing.RegisterRoute("FourthPage", typeof(ContentPage));
var shell = new TestShell(CreateShellItem<FlyoutItem>(shellContentRoute: "HomePageView"));
await shell.GoToAsync("//HomePageView/SecondPageView/ThirdPageView");
await shell.GoToAsync("//HomePageView/FourthPage");
shell.TestNavigatedArgs(ShellNavigationSource.Pop, "//HomePageView/SecondPageView/ThirdPageView", "//HomePageView/FourthPage");
Assert.AreEqual(3, shell.NavigatedCount);
}
[Test]
public async Task PopToRootSetsCorrectNavigationSource()
{
var shell = new TestShell(CreateShellItem());
await shell.Navigation.PushAsync(new ContentPage());
await shell.Navigation.PushAsync(new ContentPage());
await shell.Navigation.PopToRootAsync();
Assert.AreEqual(ShellNavigationSource.PopToRoot, shell.LastShellNavigatingEventArgs.Source);
await shell.Navigation.PushAsync(new ContentPage());
await shell.Navigation.PushAsync(new ContentPage());
await shell.Navigation.PopAsync();
Assert.AreEqual(ShellNavigationSource.Pop, shell.LastShellNavigatingEventArgs.Source);
await shell.Navigation.PopAsync();
Assert.AreEqual(ShellNavigationSource.PopToRoot, shell.LastShellNavigatingEventArgs.Source);
}
[Test]
public async Task PushingSetsCorrectNavigationSource()
{
var shell = new TestShell(CreateShellItem(shellItemRoute: "item1"));
shell.RegisterPage(nameof(PushingSetsCorrectNavigationSource));
await shell.GoToAsync(nameof(PushingSetsCorrectNavigationSource));
shell.TestNavigatingArgs(ShellNavigationSource.Push,
"//item1", $"{nameof(PushingSetsCorrectNavigationSource)}");
shell.TestNavigatedArgs(ShellNavigationSource.Push,
"//item1", $"//item1/{nameof(PushingSetsCorrectNavigationSource)}");
}
[Test]
public async Task ChangingShellItemSetsCorrectNavigationSource()
{
var shell = new TestShell(
CreateShellItem(shellItemRoute: "item1"),
CreateShellItem(shellItemRoute: "item2")
);
await shell.GoToAsync("//item2");
shell.TestNavigationArgs(ShellNavigationSource.ShellItemChanged,
"//item1", "//item2");
}
[Test]
public async Task ChangingShellSectionSetsCorrectNavigationSource()
{
var shell = new TestShell(
CreateShellItem(shellSectionRoute: "item1")
);
shell.Items[0].Items.Add(CreateShellSection(shellContentRoute: "item2"));
await shell.GoToAsync("//item2");
shell.TestNavigationArgs(ShellNavigationSource.ShellSectionChanged,
"//item1", "//item2");
}
[Test]
public async Task PoppingSamePageSetsCorrectNavigationSource()
{
Routing.RegisterRoute("detailspage", typeof(ContentPage));
var shell = new TestShell(CreateShellItem(shellItemRoute: "item1"));
await shell.GoToAsync("detailspage/detailspage");
await shell.Navigation.PopAsync();
shell.TestNavigatingArgs(ShellNavigationSource.Pop,
"//item1/detailspage/detailspage", $"..");
shell.TestNavigatedArgs(ShellNavigationSource.Pop,
"//item1/detailspage/detailspage", $"//item1/detailspage");
}
[Test]
public async Task ChangingShellContentSetsCorrectNavigationSource()
{
var shell = new TestShell(
CreateShellItem(shellContentRoute: "item1")
);
shell.Items[0].Items[0].Items.Add(CreateShellContent(shellContentRoute: "item2"));
await shell.GoToAsync("//item2");
shell.TestNavigationArgs(ShellNavigationSource.ShellContentChanged,
"//item1", "//item2");
}
[Test]
public async Task InsertPageSetsCorrectNavigationSource()
{
Routing.RegisterRoute("pagemiddle", typeof(ContentPage));
Routing.RegisterRoute("page", typeof(ContentPage));
var shell = new TestShell(
CreateShellItem(shellItemRoute: "item")
);
await shell.GoToAsync("//item/page");
await shell.GoToAsync("//item/pagemiddle/page");
shell.TestNavigationArgs(ShellNavigationSource.Insert,
"//item/page", "//item/pagemiddle/page");
}
[Test]
public async Task InsertPageFromINavigationSetsCorrectNavigationSource()
{
Routing.RegisterRoute("pagemiddle", typeof(ContentPage));
Routing.RegisterRoute("page", typeof(ContentPage));
var shell = new TestShell(
CreateShellItem(shellItemRoute: "item")
);
await shell.GoToAsync("//item/page");
ContentPage contentPage = new ContentPage();
Routing.SetRoute(contentPage, "pagemiddle");
shell.Navigation.InsertPageBefore(contentPage, shell.Navigation.NavigationStack.Last());
shell.TestNavigationArgs(ShellNavigationSource.Insert,
"//item/page", "//item/pagemiddle/page");
}
[Test]
public async Task RemovePageFromINavigationSetsCorrectNavigationSource()
{
Routing.RegisterRoute("pagemiddle", typeof(ContentPage));
Routing.RegisterRoute("page", typeof(ContentPage));
var shell = new TestShell(
CreateShellItem(shellItemRoute: "item")
);
await shell.GoToAsync("//item/pagemiddle/page");
shell.Navigation.RemovePage(shell.Navigation.NavigationStack[1]);
shell.TestNavigationArgs(ShellNavigationSource.Remove,
"//item/pagemiddle/page", "//item/page");
}
[Test]
public async Task RemovePageSetsCorrectNavigationSource()
{
Routing.RegisterRoute("pagemiddle", typeof(ContentPage));
Routing.RegisterRoute("page", typeof(ContentPage));
var shell = new TestShell(
CreateShellItem(shellItemRoute: "item")
);
await shell.GoToAsync("//item/pagemiddle/page");
await shell.GoToAsync("//item/page");
shell.TestNavigationArgs(ShellNavigationSource.Remove,
"//item/pagemiddle/page", "//item/page");
}
[Test]
public async Task InitialNavigatingArgs()
{
var shell = new TestShell(
CreateShellItem(shellItemRoute: "item")
);
shell.TestNavigationArgs(ShellNavigationSource.ShellItemChanged,
null, "//item");
}
}
}

Просмотреть файл

@ -488,22 +488,6 @@ namespace Xamarin.Forms.Core.UnitTests
Assert.AreEqual(3, tab.NavigationsFired.Count);
}
[Test]
public async Task PoppingSamePageSetsCorrectNavigationSource()
{
Routing.RegisterRoute("detailspage", typeof(ContentPage));
var shell = new TestShell(CreateShellItem(shellItemRoute: "item1"));
await shell.GoToAsync("detailspage/detailspage");
await shell.Navigation.PopAsync();
shell.TestNavigatingArgs(ShellNavigationSource.Pop,
"//item1/detailspage/detailspage", $"..");
shell.TestNavigatedArgs(ShellNavigationSource.Pop,
"//item1/detailspage/detailspage", $"//item1/detailspage");
}
[Test]
public async Task PoppingSetsCorrectNavigationSource()
{
@ -522,127 +506,6 @@ namespace Xamarin.Forms.Core.UnitTests
"//item1/page1/page2", $"//item1/page1");
}
[Test]
public async Task PopToRootSetsCorrectNavigationSource()
{
var shell = new TestShell(CreateShellItem());
await shell.Navigation.PushAsync(new ContentPage());
await shell.Navigation.PushAsync(new ContentPage());
await shell.Navigation.PopToRootAsync();
Assert.AreEqual(ShellNavigationSource.PopToRoot, shell.LastShellNavigatingEventArgs.Source);
await shell.Navigation.PushAsync(new ContentPage());
await shell.Navigation.PushAsync(new ContentPage());
await shell.Navigation.PopAsync();
Assert.AreEqual(ShellNavigationSource.Pop, shell.LastShellNavigatingEventArgs.Source);
await shell.Navigation.PopAsync();
Assert.AreEqual(ShellNavigationSource.PopToRoot, shell.LastShellNavigatingEventArgs.Source);
}
[Test]
public async Task PushingSetsCorrectNavigationSource()
{
var shell = new TestShell(CreateShellItem(shellItemRoute: "item1"));
shell.RegisterPage(nameof(PushingSetsCorrectNavigationSource));
await shell.GoToAsync(nameof(PushingSetsCorrectNavigationSource));
shell.TestNavigatingArgs(ShellNavigationSource.Push,
"//item1", $"{nameof(PushingSetsCorrectNavigationSource)}");
shell.TestNavigatedArgs(ShellNavigationSource.Push,
"//item1", $"//item1/{nameof(PushingSetsCorrectNavigationSource)}");
}
[Test]
public async Task ChangingShellItemSetsCorrectNavigationSource()
{
var shell = new TestShell(
CreateShellItem(shellItemRoute: "item1"),
CreateShellItem(shellItemRoute: "item2")
);
await shell.GoToAsync("//item2");
shell.TestNavigationArgs(ShellNavigationSource.ShellItemChanged,
"//item1", "//item2");
}
[Test]
public async Task ChangingShellSectionSetsCorrectNavigationSource()
{
var shell = new TestShell(
CreateShellItem(shellSectionRoute: "item1")
);
shell.Items[0].Items.Add(CreateShellSection(shellContentRoute: "item2"));
await shell.GoToAsync("//item2");
shell.TestNavigationArgs(ShellNavigationSource.ShellSectionChanged,
"//item1", "//item2");
}
[Test]
public async Task ChangingShellContentSetsCorrectNavigationSource()
{
var shell = new TestShell(
CreateShellItem(shellContentRoute: "item1")
);
shell.Items[0].Items[0].Items.Add(CreateShellContent(shellContentRoute: "item2"));
await shell.GoToAsync("//item2");
shell.TestNavigationArgs(ShellNavigationSource.ShellContentChanged,
"//item1", "//item2");
}
[Test]
public async Task InsertPageSetsCorrectNavigationSource()
{
Routing.RegisterRoute("pagemiddle", typeof(ContentPage));
Routing.RegisterRoute("page", typeof(ContentPage));
var shell = new TestShell(
CreateShellItem(shellItemRoute: "item")
);
await shell.GoToAsync("//item/page");
await shell.GoToAsync("//item/pagemiddle/page");
shell.TestNavigationArgs(ShellNavigationSource.Insert,
"//item/page", "//item/pagemiddle/page");
}
[Test]
public async Task RemovePageSetsCorrectNavigationSource()
{
Routing.RegisterRoute("pagemiddle", typeof(ContentPage));
Routing.RegisterRoute("page", typeof(ContentPage));
var shell = new TestShell(
CreateShellItem(shellItemRoute: "item")
);
await shell.GoToAsync("//item/pagemiddle/page");
await shell.GoToAsync("//item/page");
shell.TestNavigationArgs(ShellNavigationSource.Remove,
"//item/pagemiddle/page", "//item/page");
}
[Test]
public async Task InitialNavigatingArgs()
{
var shell = new TestShell(
CreateShellItem(shellItemRoute: "item")
);
shell.TestNavigationArgs(ShellNavigationSource.ShellItemChanged,
null, "//item");
}
[TestCase(true, 2)]
[TestCase(false, 2)]

Просмотреть файл

@ -374,8 +374,6 @@ namespace Xamarin.Forms.Core.UnitTests
OnNavigatingCount++;
}
public void TestNavigationArgs(ShellNavigationSource source, string from, string to)
{
TestNavigatingArgs(source, from, to);
@ -392,6 +390,7 @@ namespace Xamarin.Forms.Core.UnitTests
Assert.AreEqual(from, this.LastShellNavigatedEventArgs.Previous.Location.ToString());
Assert.AreEqual(to, this.LastShellNavigatedEventArgs.Current.Location.ToString());
Assert.AreEqual(to, this.CurrentState.Location.ToString());
}
public void TestNavigatingArgs(ShellNavigationSource source, string from, string to)

Просмотреть файл

@ -94,6 +94,7 @@
<Compile Include="ShellFlyoutItemGroupTests.cs" />
<Compile Include="ShellFlyoutItemTemplateTests.cs" />
<Compile Include="ShellModalTests.cs" />
<Compile Include="ShellNavigatedArgsTests.cs" />
<Compile Include="ShellNavigationStateTests.cs" />
<Compile Include="ShellNavigatingTests.cs" />
<Compile Include="ShellTestBase.cs" />

Просмотреть файл

@ -53,7 +53,7 @@
<PackageReference Include="Selenium.Support" Version="3.141.0" />
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
<PackageReference Include="Xam.Plugin.DeviceInfo" Version="3.0.2" />
<PackageReference Include="Xamarin.UITest" Version="3.0.14" />
<PackageReference Include="Xamarin.UITest" Version="3.1.0" />
<PackageReference Include="NUnit3TestAdapter">
<Version>3.17.0</Version>
</PackageReference>

Просмотреть файл

@ -49,7 +49,7 @@
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="Xam.Plugin.DeviceInfo" Version="3.0.2" />
<PackageReference Include="Xamarin.UITest" Version="3.0.14" />
<PackageReference Include="Xamarin.UITest" Version="3.1.0" />
<PackageReference Include="NUnit3TestAdapter">
<Version>3.17.0</Version>
</PackageReference>

Просмотреть файл

@ -53,7 +53,7 @@
<PackageReference Include="ServiceStack.Interfaces" Version="4.5.12" />
<PackageReference Include="ServiceStack.Text" Version="4.5.12" />
<PackageReference Include="Xam.Plugin.DeviceInfo" Version="3.0.2" />
<PackageReference Include="Xamarin.UITest" Version="3.0.14" />
<PackageReference Include="Xamarin.UITest" Version="3.1.0" />
<PackageReference Include="Xamarin.UITest.Desktop" Version="0.0.7" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -468,10 +468,13 @@ namespace Xamarin.Forms
var modalStack = shellSection?.Navigation?.ModalStack;
var result = ShellNavigationManager.GetNavigationState(shellItem, shellSection, shellContent, stack, modalStack);
SetValueFromRenderer(CurrentStatePropertyKey, result);
_navigationManager.HandleNavigated(new ShellNavigatedEventArgs(oldState, CurrentState, source));
if (result?.Location != oldState?.Location)
{
SetValueFromRenderer(CurrentStatePropertyKey, result);
_navigationManager.HandleNavigated(new ShellNavigatedEventArgs(oldState, CurrentState, source));
}
}
ReadOnlyCollection<ShellItem> IShellController.GetItems() =>
new ReadOnlyCollection<ShellItem>(((ShellItemCollection)Items).VisibleItemsReadOnly.ToList());

Просмотреть файл

@ -11,7 +11,7 @@ namespace Xamarin.Forms
readonly Shell _shell;
ShellNavigatedEventArgs _accumulatedEvent;
bool _accumulateNavigatedEvents;
public bool AccumulateNavigatedEvents => _accumulateNavigatedEvents;
public event EventHandler<ShellNavigatedEventArgs> Navigated;
public event EventHandler<ShellNavigatingEventArgs> Navigating;
@ -166,6 +166,7 @@ namespace Xamarin.Forms
await _shell.CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, animate, isRelativePopping);
}
(_shell as IShellController).UpdateCurrentState(source);
_accumulateNavigatedEvents = false;
// this can be null in the event that no navigation actually took place!

Просмотреть файл

@ -116,7 +116,6 @@ namespace Xamarin.Forms
await poppingCompleted;
RemovePage(page);
SendUpdateCurrentState(ShellNavigationSource.Pop);
}
async void IShellSectionController.SendPoppingToRoot(Task finishedPopping)
@ -135,8 +134,6 @@ namespace Xamarin.Forms
for (int i = 1; i < oldStack.Count; i++)
RemovePage(oldStack[i]);
SendUpdateCurrentState(ShellNavigationSource.PopToRoot);
}
[Obsolete]
@ -151,8 +148,6 @@ namespace Xamarin.Forms
_navStack.Remove(last);
RemovePage(last);
SendUpdateCurrentState(ShellNavigationSource.Pop);
}
// we want the list returned from here to remain point in time accurate
@ -178,7 +173,6 @@ namespace Xamarin.Forms
_navStack.Remove(page);
RemovePage(page);
SendUpdateCurrentState(ShellNavigationSource.Pop);
}
@ -572,7 +566,7 @@ namespace Xamarin.Forms
if (Parent?.Parent is IShellController shell)
{
shell.UpdateCurrentState(ShellNavigationSource.ShellSectionChanged);
//shell.UpdateCurrentState(ShellNavigationSource.ShellSectionChanged);
}
}
@ -724,8 +718,6 @@ namespace Xamarin.Forms
};
_navigationRequested?.Invoke(this, args);
SendUpdateCurrentState(ShellNavigationSource.Insert);
}
protected async virtual Task<Page> OnPopAsync(bool animated)
@ -762,8 +754,6 @@ namespace Xamarin.Forms
await args.Task;
RemovePage(page);
SendUpdateCurrentState(ShellNavigationSource.Pop);
return page;
}
@ -804,7 +794,6 @@ namespace Xamarin.Forms
}
PresentedPageAppearing();
SendUpdateCurrentState(ShellNavigationSource.PopToRoot);
}
protected virtual Task OnPushAsync(Page page, bool animated)
@ -834,8 +823,6 @@ namespace Xamarin.Forms
AddPage(page);
_navigationRequested?.Invoke(this, args);
SendUpdateCurrentState(ShellNavigationSource.Push);
if (args.Task == null)
return Task.FromResult(true);
return args.Task;
@ -867,8 +854,6 @@ namespace Xamarin.Forms
bool isAnimated = animated ?? (Shell.GetPresentationMode(pageToPop) & PresentationMode.NotAnimated) != PresentationMode.NotAnimated;
await Navigation.PopModalAsync(isAnimated);
}
((IShellController)Shell).UpdateCurrentState(ShellNavigationSource.ShellSectionChanged);
}
finally
{
@ -911,8 +896,6 @@ namespace Xamarin.Forms
RequestType = NavigationRequestType.Remove
};
_navigationRequested?.Invoke(this, args);
SendUpdateCurrentState(ShellNavigationSource.Remove);
}
internal bool IsVisibleSection => Parent?.Parent is Shell shell && shell.CurrentItem?.CurrentItem == this;
@ -1013,14 +996,6 @@ namespace Xamarin.Forms
void SendAppearanceChanged() => ((IShellController)Parent?.Parent)?.AppearanceChanged(this, false);
void SendUpdateCurrentState(ShellNavigationSource source)
{
if (Parent?.Parent is IShellController shell)
{
shell.UpdateCurrentState(source);
}
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
@ -1051,8 +1026,6 @@ namespace Xamarin.Forms
protected override IReadOnlyList<Page> GetNavigationStack() => _owner.GetNavigationStack();
protected override void OnInsertPageBefore(Page page, Page before) => _owner.OnInsertPageBefore(page, before);
protected override async Task<Page> OnPopAsync(bool animated)
{
if (!_owner.IsVisibleSection)
@ -1116,7 +1089,64 @@ namespace Xamarin.Forms
return _owner.Shell.NavigationManager.GoToAsync(navigationParameters);
}
protected override void OnRemovePage(Page page) => _owner.OnRemovePage(page);
protected override void OnRemovePage(Page page)
{
if (!_owner.IsVisibleSection || _owner.Shell.NavigationManager.AccumulateNavigatedEvents)
{
_owner.OnRemovePage(page);
return;
}
var stack = _owner.Stack.ToList();
stack.Remove(page);
var navigationState = GetUpdatedStatus(stack);
ShellNavigatingEventArgs shellNavigatingEventArgs = new ShellNavigatingEventArgs(
_owner.Shell.CurrentState,
navigationState.Location,
ShellNavigationSource.Remove,
false);
_owner.Shell.NavigationManager.HandleNavigating(shellNavigatingEventArgs);
_owner.OnRemovePage(page);
(_owner.Shell as IShellController).UpdateCurrentState(ShellNavigationSource.Remove);
}
protected override void OnInsertPageBefore(Page page, Page before)
{
if (!_owner.IsVisibleSection || _owner.Shell.NavigationManager.AccumulateNavigatedEvents)
{
_owner.OnInsertPageBefore(page, before);
return;
}
var stack = _owner.Stack.ToList();
var index = stack.IndexOf(before);
if (index == -1)
throw new ArgumentException("Page not found in nav stack");
stack.Insert(index, page);
var navigationState = GetUpdatedStatus(stack);
ShellNavigatingEventArgs shellNavigatingEventArgs = new ShellNavigatingEventArgs(
_owner.Shell.CurrentState,
navigationState.Location,
ShellNavigationSource.Insert,
false);
_owner.Shell.NavigationManager.HandleNavigating(shellNavigatingEventArgs);
_owner.OnInsertPageBefore(page, before);
(_owner.Shell as IShellController).UpdateCurrentState(ShellNavigationSource.Insert);
}
ShellNavigationState GetUpdatedStatus(IReadOnlyList<Page> stack)
{
var shellItem = _owner.Shell.CurrentItem;
var shellSection = shellItem?.CurrentItem;
var shellContent = shellSection?.CurrentItem;
var modalStack = shellSection?.Navigation?.ModalStack;
return ShellNavigationManager.GetNavigationState(shellItem, shellSection, shellContent, stack, modalStack);
}
}
}
}

Просмотреть файл

@ -68,19 +68,33 @@ namespace Xamarin.Forms.Platform.iOS
ShellSection _shellSection;
bool _ignorePopCall;
// When setting base.ViewControllers iOS doesn't modify the property right away.
// if you set base.ViewControllers to a new array and then retrieve base.ViewControllers
// iOS will return the previous array until the new array has been processed
// This means if you try to remove one VC and then try to remove a second VC before the first one is processed
// you'll end up re-adding back the first VC
// ViewControllers = ViewControllers.Remove(vc1)
// ViewControllers = ViewControllers.Remove(vc2)
// You've now added vc1 back because the second call to ViewControllers will still return a ViewControllers list with vc1 in it
UIViewController[] _pendingViewControllers;
public ShellSectionRenderer(IShellContext context) : base()
{
Delegate = new NavDelegate(this);
_context = context;
_context.Shell.PropertyChanged += HandleShellPropertyChanged;
_context.Shell.Navigated += OnNavigated;
_context.Shell.Navigating += OnNavigating;
}
public ShellSectionRenderer(IShellContext context, Type navigationBarType, Type toolbarType)
public ShellSectionRenderer(IShellContext context, Type navigationBarType, Type toolbarType)
: base(navigationBarType, toolbarType)
{
Delegate = new NavDelegate(this);
_context = context;
_context.Shell.PropertyChanged += HandleShellPropertyChanged;
_context.Shell.Navigated += OnNavigated;
_context.Shell.Navigating += OnNavigating;
}
[Export("navigationBar:shouldPopItem:")]
@ -89,14 +103,14 @@ namespace Xamarin.Forms.Platform.iOS
SendPop();
internal bool SendPop()
{
{
// this means the pop is already done, nothing we can do
if (ViewControllers.Length < NavigationBar.Items.Length)
if (ActiveViewControllers().Length < NavigationBar.Items.Length)
return true;
foreach(var tracker in _trackers)
foreach (var tracker in _trackers)
{
if(tracker.Value.ViewController == TopViewController)
if (tracker.Value.ViewController == TopViewController)
{
var behavior = Shell.GetBackButtonBehavior(tracker.Value.Page);
var command = behavior.GetPropertyIfSet<ICommand>(BackButtonBehavior.CommandProperty, null);
@ -104,7 +118,7 @@ namespace Xamarin.Forms.Platform.iOS
if (command != null)
{
if(command.CanExecute(commandParameter))
if (command.CanExecute(commandParameter))
{
command.Execute(commandParameter);
}
@ -202,6 +216,8 @@ namespace Xamarin.Forms.Platform.iOS
if (_context.Shell != null)
{
_context.Shell.PropertyChanged -= HandleShellPropertyChanged;
_context.Shell.Navigated -= OnNavigated;
_context.Shell.Navigating -= OnNavigating;
((IShellController)_context.Shell).RemoveAppearanceObserver(this);
}
@ -263,7 +279,7 @@ namespace Xamarin.Forms.Platform.iOS
_renderer = CreateShellSectionRootRenderer(ShellSection, _context);
PushViewController(_renderer.ViewController, false);
var stack = ShellSection.Stack;
for (int i = 1; i < stack.Count; i++)
{
@ -307,7 +323,7 @@ namespace Xamarin.Forms.Platform.iOS
_trackers[page] = tracker;
ViewControllers.Insert(ViewControllers.IndexOf(beforeRenderer.ViewController), renderer.ViewController);
InsertViewController(ActiveViewControllers().IndexOf(beforeRenderer.ViewController), renderer.ViewController);
}
protected virtual void OnNavigationRequested(object sender, NavigationRequestedEventArgs e)
@ -353,7 +369,7 @@ namespace Xamarin.Forms.Platform.iOS
public override UIViewController[] PopToRootViewController(bool animated)
{
if (!_ignorePopCall && ViewControllers.Length > 1)
if (!_ignorePopCall && ActiveViewControllers().Length > 1)
{
ProcessPopToRoot();
}
@ -432,9 +448,7 @@ namespace Xamarin.Forms.Platform.iOS
OnPopRequested(e);
}
if(ViewControllers.Contains(viewController))
ViewControllers = ViewControllers.Remove(viewController);
RemoveViewController(viewController);
DisposePage(page);
}
}
@ -461,8 +475,11 @@ namespace Xamarin.Forms.Platform.iOS
{
if (_trackers.TryGetValue(page, out var tracker))
{
if(!calledFromDispose && tracker.ViewController != null && ViewControllers.Contains(tracker.ViewController))
ViewControllers = ViewControllers.Remove(_trackers[page].ViewController);
if (!calledFromDispose && tracker.ViewController != null && ActiveViewControllers().Contains(tracker.ViewController))
{
System.Diagnostics.Debug.Write($"Disposing {_trackers[page].ViewController.GetHashCode()}");
RemoveViewController(_trackers[page].ViewController);
}
tracker.Dispose();
_trackers.Remove(page);
@ -502,6 +519,70 @@ namespace Xamarin.Forms.Platform.iOS
UpdateNavigationBarHasShadow();
}
// We only care about using pendingViewControllers when we are setting the ViewControllers array directly
// So, once navigation starts again (or ends) we can just clear the pendingViewControllers
void OnNavigating(object sender, ShellNavigatingEventArgs e)
{
_pendingViewControllers = null;
}
void OnNavigated(object sender, ShellNavigatedEventArgs e)
{
_pendingViewControllers = null;
}
// These are all just safety nets to ensure that _pendingViewControllers doesn't for some reason get out of sync
// and start causing issues. In theory we could just override ViewControllers here to make sure _pendingViewControllers
// stays in sync but I don't trust that `ViewControllers.set` is reliably called with every modification
public override UIViewController[] ViewControllers
{
get => base.ViewControllers;
set
{
if (_pendingViewControllers != null)
_pendingViewControllers = value;
base.ViewControllers = value;
}
}
public override UIViewController[] PopToViewController(UIViewController viewController, bool animated)
{
_pendingViewControllers = null;
return base.PopToViewController(viewController, animated);
}
public override void PushViewController(UIViewController viewController, bool animated)
{
_pendingViewControllers = null;
base.PushViewController(viewController, animated);
}
public override UIViewController PopViewController(bool animated)
{
_pendingViewControllers = null;
return base.PopViewController(animated);
}
UIViewController[] ActiveViewControllers() =>
_pendingViewControllers ?? base.ViewControllers;
void RemoveViewController(UIViewController viewController)
{
_pendingViewControllers = _pendingViewControllers ?? base.ViewControllers;
if (_pendingViewControllers.Contains(viewController))
_pendingViewControllers = _pendingViewControllers.Remove(viewController);
ViewControllers = _pendingViewControllers;
}
void InsertViewController(int index, UIViewController viewController)
{
_pendingViewControllers = _pendingViewControllers ?? base.ViewControllers;
_pendingViewControllers = _pendingViewControllers.Insert(index, viewController);
ViewControllers = _pendingViewControllers;
}
void PushPage(Page page, bool animated, TaskCompletionSource<bool> completionSource = null)
{
var renderer = Platform.CreateRenderer(page);
@ -576,7 +657,7 @@ namespace Xamarin.Forms.Platform.iOS
public override bool ShouldBegin(UIGestureRecognizer recognizer)
{
if (_parent.ViewControllers.Length == 1)
if ((_parent as ShellSectionRenderer).ActiveViewControllers().Length == 1)
return false;
return _shouldPop();
}
@ -619,6 +700,7 @@ namespace Xamarin.Forms.Platform.iOS
public override void WillShowViewController(UINavigationController navigationController, [Transient] UIViewController viewController, bool animated)
{
System.Diagnostics.Debug.Write($"WillShowViewController {viewController.GetHashCode()}");
var element = _self.ElementForViewController(viewController);
bool navBarVisible;