зеркало из https://github.com/DeGsoft/maui-linux.git
[iOS] Grouped ListViews will use always HeaderView instead of TitleForHeader (#1030)
* Add repro for 45125 * [Core] Don't crash if group is empty * [iOS] ListView uses HeaderView instead of TitleHeader ListView previously used TitleForHeader when using a GroupDisplayBinding. The downside to that is that it will not fire any event when it disappears. Now using a HeaderView in all cases so we can use HeaderViewDisplayingEnded to fire the Disappearing event. * Fix index out of range exception on repro * Fix ui test * Fix merge error
This commit is contained in:
Родитель
c2f576abe1
Коммит
376756fabe
|
@ -0,0 +1,261 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xamarin.Forms.CustomAttributes;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
||||
#if UITEST
|
||||
using Xamarin.UITest;
|
||||
using NUnit.Framework;
|
||||
#endif
|
||||
|
||||
namespace Xamarin.Forms.Controls.Issues
|
||||
{
|
||||
[Preserve(AllMembers = true)]
|
||||
[Issue(IssueTracker.Bugzilla, 45125, "ListView lacks a way to get information about visible elements (such as FirstVisibleItem) to restore visual positions of elements", PlatformAffected.iOS)]
|
||||
public class Bugzilla45125 : TestContentPage
|
||||
{
|
||||
int _TestNumber = 0;
|
||||
|
||||
const string Instructions = "The black area below should show text listing appearing and disappearing events for the ListView beside it. It should update as you scroll the ListView, with each row firing a single Disappearing event and a single Appearing event as it leaves and enters the visible screen, respectively. If this does not happen, this test has failed.";
|
||||
const string AppearingLabelId = "appearing";
|
||||
const string DisappearingLabelId = "disappearing";
|
||||
const string TestButtonId = "TestButtonId";
|
||||
|
||||
static int _Appearing = 0;
|
||||
static int _Disappearing = 0;
|
||||
|
||||
static Label _status = new Label
|
||||
{
|
||||
TextColor = Color.White,
|
||||
//TODO: NoWrap causes the Label to be missing from the Horizontal StackLayout
|
||||
//LineBreakMode = LineBreakMode.NoWrap
|
||||
};
|
||||
|
||||
static Label _groupsAppearing = new Label
|
||||
{
|
||||
TextColor = Color.Green,
|
||||
AutomationId = AppearingLabelId
|
||||
};
|
||||
|
||||
static Label _groupsDisappearing = new Label
|
||||
{
|
||||
TextColor = Color.Blue,
|
||||
AutomationId = DisappearingLabelId
|
||||
};
|
||||
|
||||
static ScrollView _scroll = new ScrollView
|
||||
{
|
||||
BackgroundColor = Color.Black,
|
||||
Content = _status,
|
||||
MinimumWidthRequest = 200
|
||||
};
|
||||
|
||||
[Preserve(AllMembers = true)]
|
||||
class GroupItem
|
||||
{
|
||||
public string DisplayText { get; set; }
|
||||
}
|
||||
|
||||
[Preserve(AllMembers = true)]
|
||||
class GroupedData : List<GroupItem>
|
||||
{
|
||||
public string GroupName { get; set; }
|
||||
}
|
||||
|
||||
[Preserve(AllMembers = true)]
|
||||
class MyCell : ViewCell
|
||||
{
|
||||
public MyCell()
|
||||
{
|
||||
Label newLabel = new Label();
|
||||
newLabel.SetBinding(Label.TextProperty, nameof(GroupItem.DisplayText));
|
||||
View = newLabel;
|
||||
}
|
||||
}
|
||||
|
||||
[Preserve(AllMembers = true)]
|
||||
class HeaderCell : ViewCell
|
||||
{
|
||||
public HeaderCell()
|
||||
{
|
||||
Label newLabel = new Label();
|
||||
newLabel.SetBinding(Label.TextProperty, nameof(GroupedData.GroupName));
|
||||
View = newLabel;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Init()
|
||||
{
|
||||
InitTest(ListViewCachingStrategy.RecycleElement, true);
|
||||
}
|
||||
|
||||
void InitTest(ListViewCachingStrategy cachingStrategy, bool useTemplate)
|
||||
{
|
||||
_scroll.SetScrolledPosition(0, 0);
|
||||
|
||||
_status.Text = _groupsAppearing.Text = _groupsDisappearing.Text = "";
|
||||
_Appearing = _Disappearing = 0;
|
||||
|
||||
List<GroupedData> groups = GetGroups();
|
||||
|
||||
var listView = new ListView(cachingStrategy)
|
||||
{
|
||||
ItemsSource = groups,
|
||||
ItemTemplate = new DataTemplate(typeof(MyCell)),
|
||||
HasUnevenRows = true,
|
||||
|
||||
// Must be grouped to repro
|
||||
IsGroupingEnabled = true
|
||||
};
|
||||
|
||||
if (useTemplate)
|
||||
listView.GroupHeaderTemplate = new DataTemplate(typeof(HeaderCell));
|
||||
else
|
||||
listView.GroupDisplayBinding = new Binding(nameof(GroupedData.GroupName));
|
||||
|
||||
// Must attach to the ListView's events to repro
|
||||
listView.ItemAppearing += ListView_ItemAppearing;
|
||||
listView.ItemDisappearing += ListView_ItemDisappearing;
|
||||
|
||||
var horStack = new StackLayout
|
||||
{
|
||||
Orientation = StackOrientation.Horizontal,
|
||||
Children = { _scroll, listView },
|
||||
HeightRequest = 300
|
||||
};
|
||||
|
||||
Button nextButton = new Button { Text = "Next", AutomationId = TestButtonId };
|
||||
nextButton.Clicked += NextButton_Clicked;
|
||||
StackLayout stack = new StackLayout
|
||||
{
|
||||
Children = { new Label { Text = Instructions }, _groupsAppearing, _groupsDisappearing, horStack, nextButton }
|
||||
};
|
||||
Content = stack;
|
||||
|
||||
var lastGroup = groups.Last();
|
||||
var lastItem = lastGroup.First();
|
||||
|
||||
var firstGroup = groups.First();
|
||||
var firstItem = firstGroup.First();
|
||||
|
||||
Device.StartTimer(TimeSpan.FromSeconds(1), () => { listView.ScrollTo(lastItem, lastGroup, ScrollToPosition.End, true); return false; });
|
||||
Device.StartTimer(TimeSpan.FromSeconds(2), () => { listView.ScrollTo(firstItem, firstItem, ScrollToPosition.MakeVisible, true); return false; });
|
||||
|
||||
_TestNumber++;
|
||||
}
|
||||
|
||||
void NextButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
switch (_TestNumber)
|
||||
{
|
||||
default:
|
||||
InitTest(ListViewCachingStrategy.RecycleElement, useTemplate: true);
|
||||
break;
|
||||
case 1:
|
||||
InitTest(ListViewCachingStrategy.RetainElement, useTemplate: true);
|
||||
break;
|
||||
case 2:
|
||||
InitTest(ListViewCachingStrategy.RetainElement, useTemplate: false);
|
||||
break;
|
||||
case 3:
|
||||
InitTest(ListViewCachingStrategy.RecycleElement, useTemplate: false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
List<GroupedData> GetGroups()
|
||||
{
|
||||
List<GroupedData> groups = new List<GroupedData>();
|
||||
|
||||
for (int i = 1; i < 100; i++)
|
||||
{
|
||||
var group = new GroupedData { GroupName = $"Group {i}" };
|
||||
|
||||
group.Add(new GroupItem { DisplayText = $"Item {i}" });
|
||||
|
||||
groups.Add(group);
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
static string GetItemText(object item)
|
||||
{
|
||||
var groupDisplayTextItem = item as TemplatedItemsList<ItemsView<Cell>, Cell>;
|
||||
var groupItem = item as GroupedData;
|
||||
var itemItem = item as GroupItem;
|
||||
|
||||
var text = item.ToString();
|
||||
|
||||
if (groupDisplayTextItem != null)
|
||||
text = groupDisplayTextItem.Name;
|
||||
else if (groupItem != null)
|
||||
text = groupItem.GroupName;
|
||||
else if (itemItem != null)
|
||||
text = itemItem.DisplayText;
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
void ListView_ItemDisappearing(object sender, ItemVisibilityEventArgs e)
|
||||
{
|
||||
var text = GetItemText(e.Item);
|
||||
|
||||
if (_status.Text?.Length > 500)
|
||||
_status.Text = "";
|
||||
|
||||
_Disappearing++;
|
||||
|
||||
_groupsDisappearing.Text = _Disappearing.ToString();
|
||||
|
||||
_status.Text += $"\r\n {text} Disappearing";
|
||||
_scroll.ScrollToAsync(_status, ScrollToPosition.MakeVisible, false);
|
||||
}
|
||||
|
||||
void ListView_ItemAppearing(object sender, ItemVisibilityEventArgs e)
|
||||
{
|
||||
var text = GetItemText(e.Item);
|
||||
|
||||
if (_status.Text?.Length > 500)
|
||||
_status.Text = "";
|
||||
|
||||
_Appearing++;
|
||||
|
||||
_groupsAppearing.Text = _Appearing.ToString();
|
||||
|
||||
_status.Text += $"\r\n {text} Appearing";
|
||||
_scroll.ScrollToAsync(_status, ScrollToPosition.MakeVisible, false);
|
||||
}
|
||||
|
||||
#if UITEST
|
||||
[Test]
|
||||
public void Bugzilla45125Test ()
|
||||
{
|
||||
RunTest();
|
||||
|
||||
RunningApp.Tap(q => q.Marked(TestButtonId));
|
||||
RunTest();
|
||||
|
||||
RunningApp.Tap(q => q.Marked(TestButtonId));
|
||||
RunTest();
|
||||
|
||||
RunningApp.Tap(q => q.Marked(TestButtonId));
|
||||
RunTest();
|
||||
}
|
||||
|
||||
void RunTest()
|
||||
{
|
||||
RunningApp.WaitForElement (q => q.Marked (AppearingLabelId));
|
||||
RunningApp.WaitForElement (q => q.Marked (DisappearingLabelId));
|
||||
|
||||
RunningApp.Screenshot ("There should be appearing and disappearing events for the Groups and Items.");
|
||||
var appearing = int.Parse(RunningApp.Query(q => q.Marked(AppearingLabelId))[0].Text);
|
||||
var disappearing = int.Parse(RunningApp.Query(q=> q.Marked(DisappearingLabelId))[0].Text);
|
||||
|
||||
Assert.IsTrue(appearing > 0, $"Test {_TestNumber}: No appearing events for groups found.");
|
||||
Assert.IsTrue(disappearing > 0, $"Test {_TestNumber}: No disappearing events for groups found.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -331,6 +331,7 @@
|
|||
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla40161.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla44886.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Bugzila57749.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla45125.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ScrollViewObjectDisposed.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla58645.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla27731.cs" />
|
||||
|
|
|
@ -379,6 +379,10 @@ namespace Xamarin.Forms
|
|||
public string GetDisplayTextFromGroup(object cell)
|
||||
{
|
||||
int groupIndex = TemplatedItems.GetGlobalIndexOfGroup(cell);
|
||||
|
||||
if (groupIndex == -1)
|
||||
return cell.ToString();
|
||||
|
||||
var group = TemplatedItems.GetGroup(groupIndex);
|
||||
|
||||
string displayBinding = null;
|
||||
|
|
|
@ -922,21 +922,30 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
|
||||
public override UIView GetViewForHeader(UITableView tableView, nint section)
|
||||
{
|
||||
if (List.IsGroupingEnabled && List.GroupHeaderTemplate != null)
|
||||
{
|
||||
var cell = TemplatedItemsView.TemplatedItems[(int)section];
|
||||
if (cell.HasContextActions)
|
||||
throw new NotSupportedException("Header cells do not support context actions");
|
||||
UIView view = null;
|
||||
|
||||
if (!List.IsGroupingEnabled)
|
||||
return view;
|
||||
|
||||
var cell = TemplatedItemsView.TemplatedItems[(int)section];
|
||||
if (cell.HasContextActions)
|
||||
throw new NotSupportedException("Header cells do not support context actions");
|
||||
|
||||
var renderer = (CellRenderer)Internals.Registrar.Registered.GetHandlerForObject<IRegisterable>(cell);
|
||||
|
||||
var view = new HeaderWrapperView();
|
||||
view = new HeaderWrapperView();
|
||||
view.AddSubview(renderer.GetCell(cell, null, tableView));
|
||||
|
||||
return view;
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
return null;
|
||||
public override void HeaderViewDisplayingEnded(UITableView tableView, UIView headerView, nint section)
|
||||
{
|
||||
if (!List.IsGroupingEnabled)
|
||||
return;
|
||||
|
||||
var cell = TemplatedItemsView.TemplatedItems[(int)section];
|
||||
cell.SendDisappearing();
|
||||
}
|
||||
|
||||
public override nint NumberOfSections(UITableView tableView)
|
||||
|
@ -1046,18 +1055,6 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
return templatedItems.ShortNames.ToArray();
|
||||
}
|
||||
|
||||
public override string TitleForHeader(UITableView tableView, nint section)
|
||||
{
|
||||
if (!List.IsGroupingEnabled)
|
||||
return null;
|
||||
|
||||
var sl = GetSectionList((int)section);
|
||||
sl.PropertyChanged -= OnSectionPropertyChanged;
|
||||
sl.PropertyChanged += OnSectionPropertyChanged;
|
||||
|
||||
return sl.Name;
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
_selectionFromNative = false;
|
||||
|
@ -1100,27 +1097,6 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
return cell;
|
||||
}
|
||||
|
||||
ITemplatedItemsList<Cell> GetSectionList(int section)
|
||||
{
|
||||
return (ITemplatedItemsList<Cell>)((IList)TemplatedItemsView.TemplatedItems)[section];
|
||||
}
|
||||
|
||||
void OnSectionPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
var currentSelected = _uiTableView.IndexPathForSelectedRow;
|
||||
|
||||
var til = (TemplatedItemsList<ItemsView<Cell>, Cell>)sender;
|
||||
var groupIndex = ((IList)TemplatedItemsView.TemplatedItems).IndexOf(til);
|
||||
if (groupIndex == -1)
|
||||
{
|
||||
til.PropertyChanged -= OnSectionPropertyChanged;
|
||||
return;
|
||||
}
|
||||
|
||||
_uiTableView.ReloadSections(NSIndexSet.FromIndex(groupIndex), ReloadSectionsAnimation);
|
||||
_uiTableView.SelectRow(currentSelected, false, UITableViewScrollPosition.None);
|
||||
}
|
||||
|
||||
void OnShortNamesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
_uiTableView.ReloadSectionIndexTitles();
|
||||
|
|
Загрузка…
Ссылка в новой задаче