This repository was archived by the owner on May 1, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
Copy pathGroupableItemsViewController.cs
303 lines (248 loc) · 9.23 KB
/
GroupableItemsViewController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
using System;
using CoreGraphics;
using Foundation;
using UIKit;
namespace Xamarin.Forms.Platform.iOS
{
public class GroupableItemsViewController<TItemsView> : SelectableItemsViewController<TItemsView>
where TItemsView : GroupableItemsView
{
// Keep a cached value for the current state of grouping around so we can avoid hitting the
// BindableProperty all the time
bool _isGrouped;
// Keep out header measurement cells for iOS handy so we don't have to
// create new ones all the time. For other versions, the reusable cells
// queueing mechanism does this for us.
TemplatedCell _measurementCellTemplated;
DefaultCell _measurementCellDefault;
Action _scrollAnimationEndedCallback;
public GroupableItemsViewController(TItemsView groupableItemsView, ItemsViewLayout layout)
: base(groupableItemsView, layout)
{
_isGrouped = ItemsView.IsGrouped;
}
protected override UICollectionViewDelegateFlowLayout CreateDelegator()
{
return new GroupableItemsViewDelegator<TItemsView, GroupableItemsViewController<TItemsView>>(ItemsViewLayout, this);
}
protected override IItemsViewSource CreateItemsViewSource()
{
// Use the BindableProperty here (instead of _isGroupingEnabled) because the cached value might not be set yet
if (ItemsView.IsGrouped)
{
return ItemsSourceFactory.CreateGrouped(ItemsView.ItemsSource, this);
}
return base.CreateItemsViewSource();
}
public override void UpdateItemsSource()
{
_isGrouped = ItemsView.IsGrouped;
base.UpdateItemsSource();
}
protected override void RegisterViewTypes()
{
base.RegisterViewTypes();
RegisterSupplementaryViews(UICollectionElementKindSection.Header);
RegisterSupplementaryViews(UICollectionElementKindSection.Footer);
}
void RegisterSupplementaryViews(UICollectionElementKindSection kind)
{
CollectionView.RegisterClassForSupplementaryView(typeof(HorizontalSupplementaryView),
kind, HorizontalSupplementaryView.ReuseId);
CollectionView.RegisterClassForSupplementaryView(typeof(VerticalSupplementaryView),
kind, VerticalSupplementaryView.ReuseId);
CollectionView.RegisterClassForSupplementaryView(typeof(HorizontalDefaultSupplementalView),
kind, HorizontalDefaultSupplementalView.ReuseId);
CollectionView.RegisterClassForSupplementaryView(typeof(VerticalDefaultSupplementalView),
kind, VerticalDefaultSupplementalView.ReuseId);
}
public override UICollectionReusableView GetViewForSupplementaryElement(UICollectionView collectionView,
NSString elementKind, NSIndexPath indexPath)
{
var reuseId = DetermineViewReuseId(elementKind);
var view = collectionView.DequeueReusableSupplementaryView(elementKind, reuseId, indexPath) as UICollectionReusableView;
switch (view)
{
case DefaultCell defaultCell:
UpdateDefaultSupplementaryView(defaultCell, elementKind, indexPath);
break;
case TemplatedCell templatedCell:
UpdateTemplatedSupplementaryView(templatedCell, elementKind, indexPath);
break;
}
return view;
}
void UpdateDefaultSupplementaryView(DefaultCell cell, NSString elementKind, NSIndexPath indexPath)
{
cell.Label.Text = ItemsSource.Group(indexPath).ToString();
if (cell is ItemsViewCell)
{
cell.ConstrainTo(GetLayoutSpanCount() * ItemsViewLayout.ConstrainedDimension);
}
}
void UpdateTemplatedSupplementaryView(TemplatedCell cell, NSString elementKind, NSIndexPath indexPath)
{
DataTemplate template = elementKind == UICollectionElementKindSectionKey.Header
? ItemsView.GroupHeaderTemplate
: ItemsView.GroupFooterTemplate;
var bindingContext = ItemsSource.Group(indexPath);
cell.Bind(template, bindingContext, ItemsView);
if (cell is ItemsViewCell)
{
cell.ConstrainTo(GetLayoutSpanCount() * ItemsViewLayout.ConstrainedDimension);
}
}
string DetermineViewReuseId(NSString elementKind)
{
return DetermineViewReuseId(elementKind == UICollectionElementKindSectionKey.Header
? ItemsView.GroupHeaderTemplate
: ItemsView.GroupFooterTemplate);
}
string DetermineViewReuseId(DataTemplate template)
{
if (template == null)
{
// No template, fall back the the default supplemental views
return ItemsViewLayout.ScrollDirection == UICollectionViewScrollDirection.Horizontal
? HorizontalDefaultSupplementalView.ReuseId
: VerticalDefaultSupplementalView.ReuseId;
}
return ItemsViewLayout.ScrollDirection == UICollectionViewScrollDirection.Horizontal
? HorizontalSupplementaryView.ReuseId
: VerticalSupplementaryView.ReuseId;
}
internal CGSize GetReferenceSizeForHeader(UICollectionView collectionView, UICollectionViewLayout layout, nint section)
{
if (!_isGrouped)
{
return CGSize.Empty;
}
// Currently we explicitly measure all of the headers/footers
// Long-term, we might want to look at performance hints (similar to ItemSizingStrategy) for
// headers/footers (if the dev knows for sure they'll all the be the same size)
return GetReferenceSizeForheaderOrFooter(collectionView, ItemsView.GroupHeaderTemplate, UICollectionElementKindSectionKey.Header, section);
}
internal CGSize GetReferenceSizeForFooter(UICollectionView collectionView, UICollectionViewLayout layout, nint section)
{
if (!_isGrouped)
{
return CGSize.Empty;
}
return GetReferenceSizeForheaderOrFooter(collectionView, ItemsView.GroupFooterTemplate, UICollectionElementKindSectionKey.Footer, section);
}
internal CGSize GetReferenceSizeForheaderOrFooter(UICollectionView collectionView, DataTemplate template, NSString elementKind, nint section)
{
if (!_isGrouped || template == null)
{
return CGSize.Empty;
}
if (ItemsSource.GroupCount < 1 || section > ItemsSource.GroupCount - 1)
{
return CGSize.Empty;
}
if (!Forms.IsiOS11OrNewer)
{
// iOS 10 crashes if we try to dequeue a cell for measurement
// so we'll use an alternate method
return MeasureSupplementaryView(elementKind, section);
}
var cell = GetViewForSupplementaryElement(collectionView, elementKind,
NSIndexPath.FromItemSection(0, section)) as ItemsViewCell;
return cell.Measure();
}
internal void SetScrollAnimationEndedCallback(Action callback)
{
_scrollAnimationEndedCallback = callback;
}
internal void HandleScrollAnimationEnded()
{
_scrollAnimationEndedCallback?.Invoke();
_scrollAnimationEndedCallback = null;
}
int GetLayoutSpanCount()
{
var span = 1;
if (ItemsView?.ItemsLayout is GridItemsLayout gridItemsLayout)
{
span = gridItemsLayout.Span;
}
return span;
}
internal UIEdgeInsets GetInsetForSection(ItemsViewLayout itemsViewLayout,
UICollectionView collectionView, nint section)
{
var uIEdgeInsets = ItemsViewLayout.GetInsetForSection(collectionView, itemsViewLayout, section);
if (!ItemsView.IsGrouped)
{
return uIEdgeInsets;
}
// If we're grouping, we'll need to inset the sections to maintain the spacing between the
// groups and their group headers/footers
nfloat spacing = itemsViewLayout.GetMinimumLineSpacingForSection(collectionView, itemsViewLayout, section);
var top = uIEdgeInsets.Top;
var left = uIEdgeInsets.Left;
if (itemsViewLayout.ScrollDirection == UICollectionViewScrollDirection.Horizontal)
{
left += spacing;
}
else
{
top += spacing;
}
return new UIEdgeInsets(top, left,
uIEdgeInsets.Bottom, uIEdgeInsets.Right);
}
// These measurement methods are only necessary for iOS 10 and lower
CGSize MeasureTemplatedSupplementaryCell(NSString elementKind, nint section, NSString reuseId)
{
if (_measurementCellTemplated == null)
{
if (reuseId == HorizontalSupplementaryView.ReuseId)
{
_measurementCellTemplated = new HorizontalSupplementaryView(CGRect.Empty);
}
else if (reuseId == VerticalSupplementaryView.ReuseId)
{
_measurementCellTemplated = new VerticalSupplementaryView(CGRect.Empty);
}
}
if (_measurementCellTemplated == null)
{
return CGSize.Empty;
}
UpdateTemplatedSupplementaryView(_measurementCellTemplated, elementKind, NSIndexPath.FromItemSection(0, section));
return _measurementCellTemplated.Measure();
}
CGSize MeasureDefaultSupplementaryCell(NSString elementKind, nint section, NSString reuseId)
{
if (_measurementCellDefault == null)
{
if (reuseId == HorizontalDefaultSupplementalView.ReuseId)
{
_measurementCellDefault = new HorizontalDefaultSupplementalView(CGRect.Empty);
}
else if (reuseId == VerticalDefaultSupplementalView.ReuseId)
{
_measurementCellDefault = new VerticalDefaultSupplementalView(CGRect.Empty);
}
}
if (_measurementCellDefault == null)
{
return CGSize.Empty;
}
UpdateDefaultSupplementaryView(_measurementCellDefault, elementKind, NSIndexPath.FromItemSection(0, section));
return _measurementCellDefault.Measure();
}
CGSize MeasureSupplementaryView(NSString elementKind, nint section)
{
var reuseId = (NSString)DetermineViewReuseId(elementKind);
if (reuseId == HorizontalDefaultSupplementalView.ReuseId
|| reuseId == VerticalDefaultSupplementalView.ReuseId)
{
return MeasureDefaultSupplementaryCell(elementKind, section, reuseId);
}
return MeasureTemplatedSupplementaryCell(elementKind, section, reuseId);
}
// end of iOS 10 workaround stuff
}
}