From 1d0578c90252b02bf02cefafb0e8d2acc5b0dbd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez?= Date: Fri, 21 May 2021 09:26:36 +0200 Subject: [PATCH] [iOS] Fix incorrect refresh indicador position using RefreshView with CollectionView Header (#13773) * Added repro sample * Fix the issue * Update issue description * Changes to use another approach to detect if the CollectionView has header or not --- .../Issue8282.xaml | 62 +++++++++ .../Issue8282.xaml.cs | 123 ++++++++++++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 4 + .../StructuredItemsViewController.cs | 15 ++- .../Renderers/RefreshViewRenderer.cs | 38 +++++- 5 files changed, 232 insertions(+), 10 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8282.xaml create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8282.xaml.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8282.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8282.xaml new file mode 100644 index 00000000000..19c87b58026 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8282.xaml @@ -0,0 +1,62 @@ + + + + + + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8282.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8282.xaml.cs new file mode 100644 index 00000000000..20390f2d86c --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8282.xaml.cs @@ -0,0 +1,123 @@ +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Threading.Tasks; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; +using Xamarin.Forms.Xaml; + +#if UITEST +using Xamarin.UITest; +using Xamarin.UITest.Queries; +using NUnit.Framework; +using Xamarin.Forms.Core.UITests; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [NUnit.Framework.Category(UITestCategories.RefreshView)] +#endif +#if APP + [XamlCompilation(XamlCompilationOptions.Compile)] +#endif + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 8282, "[Bug] [iOS] RefreshView draws behind CollectionView Header", PlatformAffected.iOS)] + public partial class Issue8282 : TestContentPage + { + public Issue8282() + { +#if APP + Title = "Issue 8282"; + InitializeComponent(); + BindingContext = new Issue8282ViewModel(); +#endif + } + + protected override void Init() + { + + } + } + + [Preserve(AllMembers = true)] + public class Issue8282Model : INotifyPropertyChanged + { + private int _position; + + public int Position + { + get + { + return _position; + } + set + { + _position = value; + + OnPropertyChanged("Position"); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnPropertyChanged(string name) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } + } + + [Preserve(AllMembers = true)] + public class Issue8282ViewModel : INotifyPropertyChanged + { + bool _isRefreshing; + + public Issue8282ViewModel() + { + PopulateItems(); + + RefreshCommand = new Command(async () => + { + IsRefreshing = true; + + await Task.Delay(2000); + PopulateItems(); + + IsRefreshing = false; + }); + } + + public ObservableCollection Items { get; set; } = new ObservableCollection(); + + public bool IsRefreshing + { + get + { + return _isRefreshing; + } + set + { + _isRefreshing = value; + + OnPropertyChanged("IsRefreshing"); + } + } + + + public Command RefreshCommand { get; set; } + + void PopulateItems() + { + var count = Items.Count; + + for (var i = count; i < count + 10; i++) + Items.Add(new Issue8282Model() { Position = i }); + } + + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnPropertyChanged(string name) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index c1b31a23f46..3105424e3f4 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -1717,6 +1717,7 @@ + @@ -2129,6 +2130,9 @@ MSBuild:UpdateDesignTimeXaml + + MSBuild:UpdateDesignTimeXaml + MSBuild:UpdateDesignTimeXaml diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/StructuredItemsViewController.cs b/Xamarin.Forms.Platform.iOS/CollectionView/StructuredItemsViewController.cs index dd1ec0869c8..91589fe6700 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/StructuredItemsViewController.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/StructuredItemsViewController.cs @@ -4,12 +4,15 @@ namespace Xamarin.Forms.Platform.iOS { - public class StructuredItemsViewController : ItemsViewController - where TItemsView : StructuredItemsView + public class ItemsViewTags { public const int HeaderTag = 111; public const int FooterTag = 222; + } + public class StructuredItemsViewController : ItemsViewController + where TItemsView : StructuredItemsView + { bool _disposed; UIView _headerUIView; @@ -58,13 +61,13 @@ protected override void Dispose(bool disposing) protected override CGRect DetermineEmptyViewFrame() { nfloat headerHeight = 0; - var headerView = CollectionView.ViewWithTag(HeaderTag); + var headerView = CollectionView.ViewWithTag(ItemsViewTags.HeaderTag); if (headerView != null) headerHeight = headerView.Frame.Height; nfloat footerHeight = 0; - var footerView = CollectionView.ViewWithTag(FooterTag); + var footerView = CollectionView.ViewWithTag(ItemsViewTags.FooterTag); if (footerView != null) footerHeight = footerView.Frame.Height; @@ -100,14 +103,14 @@ _footerUIView.Frame.Y < (emptyView?.Frame.Y + emptyView?.Frame.Height)) internal void UpdateFooterView() { - UpdateSubview(ItemsView?.Footer, ItemsView?.FooterTemplate, FooterTag, + UpdateSubview(ItemsView?.Footer, ItemsView?.FooterTemplate, ItemsViewTags.FooterTag, ref _footerUIView, ref _footerViewFormsElement); UpdateHeaderFooterPosition(); } internal void UpdateHeaderView() { - UpdateSubview(ItemsView?.Header, ItemsView?.HeaderTemplate, HeaderTag, + UpdateSubview(ItemsView?.Header, ItemsView?.HeaderTemplate, ItemsViewTags.HeaderTag, ref _headerUIView, ref _headerViewFormsElement); UpdateHeaderFooterPosition(); } diff --git a/Xamarin.Forms.Platform.iOS/Renderers/RefreshViewRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/RefreshViewRenderer.cs index 4525bf771f1..5ec1c835326 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/RefreshViewRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/RefreshViewRenderer.cs @@ -187,10 +187,24 @@ bool TryInsertRefresh(UIView view, int index = 0) if (view is UIScrollView scrollView) { - if (CanUseRefreshControlProperty()) - scrollView.RefreshControl = _refreshControl; - else - scrollView.InsertSubview(_refreshControl, index); + bool addedRefreshControl = false; + + if (scrollView is UICollectionView collectionView) + { + if (HasCollectionViewHeader(collectionView)) + { + collectionView.BackgroundView = _refreshControl; + addedRefreshControl = true; + } + } + + if (!addedRefreshControl) + { + if (CanUseRefreshControlProperty()) + scrollView.RefreshControl = _refreshControl; + else + scrollView.InsertSubview(_refreshControl, index); + } scrollView.AlwaysBounceVertical = true; @@ -259,6 +273,22 @@ bool CanUseRefreshControlProperty() return Forms.IsiOS10OrNewer && !_usingLargeTitles; } + bool HasCollectionViewHeader(UICollectionView collectionView) + { + bool hasHeader = false; + + foreach (var children in collectionView.Subviews) + { + if (children.Tag == ItemsViewTags.HeaderTag) + { + hasHeader = true; + break; + } + } + + return hasHeader; + } + void OnRefresh(object sender, EventArgs e) { IsRefreshing = true;