diff --git a/src/Controls/samples/Controls.Sample/Pages/Controls/CollectionViewGalleries/EmptyViewGalleries/EmptyViewGallery.cs b/src/Controls/samples/Controls.Sample/Pages/Controls/CollectionViewGalleries/EmptyViewGalleries/EmptyViewGallery.cs index d08ca966315f..39f511cee8ec 100644 --- a/src/Controls/samples/Controls.Sample/Pages/Controls/CollectionViewGalleries/EmptyViewGalleries/EmptyViewGallery.cs +++ b/src/Controls/samples/Controls.Sample/Pages/Controls/CollectionViewGalleries/EmptyViewGalleries/EmptyViewGallery.cs @@ -31,6 +31,8 @@ public EmptyViewGallery() new EmptyViewTemplateGallery(), Navigation), GalleryBuilder.NavButton("EmptyView (Swap EmptyView)", () => new EmptyViewSwapGallery(), Navigation), + GalleryBuilder.NavButton("EmptyView (Data Template Selector)", () => + new EmptyViewWithDataTemplateSelector(), Navigation), GalleryBuilder.NavButton("EmptyView (load simulation)", () => new EmptyViewLoadSimulateGallery(), Navigation), GalleryBuilder.NavButton("EmptyView RTL", () => diff --git a/src/Controls/samples/Controls.Sample/Pages/Controls/CollectionViewGalleries/EmptyViewGalleries/EmptyViewWithDataTemplateSelector.xaml b/src/Controls/samples/Controls.Sample/Pages/Controls/CollectionViewGalleries/EmptyViewGalleries/EmptyViewWithDataTemplateSelector.xaml new file mode 100644 index 000000000000..9e7fad850d31 --- /dev/null +++ b/src/Controls/samples/Controls.Sample/Pages/Controls/CollectionViewGalleries/EmptyViewGalleries/EmptyViewWithDataTemplateSelector.xaml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample/Pages/Controls/CollectionViewGalleries/EmptyViewGalleries/EmptyViewWithDataTemplateSelector.xaml.cs b/src/Controls/samples/Controls.Sample/Pages/Controls/CollectionViewGalleries/EmptyViewGalleries/EmptyViewWithDataTemplateSelector.xaml.cs new file mode 100644 index 000000000000..3cf229703706 --- /dev/null +++ b/src/Controls/samples/Controls.Sample/Pages/Controls/CollectionViewGalleries/EmptyViewGalleries/EmptyViewWithDataTemplateSelector.xaml.cs @@ -0,0 +1,75 @@ +#nullable enable +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Xaml; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Windows.Input; + +namespace Maui.Controls.Sample.Pages.CollectionViewGalleries.EmptyViewGalleries +{ + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class EmptyViewWithDataTemplateSelector : ContentPage + { + public EmptyViewWithDataTemplateSelector() + { + InitializeComponent(); + var emptyViewTemplateSelector = new SearchTermDataTemplateSelector + { + DefaultTemplate = (DataTemplate)Resources["AdvancedTemplate"], + OtherTemplate = (DataTemplate)Resources["BasicTemplate"] + }; + collectionView.EmptyViewTemplate = emptyViewTemplateSelector; + BindingContext = new EmptyViewWithDataTemplateSelectorViewModel(); + } + + public class Monkey + { + public string? Name { get; set; } + public string? Location { get; set; } + public string? Details { get; set; } + } + + public class SearchTermDataTemplateSelector : DataTemplateSelector + { + public DataTemplate? DefaultTemplate { get; set; } + public DataTemplate? OtherTemplate { get; set; } + + protected override DataTemplate? OnSelectTemplate(object item, BindableObject container) + { + string query = (string)item; + return query.Equals("xamarin", StringComparison.OrdinalIgnoreCase) ? OtherTemplate : DefaultTemplate; + } + } + + internal class EmptyViewWithDataTemplateSelectorViewModel + { + public ObservableCollection Monkeys { get; } = new(); + public ICommand FilterCommand => new Command(FilterItems); + + public EmptyViewWithDataTemplateSelectorViewModel() + { + // Directly populate the ObservableCollection + Monkeys.Add(new Monkey + { + Name = "Baboon", + Location = "Africa & Asia", + Details = "Baboons are African and Arabian Old World monkeys belonging to the genus Papio, part of the subfamily Cercopithecinae." + }); + } + + private void FilterItems(string filter) + { + var filteredItems = Monkeys.Where(monkey => monkey.Name?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false).ToList(); + Monkeys.Clear(); + foreach (var monkey in filteredItems) + { + Monkeys.Add(monkey); + } + } + } + } +} \ No newline at end of file diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs index 72de6841000a..0a925c31b9e0 100644 --- a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs +++ b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs @@ -567,7 +567,7 @@ protected virtual void HandleFormsElementMeasureInvalidated(VisualElement formsE internal void UpdateView(object view, DataTemplate viewTemplate, ref UIView uiView, ref VisualElement formsElement) { // Is view set on the ItemsView? - if (view is null && viewTemplate is null) + if (view is null && (viewTemplate is null || viewTemplate is DataTemplateSelector)) { if (formsElement != null) { diff --git a/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs b/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs index 742972bb9941..d827de4b9b60 100644 --- a/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs +++ b/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs @@ -401,7 +401,7 @@ protected virtual CGRect DetermineEmptyViewFrame() internal void UpdateView(object view, DataTemplate viewTemplate, ref UIView uiView, ref VisualElement formsElement) { // Is view set on the ItemsView? - if (view == null && viewTemplate is null) + if (view is null && (viewTemplate is null || viewTemplate is DataTemplateSelector)) { if (formsElement != null) { diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue25224.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issue25224.xaml new file mode 100644 index 000000000000..1b35e0b3835a --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue25224.xaml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue25224.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue25224.xaml.cs new file mode 100644 index 000000000000..8d44ee93e2ac --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue25224.xaml.cs @@ -0,0 +1,64 @@ +#nullable enable +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Windows.Input; + +namespace Maui.Controls.Sample.Issues +{ + [XamlCompilation(XamlCompilationOptions.Compile)] + [Issue(IssueTracker.Github, 25224, "CollectionView - EmptyView with EmptyViewTemplate for Data template selector page throws an exception", PlatformAffected.iOS)] + public partial class Issue25224: ContentPage + { + public Issue25224() + { + InitializeComponent(); + var emptyViewTemplateSelector = new SearchTermDataTemplateSelector + { + DefaultTemplate = (DataTemplate)Resources["AdvancedTemplate"], + OtherTemplate = (DataTemplate)Resources["BasicTemplate"] + }; + collectionView.EmptyViewTemplate = emptyViewTemplateSelector; + BindingContext = new Issue25224ViewModel(); + } + + public class SearchTermDataTemplateSelector : DataTemplateSelector + { + public DataTemplate? DefaultTemplate { get; set; } + public DataTemplate? OtherTemplate { get; set; } + + protected override DataTemplate? OnSelectTemplate(object item, BindableObject container) + { + string query = (string)item; + return query.Equals("xamarin", StringComparison.OrdinalIgnoreCase) ? OtherTemplate : DefaultTemplate; + } + } + + internal class Issue25224ViewModel + { + public ObservableCollection Monkeys { get; } = new(); + public ICommand FilterCommand => new Command(FilterItems); + + public Issue25224ViewModel() + { + // Directly populate the ObservableCollection + Monkeys.Add(new Monkey + { + Name = "Baboon", + Location = "Africa & Asia", + Details = "Baboons are African and Arabian Old World monkeys belonging to the genus Papio, part of the subfamily Cercopithecinae." + }); + } + + private void FilterItems(string filter) + { + var filteredItems = Monkeys.Where(monkey => monkey.Name?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false).ToList(); + Monkeys.Clear(); + foreach (var monkey in filteredItems) + { + Monkeys.Add(monkey); + } + } + } + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue25224.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue25224.cs new file mode 100644 index 000000000000..2623b850b409 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue25224.cs @@ -0,0 +1,36 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues +{ + internal class Issue25224 : _IssuesUITest + { + public Issue25224(TestDevice device) : base(device) { } + + public override string Issue => "CollectionView - EmptyView with EmptyViewTemplate for Data template selector page throws an exception"; + + [Test] + [Category(UITestCategories.CollectionView)] + public void CollectionViewEmptyViewDefaultTemplateShouldNotCrashOnDisplay() + { + App.WaitForElement("SearchBar"); + App.EnterText("SearchBar", "test"); + App.PressEnter(); + // On UI test, pressing Enter twice performs filtering and shows the empty view. + // This code is necessary due to the app's behavior on UI test, which differs from simple samples. + App.PressEnter(); + App.WaitForElement("Success"); + } + + [Test] + [Category(UITestCategories.CollectionView)] + public void CollectionViewEmptyViewOtherTemplateShouldNotCrashOnDisplay() + { + App.WaitForElement("SearchBar"); + App.EnterText("SearchBar", "xamarin"); + App.PressEnter(); + App.WaitForElement("Success"); + } + } +} \ No newline at end of file