Better detect IsCollapsed state/changes on master (#8181)
authorShane Neuville <shneuvil@microsoft.com>
Tue, 29 Oct 2019 00:42:11 +0000 (18:42 -0600)
committerSamantha Houts <samhouts@users.noreply.github.com>
Tue, 29 Oct 2019 00:42:11 +0000 (17:42 -0700)
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7556.cs
Xamarin.Forms.Platform.iOS/Renderers/TabletMasterDetailRenderer.cs

index b25deba..5d82798 100644 (file)
@@ -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() 
                {
index 82646c5..05b504c 100644 (file)
@@ -16,23 +16,98 @@ namespace Xamarin.Forms.Platform.iOS
 
        internal class EventedViewController : ChildViewController
        {
-               public override void ViewWillDisappear(bool animated)
+               MasterView _masterView;
+
+               event EventHandler _didAppear;
+               event EventHandler _willDisappear;
+
+               public EventedViewController()
                {
-                       base.ViewWillDisappear(animated);
+                       _masterView = new MasterView();
+               }
+
 
-                       WillDisappear?.Invoke(this, EventArgs.Empty);
+               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 override void ViewWillDisappear(bool animated)
+               {
+                       base.ViewWillDisappear(animated);
+                       _willDisappear?.Invoke(this, EventArgs.Empty);
                }
 
-               public event EventHandler DidAppear;
+               public override void ViewDidDisappear(bool animated)
+               {
+                       base.ViewDidDisappear(animated);
+                       _willDisappear?.Invoke(this, EventArgs.Empty);
+               }
 
-               public event EventHandler WillDisappear;
+               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();