[iOS] Shell flyout header on iOS now is dynamic based on the header content (#6109...
authorShane Neuville <shane94@hotmail.com>
Thu, 9 May 2019 21:16:52 +0000 (14:16 -0700)
committerRui Marinho <me@ruimarinho.net>
Thu, 9 May 2019 21:16:51 +0000 (14:16 -0700)
* fix the flyout header to size based on content opposed to a fixed height

* update storeshell

* added height slider into storeshell

* resize header if underlying measure changes

* unsubscribe

Xamarin.Forms.ControlGallery.iOS/Resources/xamarinstore.jpg [new file with mode: 0644]
Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj
Xamarin.Forms.Controls/XamStore/Controls/FlyoutHeader.xaml
Xamarin.Forms.Controls/XamStore/Views/StorePages.cs
Xamarin.Forms.Platform.iOS/Renderers/ShellFlyoutContentRenderer.cs
Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewController.cs
Xamarin.Forms.Platform.iOS/Renderers/UIContainerView.cs

diff --git a/Xamarin.Forms.ControlGallery.iOS/Resources/xamarinstore.jpg b/Xamarin.Forms.ControlGallery.iOS/Resources/xamarinstore.jpg
new file mode 100644 (file)
index 0000000..524dff7
Binary files /dev/null and b/Xamarin.Forms.ControlGallery.iOS/Resources/xamarinstore.jpg differ
index f10e9ea..08e9a97 100644 (file)
     <BundleResource Include="Resources\button_add%402x.png" />
     <BundleResource Include="Resources\icon_search%402x.png" />
     <BundleResource Include="Resources\icon_bookmark%402x.png" />
+    <BundleResource Include="Resources\xamarinstore.jpg" />
   </ItemGroup>
   <ItemGroup>
     <Reference Include="Microsoft.CSharp" />
index 84f8926..e50f853 100644 (file)
@@ -1,8 +1,8 @@
-<?xml version="1.0" encoding="UTF-8"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
                                xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                                x:Class="Xamarin.Forms.Controls.XamStore.FlyoutHeader"
-                               HeightRequest="200">
+                               HeightRequest="143">
        <ContentView.Content>
                <Grid BackgroundColor="Black">
                        <Image Aspect="AspectFill" Source="xamarinstore.jpg" Opacity="0.6" />
index 601cb5d..8b10af9 100644 (file)
@@ -243,6 +243,24 @@ namespace Xamarin.Forms.Controls.XamStore
                                async () => await Shell.Current.GoToAsync(navEntry.Text, true)),
                        2, 16);
 
+                       var headerWidth = new Slider
+                       {
+                               Minimum = 0,
+                               Maximum = 400,
+                               Value = (Shell.Current.FlyoutHeader as VisualElement)?.HeightRequest ?? 0
+                       };
+                       headerWidth.ValueChanged += (_, e) =>
+                       {
+                               if (Shell.Current.FlyoutHeader is VisualElement ve)
+                                       ve.HeightRequest = e.NewValue;
+                       };
+                       grid.Children.Add(new Label
+                       {
+                               Text = "fly Header",
+                               VerticalOptions = LayoutOptions.CenterAndExpand
+                       }, 0, 17);
+                       grid.Children.Add(headerWidth, 1, 17);
+
                        Content = new ScrollView { Content = grid };
 
                        //var listView = new ListView();
index 0b8ff58..92fd6e1 100644 (file)
@@ -9,7 +9,7 @@ namespace Xamarin.Forms.Platform.iOS
        {
                UIVisualEffectView _blurView;
                readonly IShellContext _shellContext;
-               UIView _headerView;
+               UIContainerView _headerView;
                ShellTableViewController _tableViewController;
 
                public event EventHandler WillAppear;
index a97c67c..cf571c5 100644 (file)
@@ -8,29 +8,28 @@ namespace Xamarin.Forms.Platform.iOS
        public class ShellTableViewController : UITableViewController
        {
                readonly IShellContext _context;
-               readonly UIView _headerView;
+               readonly UIContainerView _headerView;
                readonly ShellTableViewSource _source;
-               double _headerMax = 200;
-               double _headerMin = 44;
+               double _headerMin = 56;
                double _headerOffset = 0;
                double _headerSize;
 
-               public ShellTableViewController(IShellContext context, UIView headerView, Action<Element> onElementSelected)
+               public ShellTableViewController(IShellContext context, UIContainerView headerView, Action<Element> onElementSelected)
                {
-                       if (headerView == null)
-                       {
-                               _headerMax = 20;
-                               _headerMin = 0;
-                       }
-
-                       _headerSize = _headerMax;
                        _context = context;
                        _headerView = headerView;
                        _source = new ShellTableViewSource(context, onElementSelected);
                        _source.ScrolledEvent += OnScrolled;
-
+                       _headerView.HeaderSizeChanged += OnHeaderSizeChanged;
                        ((IShellController)_context.Shell).StructureChanged += OnStructureChanged;
                }
+               
+               void OnHeaderSizeChanged(object sender, EventArgs e)
+               {
+                       _headerSize = HeaderMax;
+                       TableView.ContentInset = new UIEdgeInsets((nfloat)HeaderMax + SafeAreaOffset, 0, 0, 0);
+                       LayoutParallax();
+               }
 
                void OnStructureChanged(object sender, EventArgs e)
                {
@@ -40,16 +39,20 @@ namespace Xamarin.Forms.Platform.iOS
 
                public void LayoutParallax()
                {
+                       if (TableView?.Superview == null)
+                               return;
+
                        var parent = TableView.Superview;
                        TableView.Frame = parent.Bounds.Inset(0, SafeAreaOffset);
                        if (_headerView != null)
                        {
                                _headerView.Frame = new CGRect(0, _headerOffset + SafeAreaOffset, parent.Frame.Width, _headerSize);
 
-                               if (_headerOffset < 0 && _headerSize + _headerOffset >= 0)
+                               var headerHeight = Math.Max(_headerMin, _headerSize + _headerOffset);
+                               if (_headerOffset < 0)
                                {
                                        CAShapeLayer shapeLayer = new CAShapeLayer();
-                                       CGRect rect = new CGRect(0, _headerOffset * -1, parent.Frame.Width, _headerSize + _headerOffset);
+                                       CGRect rect = new CGRect(0, _headerOffset * -1, parent.Frame.Width, headerHeight);
                                        var path = CGPath.FromRect(rect);
                                        shapeLayer.Path = path;
                                        _headerView.Layer.Mask = shapeLayer;
@@ -60,11 +63,12 @@ namespace Xamarin.Forms.Platform.iOS
                public override void ViewDidLoad()
                {
                        base.ViewDidLoad();
+                       _headerView.MeasureIfNeeded();
 
                        TableView.SeparatorStyle = UITableViewCellSeparatorStyle.None;
                        if (Forms.IsiOS11OrNewer)
                                TableView.ContentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentBehavior.Never;
-                       TableView.ContentInset = new UIEdgeInsets((nfloat)_headerMax + SafeAreaOffset, 0, 0, 0);
+                       TableView.ContentInset = new UIEdgeInsets((nfloat)HeaderMax + SafeAreaOffset, 0, 0, 0);
                        TableView.Source = _source;
                }
 
@@ -74,11 +78,18 @@ namespace Xamarin.Forms.Platform.iOS
                        {
                                if ((_context?.Shell as IShellController) != null)
                                        ((IShellController)_context.Shell).StructureChanged -= OnStructureChanged;
+
+                               if(_source != null)
+                                       _source.ScrolledEvent -= OnScrolled;
+
+                               if(_headerView != null)
+                                       _headerView.HeaderSizeChanged -= OnHeaderSizeChanged;
                        }
 
                        base.Dispose(disposing);
                }
 
+
                void OnScrolled(object sender, UIScrollView e)
                {
                        var headerBehavior = _context.Shell.FlyoutHeaderBehavior;
@@ -87,16 +98,16 @@ namespace Xamarin.Forms.Platform.iOS
                        {
                                case FlyoutHeaderBehavior.Default:
                                case FlyoutHeaderBehavior.Fixed:
-                                       _headerSize = _headerMax;
+                                       _headerSize = HeaderMax;
                                        break;
 
                                case FlyoutHeaderBehavior.Scroll:
-                                       _headerSize = _headerMax;
-                                       _headerOffset = Math.Min(0, -(_headerMax + e.ContentOffset.Y));
+                                       _headerSize = HeaderMax;
+                                       _headerOffset = Math.Min(0, -(HeaderMax + e.ContentOffset.Y));
                                        break;
 
                                case FlyoutHeaderBehavior.CollapseOnScroll:
-                                       _headerSize = Math.Max(_headerMin, Math.Min(_headerMax, _headerMax - e.ContentOffset.Y - _headerMax));
+                                       _headerSize = Math.Max(_headerMin, Math.Min(HeaderMax, HeaderMax - e.ContentOffset.Y - HeaderMax));
                                        break;
                        }
 
@@ -104,5 +115,6 @@ namespace Xamarin.Forms.Platform.iOS
                }
 
                float SafeAreaOffset => (float)Platform.SafeAreaInsetsForWindow.Top;
+               double HeaderMax => _headerView.MeasuredHeight;
        }
 }
\ No newline at end of file
index f2529c8..62215a0 100644 (file)
@@ -1,4 +1,5 @@
-using CoreGraphics;
+using System;
+using CoreGraphics;
 using UIKit;
 
 namespace Xamarin.Forms.Platform.iOS
@@ -8,6 +9,7 @@ namespace Xamarin.Forms.Platform.iOS
                readonly View _view;
                IVisualElementRenderer _renderer;
                bool _disposed;
+               internal event EventHandler HeaderSizeChanged;
 
                public UIContainerView(View view)
                {
@@ -17,11 +19,40 @@ namespace Xamarin.Forms.Platform.iOS
                        Platform.SetRenderer(view, _renderer);
 
                        AddSubview(_renderer.NativeView);
+                       ClipsToBounds = true;
+                       view.MeasureInvalidated += OnMeasureInvalidated;
+                       MeasuredHeight = double.NaN;
+               }
+
+               internal double MeasuredHeight { get; private set; }
+
+               internal bool MeasureIfNeeded()
+               {
+                       if (double.IsNaN(MeasuredHeight))
+                       {
+                               ReMeasure();
+                               return true;
+                       }
+                       return false;
+               }
+
+               void ReMeasure()
+               {
+                       var request = _view.Measure(Frame.Width, double.PositiveInfinity, MeasureFlags.IncludeMargins);
+                       Layout.LayoutChildIntoBoundingRegion(_view, new Rectangle(0, 0, Frame.Width, request.Request.Height));
+                       MeasuredHeight = request.Request.Height;
+                       HeaderSizeChanged?.Invoke(this, EventArgs.Empty);
+               }
+
+               void OnMeasureInvalidated(object sender, System.EventArgs e)
+               {
+                       ReMeasure();
                }
 
                public override void LayoutSubviews()
                {
-                       _view.Layout(Bounds.ToRectangle());
+                       if(!MeasureIfNeeded())
+                               _view.Layout(Bounds.ToRectangle());
                }
 
                protected override void Dispose(bool disposing)