Skip to content
This repository was archived by the owner on May 1, 2024. It is now read-only.

Commit 3d054b8

Browse files
authored
Move layout change resolution calls up to VisualElementRenderer (#13640)
* Move layout change resolution call up to VisualElement Fixes #13418 Fixes #13492 * Signal layout request when CollectionView is in a layout Fixes #13551 * Fix layout error loop from test 12714 * Fix autolayout issues when CollectionView size is less than span; * Provide measurement for default text cells (no ItemTemplate); * Cleanup
1 parent b7056ad commit 3d054b8

File tree

11 files changed

+196
-44
lines changed

11 files changed

+196
-44
lines changed

Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue13203.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ protected override void Init()
3737

3838
var source = new List<Item> { new Item { Text = Success } };
3939
cv.ItemsSource = source;
40+
4041
Content = cv;
4142

4243
Appearing += (sender, args) => { cv.IsVisible = true; };
@@ -49,7 +50,7 @@ class Item
4950

5051
#if UITEST
5152
[Test]
52-
public void SettingGroupedCollectionViewItemSourceNullShouldNotCrash()
53+
public void CollectionShouldInvalidateOnVisibilityChange()
5354
{
5455
RunningApp.WaitForElement(Success);
5556
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.ObjectModel;
4+
using System.Globalization;
5+
using System.Text;
6+
using Xamarin.Forms.CustomAttributes;
7+
using Xamarin.Forms.Internals;
8+
9+
#if UITEST
10+
using Xamarin.UITest;
11+
using NUnit.Framework;
12+
using Xamarin.Forms.Core.UITests;
13+
#endif
14+
15+
namespace Xamarin.Forms.Controls.Issues
16+
{
17+
[Issue(IssueTracker.Github, 13551, "[Bug] [iOS] CollectionView does not display items if `IsVisible` modified via a binding/trigger", PlatformAffected.iOS)]
18+
#if UITEST
19+
[NUnit.Framework.Category(UITestCategories.CollectionView)]
20+
#endif
21+
public class Issue13551 : TestContentPage
22+
{
23+
const string Success1 = "Success1";
24+
const string Success2 = "Success2";
25+
26+
public ObservableCollection<Item> Source1 { get; } = new ObservableCollection<Item>();
27+
public ObservableCollection<Item> Source2 { get; } = new ObservableCollection<Item>();
28+
29+
CollectionView BindingWithConverter()
30+
{
31+
var cv = new CollectionView
32+
{
33+
IsVisible = true,
34+
35+
ItemTemplate = new DataTemplate(() =>
36+
{
37+
var label = new Label();
38+
label.SetBinding(Label.TextProperty, new Binding(nameof(Item.Text)));
39+
return label;
40+
})
41+
};
42+
43+
cv.SetBinding(CollectionView.ItemsSourceProperty, new Binding("Source1"));
44+
cv.SetBinding(VisualElement.IsVisibleProperty, new Binding("Source1.Count", converter: new IntToBoolConverter()));
45+
46+
return cv;
47+
}
48+
49+
CollectionView WithTrigger()
50+
{
51+
var cv = new CollectionView
52+
{
53+
IsVisible = true,
54+
55+
ItemTemplate = new DataTemplate(() =>
56+
{
57+
var label = new Label();
58+
label.SetBinding(Label.TextProperty, new Binding(nameof(Item.Text)));
59+
return label;
60+
})
61+
};
62+
63+
cv.SetBinding(CollectionView.ItemsSourceProperty, new Binding("Source2"));
64+
65+
var trigger = new DataTrigger(typeof(CollectionView));
66+
trigger.Value = 0;
67+
trigger.Setters.Add(new Setter() { Property = VisualElement.IsVisibleProperty, Value = false });
68+
trigger.Binding = new Binding("Source2.Count");
69+
70+
cv.Triggers.Add(trigger);
71+
72+
return cv;
73+
}
74+
75+
protected override void Init()
76+
{
77+
BindingContext = this;
78+
79+
var cv1 = BindingWithConverter();
80+
var cv2 = WithTrigger();
81+
82+
var grid = new Grid
83+
{
84+
RowDefinitions = new RowDefinitionCollection
85+
{
86+
new RowDefinition() { Height = GridLength.Star },
87+
new RowDefinition() { Height = GridLength.Star },
88+
}
89+
};
90+
91+
grid.Children.Add(cv1);
92+
grid.Children.Add(cv2);
93+
Grid.SetRow(cv2, 1);
94+
95+
Content = grid;
96+
97+
Device.StartTimer(TimeSpan.FromMilliseconds(300), () =>
98+
{
99+
Device.BeginInvokeOnMainThread(() =>
100+
{
101+
Source1.Add(new Item { Text = Success1 });
102+
Source2.Add(new Item { Text = Success2 });
103+
});
104+
105+
return false;
106+
});
107+
}
108+
109+
class IntToBoolConverter : IValueConverter
110+
{
111+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
112+
{
113+
return value is int val && val > 0;
114+
}
115+
116+
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
117+
{
118+
throw new NotImplementedException();
119+
}
120+
}
121+
122+
public class Item
123+
{
124+
public string Text { get; set; }
125+
}
126+
127+
#if UITEST
128+
[Test]
129+
public void CollectionInLayoutShouldInvalidateOnVisibilityChange()
130+
{
131+
RunningApp.WaitForElement(Success1);
132+
RunningApp.WaitForElement(Success2);
133+
}
134+
#endif
135+
}
136+
}

Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<Compile Include="$(MSBuildThisFileDirectory)CollectionViewGroupTypeIssue.cs" />
1313
<Compile Include="$(MSBuildThisFileDirectory)Issue11214.cs" />
1414
<Compile Include="$(MSBuildThisFileDirectory)Issue13109.cs" />
15+
<Compile Include="$(MSBuildThisFileDirectory)Issue13551.cs" />
1516
<Compile Include="$(MSBuildThisFileDirectory)RadioButtonTemplateFromStyle.cs" />
1617
<Compile Include="$(MSBuildThisFileDirectory)ShellWithCustomRendererDisabledAnimations.cs" />
1718
<Compile Include="$(MSBuildThisFileDirectory)ShellFlyoutContent.cs" />

Xamarin.Forms.Platform.Android/Platform.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1315,16 +1315,6 @@ protected override void Dispose(bool disposing)
13151315

13161316
bool ILayoutChanges.HasLayoutOccurred => _hasLayoutOccurred;
13171317

1318-
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
1319-
{
1320-
if (Element is Layout layout)
1321-
{
1322-
layout.ResolveLayoutChanges();
1323-
}
1324-
1325-
base.OnMeasure(widthMeasureSpec, heightMeasureSpec);
1326-
}
1327-
13281318
protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
13291319
{
13301320
base.OnLayout(changed, left, top, right, bottom);

Xamarin.Forms.Platform.Android/VisualElementRenderer.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,5 +505,15 @@ internal virtual void SendVisualElementInitialized(VisualElement element, AView
505505

506506
void IVisualElementRenderer.SetLabelFor(int? id)
507507
=> ViewCompat.SetLabelFor(this, id ?? ViewCompat.GetLabelFor(this));
508+
509+
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
510+
{
511+
if (Element is Layout layout)
512+
{
513+
layout.ResolveLayoutChanges();
514+
}
515+
516+
base.OnMeasure(widthMeasureSpec, heightMeasureSpec);
517+
}
508518
}
509519
}

Xamarin.Forms.Platform.UAP/LayoutRenderer.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,5 @@ void UpdateClipToBounds()
8080
Clip = new RectangleGeometry { Rect = new WRect(0, 0, ActualWidth, ActualHeight) };
8181
}
8282
}
83-
84-
protected override Windows.Foundation.Size MeasureOverride(Windows.Foundation.Size availableSize)
85-
{
86-
Element?.ResolveLayoutChanges();
87-
return base.MeasureOverride(availableSize);
88-
}
8983
}
9084
}

Xamarin.Forms.Platform.UAP/VisualElementRenderer.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,11 @@ protected override Windows.Foundation.Size MeasureOverride(Windows.Foundation.Si
294294
if (Element == null || availableSize.Width * availableSize.Height == 0)
295295
return new Windows.Foundation.Size(0, 0);
296296

297+
if (Element is Layout layout)
298+
{
299+
layout.ResolveLayoutChanges();
300+
}
301+
297302
Element.IsInNativeLayout = true;
298303

299304
for (var i = 0; i < ElementController.LogicalChildren.Count; i++)

Xamarin.Forms.Platform.iOS/CollectionView/GridViewLayout.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,16 @@ public override void ConstrainTo(CGSize size)
5757
// Truncating to 177 means the rows fit, but there's a very slight gap
5858
// There may not be anything we can do about this.
5959

60-
ConstrainedDimension = (int)ConstrainedDimension;
60+
// Possibly the solution is to round to the tenths or hundredths place, we should look into that.
61+
// But for the moment, we need a special case for dimensions < 1, because upon transition from invisible to visible,
62+
// Forms will briefly layout the CollectionView at a size of 1,1. For a spanned collectionview, that means we
63+
// need to accept a constrained dimension of 1/span. If we don't, autolayout will start throwing a flurry of
64+
// exceptions (which we can't catch) and either crash the app or spin until we kill the app.
65+
if (ConstrainedDimension > 1)
66+
{
67+
ConstrainedDimension = (int)ConstrainedDimension;
68+
}
69+
6170
DetermineCellSize();
6271
}
6372

@@ -288,6 +297,12 @@ static nfloat ReduceSpacingToFitIfNeeded(nfloat available, nfloat requestedSpaci
288297
}
289298

290299
var maxSpacing = (available - span) / (span - 1);
300+
301+
if (maxSpacing < 0)
302+
{
303+
return 0;
304+
}
305+
291306
return (nfloat)Math.Min(requestedSpacing, maxSpacing);
292307
}
293308
}

Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,6 @@ public override void ViewWillLayoutSubviews()
160160
{
161161
base.ViewWillLayoutSubviews();
162162

163-
// We can't set this constraint up on ViewDidLoad, because Forms does other stuff that resizes the view
164-
// and we end up with massive layout errors. And View[Will/Did]Appear do not fire for this controller
165-
// reliably. So until one of those options is cleared up, we set this flag so that the initial constraints
166-
// are set up the first time this method is called.
167163
EnsureLayoutInitialized();
168164

169165
LayoutEmptyView();
@@ -566,11 +562,22 @@ TemplatedCell CreateAppropriateCellForLayout()
566562
return new VerticalCell(frame);
567563
}
568564

569-
public TemplatedCell CreateMeasurementCell(NSIndexPath indexPath)
565+
public UICollectionViewCell CreateMeasurementCell(NSIndexPath indexPath)
570566
{
571567
if (ItemsView.ItemTemplate == null)
572568
{
573-
return null;
569+
var frame = new CGRect(0, 0, ItemsViewLayout.EstimatedItemSize.Width, ItemsViewLayout.EstimatedItemSize.Height);
570+
571+
if (ItemsViewLayout.ScrollDirection == UICollectionViewScrollDirection.Horizontal)
572+
{
573+
var cell1 = new HorizontalDefaultCell(frame);
574+
UpdateDefaultCell(cell1, indexPath);
575+
return cell1;
576+
}
577+
578+
var cell = new VerticalDefaultCell(frame);
579+
UpdateDefaultCell(cell, indexPath);
580+
return cell;
574581
}
575582

576583
TemplatedCell templatedCell = CreateAppropriateCellForLayout();
@@ -610,6 +617,7 @@ void ItemsViewPropertyChanged(object sender, PropertyChangedEventArgs changedPro
610617
if (ItemsView.IsVisible)
611618
{
612619
Layout.InvalidateLayout();
620+
CollectionView.LayoutIfNeeded();
613621
}
614622
}
615623
}

Xamarin.Forms.Platform.iOS/Platform.cs

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -586,26 +586,6 @@ public override UIView HitTest(CGPoint point, UIEvent uievent)
586586

587587
return result;
588588
}
589-
590-
void ResolveLayoutChanges()
591-
{
592-
if (Element is Layout layout)
593-
{
594-
layout.ResolveLayoutChanges();
595-
}
596-
}
597-
598-
public override void LayoutSubviews()
599-
{
600-
ResolveLayoutChanges();
601-
base.LayoutSubviews();
602-
}
603-
604-
public override CGSize SizeThatFits(CGSize size)
605-
{
606-
ResolveLayoutChanges();
607-
return base.SizeThatFits(size);
608-
}
609589
}
610590

611591
internal static string ResolveMsAppDataUri(Uri uri)

0 commit comments

Comments
 (0)