Created
February 4, 2020 14:22
-
-
Save raver99/fcb36f98d1791901d5741df5d6c91c74 to your computer and use it in GitHub Desktop.
Workaround for Xamarin CollectionView + Refresh View + Header Issue (https://github.com/xamarin/Xamarin.Forms/issues/8282)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
using System.Linq; | |
using MyProject.iOS.Renderers; | |
using UIKit; | |
using Xamarin.Forms; | |
using Xamarin.Forms.Platform.iOS; | |
using Xamarin.Forms.PlatformConfiguration.iOSSpecific; | |
using VisualElement = Xamarin.Forms.VisualElement; | |
[assembly: ExportRenderer(typeof(RefreshView), typeof(CustomRefreshViewRenderer))] | |
namespace MyProject.iOS.Renderers | |
{ | |
public static class Extensions | |
{ | |
internal static T FindParentOfType<T>(this VisualElement element) | |
{ | |
var navPage = element.GetParentsPath() | |
.OfType<T>() | |
.FirstOrDefault(); | |
return navPage; | |
} | |
internal static IEnumerable<Element> GetParentsPath(this VisualElement self) | |
{ | |
Element current = self; | |
while (!Extensions.IsApplicationOrNull(current.RealParent)) | |
{ | |
current = current.RealParent; | |
yield return current; | |
} | |
} | |
public static bool IsApplicationOrNull(Element element) | |
{ | |
return element == null || element is Application; | |
} | |
} | |
public class CustomRefreshViewRenderer : ViewRenderer<RefreshView, UIView>, IEffectControlProvider | |
{ | |
bool _isDisposed; | |
bool _isRefreshing; | |
bool _usingLargeTitles; | |
nfloat _originalY; | |
nfloat _refreshControlHeight; | |
UIView _refreshControlParent; | |
UIRefreshControl _refreshControl; | |
bool IsiOs10OrNewer | |
{ | |
get | |
{ | |
return UIDevice.CurrentDevice.CheckSystemVersion(10, 0); | |
} | |
} | |
public bool IsRefreshing | |
{ | |
get { return _isRefreshing; } | |
set | |
{ | |
_isRefreshing = value; | |
if (Element != null && Element.IsRefreshing != _isRefreshing) | |
Element.SetValueFromRenderer(RefreshView.IsRefreshingProperty, _isRefreshing); | |
if (_isRefreshing != _refreshControl.Refreshing) | |
{ | |
if (_isRefreshing) | |
_refreshControl.BeginRefreshing(); | |
else | |
_refreshControl.EndRefreshing(); | |
TryOffsetRefresh(this, IsRefreshing); | |
} | |
} | |
} | |
protected override void OnElementChanged(ElementChangedEventArgs<RefreshView> e) | |
{ | |
base.OnElementChanged(e); | |
if (e.OldElement != null || Element == null) | |
return; | |
if (e.NewElement != null) | |
{ | |
if (Control == null) | |
{ | |
if (IsiOs10OrNewer) | |
{ | |
var parentNav = e.NewElement.FindParentOfType<Xamarin.Forms.NavigationPage>(); | |
_usingLargeTitles = parentNav != null && parentNav.OnThisPlatform().PrefersLargeTitles(); | |
} | |
_refreshControl = new UIRefreshControl(); | |
_refreshControl.ValueChanged += OnRefresh; | |
_refreshControlParent = this; | |
} | |
} | |
UpdateColors(); | |
UpdateIsRefreshing(); | |
UpdateIsEnabled(); | |
} | |
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) | |
{ | |
base.OnElementPropertyChanged(sender, e); | |
if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName) | |
UpdateIsEnabled(); | |
else if (e.PropertyName == RefreshView.IsRefreshingProperty.PropertyName) | |
UpdateIsRefreshing(); | |
else if ((e.PropertyName == RefreshView.RefreshColorProperty.PropertyName) || | |
(e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)) | |
UpdateColors(); | |
} | |
protected override void SetBackgroundColor(Color color) | |
{ | |
if (_refreshControl == null) | |
return; | |
_refreshControl.BackgroundColor = color != Color.Default ? color.ToUIColor() : null; | |
} | |
protected override void Dispose(bool disposing) | |
{ | |
if (_isDisposed) | |
return; | |
if (disposing && Control != null) | |
{ | |
_refreshControl.ValueChanged -= OnRefresh; | |
_refreshControl.Dispose(); | |
_refreshControl = null; | |
_refreshControlParent = null; | |
} | |
_isDisposed = true; | |
base.Dispose(disposing); | |
} | |
bool TryOffsetRefresh(UIView view, bool refreshing) | |
{ | |
if (view is UIScrollView scrollView) | |
{ | |
if (scrollView.ContentOffset.Y < 0) | |
return true; | |
if (refreshing) | |
scrollView.SetContentOffset(new CoreGraphics.CGPoint(0, _originalY - _refreshControlHeight), true); | |
else | |
scrollView.SetContentOffset(new CoreGraphics.CGPoint(0, _originalY), true); | |
return true; | |
} | |
if (view is WkWebViewRenderer) | |
{ | |
return true; | |
} | |
if (view.Subviews == null) | |
return false; | |
for (int i = 0; i < view.Subviews.Length; i++) | |
{ | |
var control = view.Subviews[i]; | |
if (TryOffsetRefresh(control, refreshing)) | |
return true; | |
} | |
return false; | |
} | |
bool TryInsertRefresh(UIView view, int index = 0) | |
{ | |
_refreshControlParent = view; | |
if (view is UIScrollView scrollView) | |
{ | |
if (CanUseRefreshControlProperty()) | |
scrollView.RefreshControl = _refreshControl; | |
else | |
scrollView.InsertSubview(_refreshControl, index); | |
scrollView.AlwaysBounceVertical = true; | |
_originalY = scrollView.ContentOffset.Y; | |
_refreshControlHeight = _refreshControl.Frame.Size.Height; | |
//todo: check if fixed | |
//added to fix https://github.com/xamarin/Xamarin.Forms/issues/8282 | |
_refreshControl.Bounds = new CoreGraphics.CGRect(new CoreGraphics.CGPoint(0, -scrollView.ContentOffset.Y), _refreshControl.Bounds.Size); | |
return true; | |
} | |
if (view is WkWebViewRenderer webView) | |
{ | |
webView.ScrollView.InsertSubview(_refreshControl, index); | |
return true; | |
} | |
if (view.Subviews == null) | |
return false; | |
for (int i = 0; i < view.Subviews.Length; i++) | |
{ | |
var control = view.Subviews[i]; | |
if (TryInsertRefresh(control, i)) | |
return true; | |
} | |
return false; | |
} | |
void UpdateColors() | |
{ | |
if (Element == null || _refreshControl == null) | |
return; | |
if (Element.RefreshColor != Color.Default) | |
_refreshControl.TintColor = Element.RefreshColor.ToUIColor(); | |
SetBackgroundColor(Element.BackgroundColor); | |
} | |
void UpdateIsRefreshing() | |
{ | |
IsRefreshing = Element.IsRefreshing; | |
} | |
void UpdateIsEnabled() | |
{ | |
if (Element.IsEnabled) | |
TryInsertRefresh(_refreshControlParent); | |
else | |
{ | |
if (_refreshControl.Superview != null) | |
_refreshControl.RemoveFromSuperview(); | |
} | |
} | |
bool CanUseRefreshControlProperty() | |
{ | |
return IsiOs10OrNewer && !_usingLargeTitles; | |
} | |
void OnRefresh(object sender, EventArgs e) | |
{ | |
IsRefreshing = true; | |
} | |
void IEffectControlProvider.RegisterEffect(Effect effect) | |
{ | |
VisualElementRenderer<VisualElement>.RegisterEffect(effect, this, NativeView); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment