diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7556.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7556.cs index b25debaf3..5d8279893 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7556.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7556.cs @@ -49,7 +49,8 @@ namespace Xamarin.Forms.Controls.Issues public DetailsPage(MasterDetailPage masterDetailPage) { MDP = masterDetailPage; - lblThings = new Label(); + lblThings = new Label() { HorizontalTextAlignment = TextAlignment.Center, AutomationId = "CurrentMasterBehavior" }; + Content = new StackLayout() { Children = @@ -58,7 +59,8 @@ namespace Xamarin.Forms.Controls.Issues new Button() { Text = "Click to rotate through MasterBehavior settings and test each one", - Command = new Command(OnChangeMasterBehavior) + Command = new Command(OnChangeMasterBehavior), + AutomationId = "ChangeMasterBehavior" }, new Button() { @@ -75,7 +77,8 @@ namespace Xamarin.Forms.Controls.Issues } }); }) - } + }, + new Label(){ HorizontalTextAlignment = TextAlignment.Center, Text = "Close Master" } } }; @@ -113,6 +116,39 @@ namespace Xamarin.Forms.Controls.Issues RunningApp.WaitForElement("Master Visible"); } + [Test] + public void SplitOnLandscapeFailsToDetectClose() + { + if (!RunningApp.IsTablet()) + return; + + while(RunningApp.WaitForElement("CurrentMasterBehavior")[0].ReadText() != MasterBehavior.SplitOnLandscape.ToString()) + { + RunningApp.Tap("ChangeMasterBehavior"); + + if(RunningApp.Query("Master Visible").Length > 0) + RunningApp.Tap("Close Master"); + } + + RunningApp.Tap("Master"); + RunningApp.WaitForElement("Master Visible"); + RunningApp.Tap("Close Master"); + + RunningApp.SetOrientationLandscape(); + RunningApp.SetOrientationPortrait(); + RunningApp.SetOrientationLandscape(); + RunningApp.SetOrientationPortrait(); + + if (RunningApp.Query("Master Visible").Length > 0) + RunningApp.Tap("Close Master"); + + RunningApp.Tap("Master"); + RunningApp.WaitForElement("Master Visible"); + RunningApp.Tap("Close Master"); + RunningApp.Tap("Master"); + RunningApp.WaitForElement("Master Visible"); + } + [TearDown] public override void TearDown() { diff --git a/Xamarin.Forms.Platform.iOS/Renderers/TabletMasterDetailRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/TabletMasterDetailRenderer.cs index 82646c562..05b504cd7 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/TabletMasterDetailRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/TabletMasterDetailRenderer.cs @@ -16,23 +16,98 @@ namespace Xamarin.Forms.Platform.iOS internal class EventedViewController : ChildViewController { - public override void ViewWillDisappear(bool animated) - { - base.ViewWillDisappear(animated); + MasterView _masterView; - WillDisappear?.Invoke(this, EventArgs.Empty); + event EventHandler _didAppear; + event EventHandler _willDisappear; + + public EventedViewController() + { + _masterView = new MasterView(); + } + + + public event EventHandler DidAppear + { + add + { + _masterView.DidAppear += value; + _didAppear += value; + } + remove + { + _masterView.DidAppear -= value; + _didAppear -= value; + } + } + + public event EventHandler WillDisappear + { + add + { + _masterView.WillDisappear += value; + _willDisappear += value; + } + remove + { + _masterView.WillDisappear -= value; + _willDisappear -= value; + } } public override void ViewDidAppear(bool animated) { base.ViewDidAppear(animated); - - DidAppear?.Invoke(this, EventArgs.Empty); + _didAppear?.Invoke(this, EventArgs.Empty); } - public event EventHandler DidAppear; + public override void ViewWillDisappear(bool animated) + { + base.ViewWillDisappear(animated); + _willDisappear?.Invoke(this, EventArgs.Empty); + } - public event EventHandler WillDisappear; + public override void ViewDidDisappear(bool animated) + { + base.ViewDidDisappear(animated); + _willDisappear?.Invoke(this, EventArgs.Empty); + } + + public override void LoadView() + { + View = _masterView; + } + + public class MasterView : UIView + { + public bool IsCollapsed => Center.X <= 0; + bool _previousIsCollapsed = true; + + public event EventHandler DidAppear; + public event EventHandler WillDisappear; + + // this only gets called on iOS12 everytime it's collapsed or expanded + // I haven't found an override on iOS13 that gets called but it doesn't seem + // to matter because the DidAppear and WillDisappear seem more consistent on iOS 13 + public override void LayoutSubviews() + { + base.LayoutSubviews(); + UpdateCollapsedSetting(); + } + + void UpdateCollapsedSetting() + { + if (_previousIsCollapsed != IsCollapsed) + { + _previousIsCollapsed = IsCollapsed; + + if (IsCollapsed) + WillDisappear?.Invoke(this, EventArgs.Empty); + else + DidAppear?.Invoke(this, EventArgs.Empty); + } + } + } } public class TabletMasterDetailRenderer : UISplitViewController, IVisualElementRenderer, IEffectControlProvider @@ -45,11 +120,14 @@ namespace Xamarin.Forms.Platform.iOS nfloat _masterWidth = 0; EventedViewController _masterController; MasterDetailPage _masterDetailPage; - bool _masterVisible; VisualElementTracker _tracker; + CGSize _previousSize = CGSize.Empty; + CGSize _previousViewDidLayoutSize = CGSize.Empty; + UISplitViewControllerDisplayMode _previousDisplayMode = UISplitViewControllerDisplayMode.Automatic; Page PageController => Element as Page; Element ElementController => Element as Element; + bool IsMasterVisible => !(_masterController?.View as EventedViewController.MasterView).IsCollapsed; protected MasterDetailPage MasterDetailPage => _masterDetailPage ?? (_masterDetailPage = (MasterDetailPage)Element); @@ -91,7 +169,7 @@ namespace Xamarin.Forms.Platform.iOS if (_masterController != null) { - _masterController.DidAppear -= MasterControllerWillAppear; + _masterController.DidAppear -= MasterControllerDidAppear; _masterController.WillDisappear -= MasterControllerWillDisappear; } @@ -127,7 +205,7 @@ namespace Xamarin.Forms.Platform.iOS UpdateControllers(); - _masterController.DidAppear += MasterControllerWillAppear; + _masterController.DidAppear += MasterControllerDidAppear; _masterController.WillDisappear += MasterControllerWillDisappear; PresentsWithGesture = MasterDetailPage.IsGestureEnabled; @@ -162,6 +240,7 @@ namespace Xamarin.Forms.Platform.iOS PageController?.SendDisappearing(); } + public override void ViewDidLayoutSubviews() { base.ViewDidLayoutSubviews(); @@ -203,6 +282,28 @@ namespace Xamarin.Forms.Platform.iOS if (!detailsBounds.IsEmpty) MasterDetailPage.DetailBounds = new Rectangle(0, 0, detailsBounds.Width, detailsBounds.Height); } + + if (_previousViewDidLayoutSize == CGSize.Empty) + _previousViewDidLayoutSize = View.Bounds.Size; + + // Is this being called from a rotation + if (_previousViewDidLayoutSize != View.Bounds.Size) + { + _previousViewDidLayoutSize = View.Bounds.Size; + + // make sure IsPresented matches state of Master View + if (MasterDetailPage.CanChangeIsPresented && MasterDetailPage.IsPresented != IsMasterVisible) + ElementController.SetValueFromRenderer(MasterDetailPage.IsPresentedProperty, IsMasterVisible); + } + + if(_previousDisplayMode != PreferredDisplayMode) + { + _previousDisplayMode = PreferredDisplayMode; + + // make sure IsPresented matches state of Master View + if (MasterDetailPage.CanChangeIsPresented && MasterDetailPage.IsPresented != IsMasterVisible) + ElementController.SetValueFromRenderer(MasterDetailPage.IsPresentedProperty, IsMasterVisible); + } } public override void ViewDidLoad() @@ -224,6 +325,8 @@ namespace Xamarin.Forms.Platform.iOS return; bool isPortrait = newBounds.Height > newBounds.Width; + var previous = PreferredDisplayMode; + switch (masterDetailPage.MasterBehavior) { case MasterBehavior.Split: @@ -243,18 +346,18 @@ namespace Xamarin.Forms.Platform.iOS break; } + if (previous == PreferredDisplayMode) + return; + if (!MasterDetailPage.ShouldShowSplitMode) MasterDetailPage.CanChangeIsPresented = true; MasterDetailPage.UpdateMasterBehavior(); - - if(MasterDetailPage.CanChangeIsPresented && !MasterDetailPage.ShouldShowSplitMode) - ElementController.SetValueFromRenderer(MasterDetailPage.IsPresentedProperty, false); } public override void ViewWillDisappear(bool animated) { - if (_masterVisible && !MasterDetailPage.ShouldShowSplitMode) + if (IsMasterVisible && !MasterDetailPage.ShouldShowSplitMode) PerformButtonSelector(); base.ViewWillDisappear(animated); @@ -271,7 +374,7 @@ namespace Xamarin.Forms.Platform.iOS // I tested this code on iOS9+ and it's never called if (!Forms.IsiOS9OrNewer) { - if (!MasterDetailPage.ShouldShowSplitMode && _masterVisible) + if (!MasterDetailPage.ShouldShowSplitMode && IsMasterVisible) { MasterDetailPage.CanChangeIsPresented = true; PreferredDisplayMode = UISplitViewControllerDisplayMode.PrimaryHidden; @@ -362,20 +465,23 @@ namespace Xamarin.Forms.Platform.iOS public override void ViewWillTransitionToSize(CGSize toSize, IUIViewControllerTransitionCoordinator coordinator) { base.ViewWillTransitionToSize(toSize, coordinator); - UpdateMasterBehavior(toSize); + + if (_previousSize != toSize) + { + _previousSize = toSize; + UpdateMasterBehavior(toSize); + } } - void MasterControllerWillAppear(object sender, EventArgs e) + void MasterControllerDidAppear(object sender, EventArgs e) { - _masterVisible = true; - if (MasterDetailPage.CanChangeIsPresented) + if (MasterDetailPage.CanChangeIsPresented && IsMasterVisible) ElementController.SetValueFromRenderer(MasterDetailPage.IsPresentedProperty, true); } void MasterControllerWillDisappear(object sender, EventArgs e) { - _masterVisible = false; - if (MasterDetailPage.CanChangeIsPresented) + if (MasterDetailPage.CanChangeIsPresented && !IsMasterVisible) ElementController.SetValueFromRenderer(MasterDetailPage.IsPresentedProperty, false); } @@ -386,7 +492,7 @@ namespace Xamarin.Forms.Platform.iOS void ToggleMaster() { - if (_masterVisible == MasterDetailPage.IsPresented || MasterDetailPage.ShouldShowSplitMode) + if (IsMasterVisible == MasterDetailPage.IsPresented || MasterDetailPage.ShouldShowSplitMode) return; PerformButtonSelector();