xamarin / Xamarin.Forms

Xamarin.Forms is no longer supported. Migrate your apps to .NET MAUI.
https://aka.ms/xamarin-upgrade
Other
5.63k stars 1.88k forks source link

[iOS] Listview scrolled cells blank when using DataTemplateSelector #2009

Open Magendanz opened 6 years ago

Magendanz commented 6 years ago

When using a ListView with a DataTemplateSelector, cells scrolled into view appear either partially or fully blank. The bound collections are still there, since clicking the blank cells brings up the correct detail page, but it seems the cached views have been disposed.

Bypassing the DataTemplateSelector and setting the ItemTemplate to a single DataTemplate fixes the problem (but you're stuck with the single DataTemplate). Also, you won't see the issue as long as you're viewing cells that are all resolved to the same DataTemplate. It's only when you start switching between templates that the cell contents start disappearing when scrolled into view.

Note: This problem only occurs on iOS platform. Everything works as expected on Android.

Steps to Reproduce

  1. Open any ContentPage with ListView that uses a DataTemplateSelector on iOS device or simulator
  2. Scroll Listview to show additional cells

Expected Behavior

New cells should scroll into view

Actual Behavior

Cells scrolling into view are either completely or partially blank

Basic Information

Attachments

hartez commented 6 years ago

Full repro project

kingces95 commented 6 years ago

@Magendanz Please try and use RecycleElementAndDataTemplate. If the issue still reproduces, please see if @hartez reproduction demonstrates your issue (it works for me).

Magendanz commented 6 years ago

With this set, I now get an exception: System.NotSupportedException: RecycleElementAndDataTemplate requires DataTemplate activated with ctor taking a type.

kingces95 commented 6 years ago

@Magendanz That's expected if the DataTemplate is not constructed using the override that takes a type. Can you can return a DataTemplate from the Selector that takes a Type during construction? That is most efficient -- otherwise try simply RecycleElement.

@ez On master I'm unable to reproduce the issue with the attached reproduction. It could be that it's been fixed or possibly only reproduces on a specific environment. I'm running 11.2 as well.

Magendanz commented 6 years ago

Okay, using the DataTemplate constructor that takes a type works around the problem. (For those looking for sample code, I finally found it here.) The default CachingStrategy and CachingStrategy.RecycleElement weren't working on iOS for me, though.

UnreachableCode commented 5 years ago

@Magendanz That's expected if the DataTemplate is not constructed using the override that takes a type. Can you can return a DataTemplate from the Selector that takes a Type during construction? That is most efficient -- otherwise try simply RecycleElement.

@ez On master I'm unable to reproduce the issue with the attached reproduction. It could be that it's been fixed or possibly only reproduces on a specific environment. I'm running 11.2 as well.

Using the type constructor still isn't working for me. CanRecycle of the DataTemplate is false, despite setting the RecycleElementAndDataTemplate attribute.

Update: I was still incorrectly instantiating the templates left in my Xaml. They have to be constructed by the constructor.

Magendanz commented 5 years ago

Yeah, this is what worked for me:

    public class ListTemplateSelector : DataTemplateSelector
    {
        private readonly DataTemplate householdTemplate = new DataTemplate(typeof(HouseholdCell));
        private readonly DataTemplate childTemplate = new DataTemplate(typeof(ChildCell));

        protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
            => item is HouseholdViewModel ? householdTemplate : childTemplate;
    }
UnreachableCode commented 5 years ago

I am finding RecycleElementAndDataTemplate to effect scrolling performance quite significantly, where as RetainElmenet does not. I haven't noticed issues with RetainElement when adding more than 20 items of the same template. Should I continue to use RetainElement or will I run into problems with the note here?: https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/templates/data-templates/selector

"On Android, there can be no more than 20 different data templates per ListView."

hartez commented 5 years ago

@UnreachableCode I believe that's referring to the number of different templates, not the number of items which use that template. So you shouldn't run into any issues.

LeoJHarris commented 4 years ago

@Magendanz That's expected if the DataTemplate is not constructed using the override that takes a type. Can you can return a DataTemplate from the Selector that takes a Type during construction? That is most efficient -- otherwise try simply RecycleElement.

@kingces95 what if the viewcell is defined in the Resources of the page itself, as in my case:

<pages:BaseContentPage.Resources>
        <DataTemplate x:Key="IncomingTemplate">
            <ViewCell>
                ...
            </ViewCell>
        </DataTemplate>
        <DataTemplate x:Key="OutGoingTemplate">
            <ViewCell>
                ...
            </ViewCell>
        </DataTemplate>
        <viewcells:ChatTemplateSelector
            x:Key="ChatTemplateSelector"
            IncomingTemplate="{StaticResource IncomingTemplate}"
            OutgoingTemplate="{StaticResource OutGoingTemplate}" />
    </pages:BaseContentPage.Resources>
...
 <customviews:EnhancedListView
                    ItemTemplate="{StaticResource ChatTemplateSelector}"
                    <x:Arguments>
                       <ListViewCachingStrategy>RecycleElementAndDataTemplate</ListViewCachingStrategy>
                    </x:Arguments>
 </customviews:EnhancedListView>

Im getting the same error as @Magendanz 'RecycleElementAndDataTemplate requires DataTemplate activated with ctor taking a type.'

My ChatTemplateSelector looks like this:

public class ChatTemplateSelector : DataTemplateSelector
    {
        public DataTemplate IncomingTemplate { get; set; }

        public DataTemplate OutgoingTemplate { get; set; }

        protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
        {
            if (item is Message messageVm)
            {
                return messageVm.IsIncoming ? IncomingTemplate : OutgoingTemplate;
            }
            return null;
        }
    }
cristiproj commented 4 years ago

@Magendanz That's expected if the DataTemplate is not constructed using the override that takes a type. Can you can return a DataTemplate from the Selector that takes a Type during construction? That is most efficient -- otherwise try simply RecycleElement.

@kingces95 what if the viewcell is defined in the Resources of the page itself, as in my case:

<pages:BaseContentPage.Resources>
        <DataTemplate x:Key="IncomingTemplate">
            <ViewCell>
                ...
            </ViewCell>
        </DataTemplate>
        <DataTemplate x:Key="OutGoingTemplate">
            <ViewCell>
                ...
            </ViewCell>
        </DataTemplate>
        <viewcells:ChatTemplateSelector
            x:Key="ChatTemplateSelector"
            IncomingTemplate="{StaticResource IncomingTemplate}"
            OutgoingTemplate="{StaticResource OutGoingTemplate}" />
    </pages:BaseContentPage.Resources>
...
 <customviews:EnhancedListView
                    ItemTemplate="{StaticResource ChatTemplateSelector}"
                    <x:Arguments>
                       <ListViewCachingStrategy>RecycleElementAndDataTemplate</ListViewCachingStrategy>
                    </x:Arguments>
 </customviews:EnhancedListView>

Im getting the same error as @Magendanz 'RecycleElementAndDataTemplate requires DataTemplate activated with ctor taking a type.'

My ChatTemplateSelector looks like this:

public class ChatTemplateSelector : DataTemplateSelector
    {
        public DataTemplate IncomingTemplate { get; set; }

        public DataTemplate OutgoingTemplate { get; set; }

        protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
        {
            if (item is Message messageVm)
            {
                return messageVm.IsIncoming ? IncomingTemplate : OutgoingTemplate;
            }
            return null;
        }
    }

I have the same question. Can anyone clarify? Thanks in advance :)

skadookkunnan commented 2 years ago

Any update on this ticket? In our case, we have filtering we apply and then update the data source depending on the selected filter. As soon as the user switch from the 'All' list to 'selected option', the ListView starts showing blank cells. Sometimes it shows like a last line of the text in the cell. However, if we try to do any interaction on that cell maybe touch or so, the data starts showing up. Again, if we scroll up and down, the issue comes back. Once this behavior starts, it is very difficult to avoid it. We have to close the page and re-open.

Any update on this would be really great!