自定义Pull-To-Refresh无法在iOS

时间:2018-03-11 08:42:14

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

我正在关注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);
    }
}

1 个答案:

答案 0 :(得分:1)

请阅读documentation关于Xamarin肝脏球员的信息。它宣布了限制:

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

当您使用Xamarin肝脏播放器时,还有一些其他限制或问题。所以我建议你使用模拟器或真实的物理设备来测试你的项目。

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