Skip to content

Instantly share code, notes, and snippets.

@raver99
Created February 4, 2020 14:22
Show Gist options
  • Save raver99/fcb36f98d1791901d5741df5d6c91c74 to your computer and use it in GitHub Desktop.
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)
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