自定义下拉刷新不适用于 iOS
Posted
技术标签:
【中文标题】自定义下拉刷新不适用于 iOS【英文标题】:Custom Pull-To-Refresh not working on iOS 【发布时间】:2018-03-11 08:42:14 【问题描述】:我正在按照 James Montemagno 的教程为我的布局添加下拉刷新支持,它在 android 上完美运行,但当我导航到与 Android 相同的页面时,ios 会生成以下错误。
System.InvalidCastException:
我试图显示的页面是一个简单的 StackLayout,它再次在 Android 上完美运行。
这是本教程中我的 iOS 渲染器类
[assembly: ExportRenderer(typeof(RefreshableLayout), typeof(RefreshableLayoutiOS))]
namespace SocialNetwork.iOS.Renderers
[Preserve(AllMembers = true)]
public class RefreshableLayoutiOS : ViewRenderer<RefreshableLayout, UIView>
public async static void Init()
var temp = DateTime.Now;
UIRefreshControl refreshControl;
protected override void OnElementChanged(ElementChangedEventArgs<RefreshableLayout> e)
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
return;
refreshControl = new UIRefreshControl();
refreshControl.ValueChanged += OnRefresh;
try
TryInsertRefresh(this);
catch (Exception ex)
Debug.WriteLine("View is not supported in PullToRefreshLayout: " + ex);
UpdateColors();
UpdateIsRefreshing();
UpdateIsSwipeToRefreshEnabled();
bool set;
nfloat origininalY;
bool TryOffsetRefresh(UIView view, bool refreshing, int index = 0)
if (view is UITableView)
var uiTableView = view as UITableView;
if (!set)
origininalY = uiTableView.ContentOffset.Y;
set = true;
if (refreshing)
uiTableView.SetContentOffset(new CoreGraphics.CGPoint(0, origininalY - refreshControl.Frame.Size.Height), true);
else
uiTableView.SetContentOffset(new CoreGraphics.CGPoint(0, origininalY), true);
return true;
if (view is UICollectionView)
var uiCollectionView = view as UICollectionView;
if (!set)
origininalY = uiCollectionView.ContentOffset.Y;
set = true;
if (refreshing)
uiCollectionView.SetContentOffset(new CoreGraphics.CGPoint(0, origininalY - refreshControl.Frame.Size.Height), true);
else
uiCollectionView.SetContentOffset(new CoreGraphics.CGPoint(0, origininalY), true);
return true;
if (view is UIWebView)
//can't do anything
return true;
if (view is UIScrollView)
var uiScrollView = view as UIScrollView;
if (!set)
origininalY = uiScrollView.ContentOffset.Y;
set = true;
if (refreshing)
uiScrollView.SetContentOffset(new CoreGraphics.CGPoint(0, origininalY - refreshControl.Frame.Size.Height), true);
else
uiScrollView.SetContentOffset(new CoreGraphics.CGPoint(0, origininalY), true);
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, i))
return true;
return false;
bool TryInsertRefresh(UIView view, int index = 0)
if (view is UITableView)
var uiTableView = view as UITableView;
uiTableView = view as UITableView;
view.InsertSubview(refreshControl, index);
return true;
if (view is UICollectionView)
var uiCollectionView = view as UICollectionView;
uiCollectionView = view as UICollectionView;
view.InsertSubview(refreshControl, index);
return true;
if (view is UIWebView)
var uiWebView = view as UIWebView;
uiWebView.ScrollView.InsertSubview(refreshControl, index);
return true;
if (view is UIScrollView)
var uiScrollView = view as UIScrollView;
view.InsertSubview(refreshControl, index);
uiScrollView.AlwaysBounceVertical = true;
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;
BindableProperty rendererProperty;
BindableProperty RendererProperty
get
if (rendererProperty != null)
return rendererProperty;
var type = Type.GetType("Xamarin.Forms.Platform.iOS.Platform, Xamarin.Forms.Platform.iOS");
var prop = type.GetField("RendererProperty");
var val = prop.GetValue(null);
rendererProperty = val as BindableProperty;
return rendererProperty;
void UpdateColors()
if (RefreshView == null)
return;
if (RefreshView.RefreshColor != Color.Default)
refreshControl.TintColor = RefreshView.RefreshColor.ToUIColor();
if (RefreshView.RefreshBackgroundColor != Color.Default)
refreshControl.BackgroundColor = RefreshView.RefreshBackgroundColor.ToUIColor();
void UpdateIsRefreshing()
IsRefreshing = RefreshView.IsRefreshing;
void UpdateIsSwipeToRefreshEnabled()
refreshControl.Enabled = RefreshView.IsPullToRefreshEnabled;
public RefreshableLayout RefreshView
get return Element;
bool isRefreshing;
public bool IsRefreshing
get return isRefreshing;
set
bool changed = IsRefreshing != value;
isRefreshing = value;
if (isRefreshing)
refreshControl.BeginRefreshing();
else
refreshControl.EndRefreshing();
if (changed)
TryOffsetRefresh(this, IsRefreshing);
void OnRefresh(object sender, EventArgs e)
if (RefreshView?.RefreshCommand?.CanExecute(RefreshView?.RefreshCommandParameter) ?? false)
RefreshView.RefreshCommand.Execute(RefreshView?.RefreshCommandParameter);
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == RefreshableLayout.IsPullToRefreshEnabledProperty.PropertyName)
UpdateIsSwipeToRefreshEnabled();
else if (e.PropertyName == RefreshableLayout.IsRefreshingProperty.PropertyName)
UpdateIsRefreshing();
else if (e.PropertyName == RefreshableLayout.RefreshColorProperty.PropertyName)
UpdateColors();
else if (e.PropertyName == RefreshableLayout.RefreshBackgroundColorProperty.PropertyName)
UpdateColors();
protected override void Dispose(bool disposing)
base.Dispose(disposing);
if (refreshControl != null)
refreshControl.ValueChanged -= OnRefresh;
我得到了这个tutorial 和这个GitHub 的代码
编辑:
XAML
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SocialNetwork.TestScrollPage" xmlns:local="clr-namespace:SocialNetwork.Renderers">
<ContentPage.Content>
<StackLayout>
<local:RefreshableLayout x:Name="RefreshableLayout" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<StackLayout>
</StackLayout>
</local:RefreshableLayout>
</StackLayout>
</ContentPage.Content>
CS:
public partial class TestScrollPage : ContentPage
public TestScrollPage ()
InitializeComponent ();
RefreshableLayout.RefreshCommand = new Command(() => RefreshPage());
public void RefreshPage()
RefreshableLayout.IsRefreshing = false;
DisplayAlert("ok", "ok", "ok");
然后我使用Detail = new TestScrollPage();
导航到页面
编辑 2:
public partial class RefreshableLayout : ContentView
public static readonly BindableProperty IsRefreshingProperty =
BindableProperty.Create(nameof(IsRefreshing), typeof(bool), typeof(RefreshableLayout), false);
/// <summary>
/// Gets or sets a value indicating whether this instance is refreshing.
/// </summary>
/// <value><c>true</c> if this instance is refreshing; otherwise, <c>false</c>.</value>
public bool IsRefreshing
get return (bool)GetValue(IsRefreshingProperty);
set
if ((bool)GetValue(IsRefreshingProperty) == value)
OnPropertyChanged(nameof(IsRefreshing));
SetValue(IsRefreshingProperty, value);
/// <summary>
/// The is pull to refresh enabled property.
/// </summary>
public static readonly BindableProperty IsPullToRefreshEnabledProperty =
BindableProperty.Create(nameof(IsPullToRefreshEnabled), typeof(bool), typeof(RefreshableLayout), true);
/// <summary>
/// Gets or sets a value indicating whether this instance is pull to refresh enabled.
/// </summary>
/// <value><c>true</c> if this instance is pull to refresh enabled; otherwise, <c>false</c>.</value>
public bool IsPullToRefreshEnabled
get return (bool)GetValue(IsPullToRefreshEnabledProperty);
set SetValue(IsPullToRefreshEnabledProperty, value);
/// <summary>
/// The refresh command property.
/// </summary>
public static readonly BindableProperty RefreshCommandProperty =
BindableProperty.Create(nameof(RefreshCommand), typeof(ICommand), typeof(RefreshableLayout));
/// <summary>
/// Gets or sets the refresh command.
/// </summary>
/// <value>The refresh command.</value>
public ICommand RefreshCommand
get return (ICommand)GetValue(RefreshCommandProperty);
set SetValue(RefreshCommandProperty, value);
/// <summary>
/// Gets the Refresh command
/// </summary>
public static readonly BindableProperty RefreshCommandParameterProperty =
BindableProperty.Create(nameof(RefreshCommandParameter),
typeof(object),
typeof(RefreshableLayout),
null,
propertyChanged: (bindable, oldvalue, newvalue) => ((RefreshableLayout)bindable).RefreshCommandCanExecuteChanged(bindable, EventArgs.Empty));
/// <summary>
/// Gets or sets the Refresh command parameter
/// </summary>
public object RefreshCommandParameter
get return GetValue(RefreshCommandParameterProperty);
set SetValue(RefreshCommandParameterProperty, value);
/// <summary>
/// Executes if enabled or not based on can execute changed
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
void RefreshCommandCanExecuteChanged(object sender, EventArgs eventArgs)
ICommand cmd = RefreshCommand;
if (cmd != null)
IsEnabled = cmd.CanExecute(RefreshCommandParameter);
/// <summary>
/// Color property of refresh spinner color
/// </summary>
public static readonly BindableProperty RefreshColorProperty =
BindableProperty.Create(nameof(RefreshColor), typeof(Color), typeof(RefreshableLayout), Color.Default);
/// <summary>
/// Refresh color
/// </summary>
public Color RefreshColor
get return (Color)GetValue(RefreshColorProperty);
set SetValue(RefreshColorProperty, value);
/// <summary>
/// Color property of refresh background color
/// </summary>
public static readonly BindableProperty RefreshBackgroundColorProperty =
BindableProperty.Create(nameof(RefreshBackgroundColor), typeof(Color), typeof(RefreshableLayout), Color.Default);
/// <summary>
/// Refresh background color
/// </summary>
public Color RefreshBackgroundColor
get return (Color)GetValue(RefreshBackgroundColorProperty);
set SetValue(RefreshBackgroundColorProperty, value);
/// <param name="widthConstraint">The available width for the element to use.</param>
/// <param name="heightConstraint">The available height for the element to use.</param>
/// <summary>
/// Optimization as we can get the size here of our content all in DIP
/// </summary>
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
if (Content == null)
return new SizeRequest(new Size(100, 100));
return base.OnMeasure(widthConstraint, heightConstraint);
【问题讨论】:
哪一行给出了 InvalidCastException? 运行Detail = new TestScrollPage();
时发生
您使用的是主从页面吗?发布您的共享代码,从哪里引发异常?
@hichame.yessou 我知道它不是页面,因为它只发生在渲染器中,如果我从 iOS 中删除渲染器类,它会加载页面,只是没有拉动刷新能力。
@Dan 无法从您的代码中分析更多内容。你能提供一个小样本吗?
【参考方案1】:
请阅读此documentation 关于 Xamarin Liver Player 的内容。它声明了限制:
Xamarin 表单不支持自定义渲染器。在您使用 Xamarin Liver Player 时还有一些其他限制或问题。所以我建议您使用模拟器或真实的物理设备来测试您的项目。
如果您没有 Mac。您也可以尝试下载 Enterprise Visual Studio,让模拟器映射到 Windows。
【讨论】:
以上是关于自定义下拉刷新不适用于 iOS的主要内容,如果未能解决你的问题,请参考以下文章