Pull to refresh instantly removes the IsRefreshing spinner. Has something changed? Is it iOS10?

SimonJaspersSimonJaspers USMember ✭✭
edited September 2016 in Xamarin.Forms

The issue

I've implemented a ListView with a pull-to-refresh command following this approach by James Montemagno. Recently (I believe after updating to iOS10), the pull to refresh animation broke. Here's what happens:

  1. Pulling down: The spinner fills to 360 degrees. (Expected, correct behavior)
  2. Releasing: The spinner flashes and disappears. (Expected bahavior: spinner remains at top of listview and spins until IsBusy is set to false)
  3. Completing: The list item is added to the list. (Expected, correct behavior)

I've included an animated gif that shows exactly what happens.

Sometimes, I'm seeing this error popup in the application output:

Attempting to change the refresh control while it is not idle is strongly discouraged and probably won't work properly.

Any ideas on what could be the cause of this issue?


Version info

Xamarin.Forms version 2.3.2.127
iOS 10.0


Some more info on the code behind the gif:

I've followed the code in the post I've linked above quite strictly. To simulate a web request, I'm awaiting a Task.Delay. I'm expecting the spinner to remain visible for 1000ms.

private async void ExecuteLoadNewStringCommand()
{
    if (IsRefreshing)
        return;

    IsRefreshing = true;
    LoadNewStringCommand.ChangeCanExecute();

    await Task.Delay(1000);
    ListItems.Add($"Another person ({ListItems.Count + 1})");

    IsRefreshing = false;
    LoadNewStringCommand.ChangeCanExecute();
}

My ListView is bound to an ObservableCollection<string> via xaml:

<ListView
        ItemsSource="{Binding ListItems}"
        HasUnevenRows="True"
        IsPullToRefreshEnabled="True"
        RefreshCommand="{Binding LoadNewStringCommand}"
        IsRefreshing="{Binding IsBusy, Mode=OneWay}"/>

I can provide the Xamarin studio solution or any of the .cs/.xaml files if there's more you need to see.

Best Answers

Answers

  • _albertoms_albertoms MXUniversity ✭✭
    edited January 2017

    Hey, @SimonJaspers

    I'm using C# code to build the UI of my app and I faced this issue today. I solved it by changing the definition of my command.

    From:
    RefreshMyStuffCommand = new Command (async () => await LoadMyStuffAsync (), () => canRefresh);

    To:
    RefreshMyStuffCommand = new Command (async () => await LoadMyStuffAsync ());

    I removed the parameter that indicated if the command can be executed. I still call the ChangeCanExecute() method though.

    This is the action that is called from the command:

    async Task LoadMyStuffAsync ()
            {
                CanRefreshStuff (false);
                await Task.Run (async () => {
                    try {
                        await Task.Delay (1000); // this is in order to test if the spinner keeps rolling
                        var theActualStuff = await App.Service.GetStuff ();
                        StuffList.Clear (); // a collections that is bound to the ItemsSource property of the ListView
                        foreach (var elem in theActualStuff) {
                            StuffList.Add(elem);
                        }
                    } catch (Exception ex) {
                        Console.WriteLine (@"Error loading stuff: {0}", ex.Message);
                    }
                });
                CanRefreshStuff (true);
            }
    
    void CanRefreshStuff (bool value)
            {
                //canRefresh = value;
                IsBusy = !value; // I bound this var to the ListView.IsRefreshingProperty
                ((Command)RefreshMyStuffCommand).ChangeCanExecute ();
            }
    

    I think that the problem you will face by removing the call to ChangeCanExecute() method is that you will let the door open to perform another pull-to-refresh interaction while the first one is still in progress.

Sign In or Register to comment.