【问题标题】:Custom Pull-To-Refresh not working on iOS自定义下拉刷新不适用于 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 无法从您的代码中分析更多内容。你能提供一个小样本吗?

标签: c# xamarin xamarin.forms xamarin.ios


【解决方案1】:

请阅读此documentation 关于 Xamarin Liver Player 的内容。它声明了限制:

  • Xamarin 表单不支持自定义渲染器。

在您使用 Xamarin Liver Player 时还有一些其他限制或问题。所以我建议您使用模拟器或真实的物理设备来测试您的项目。

如果您没有 Mac。您也可以尝试下载 Enterprise Visual Studio,让模拟器映射到 Windows。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-10-27
    • 1970-01-01
    • 1970-01-01
    • 2014-04-27
    • 1970-01-01
    • 2018-09-17
    • 2023-01-12
    • 1970-01-01
    相关资源
    最近更新 更多