Skip to content

Commit 1f9d73a

Browse files
committed
Fix VirtualScroll memory leaks
1 parent 0de1e69 commit 1f9d73a

3 files changed

Lines changed: 112 additions & 49 deletions

File tree

Source/Nalu.Maui.VirtualScroll/Platforms/Apple/VirtualScrollCell.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public UIView? NativeView
4444
private set => _nativeView = value;
4545
}
4646

47-
public new VirtualScrollCellContent ContentView { get; }
47+
public new VirtualScrollCellContent ContentView { get; private set; }
4848

4949
[Export("initWithFrame:")]
5050
public VirtualScrollCell(CGRect frame) : base(frame)
@@ -195,4 +195,18 @@ public override void PrepareForReuse()
195195
_readyForReuse = true;
196196
_needsMeasure = false;
197197
}
198+
199+
protected override void Dispose(bool disposing)
200+
{
201+
base.Dispose(disposing);
202+
203+
if (disposing)
204+
{
205+
ItemsLayout = null;
206+
VirtualView = null;
207+
NativeView = null;
208+
SupplementaryType = null;
209+
IndexPath = null;
210+
}
211+
}
198212
}

Source/Nalu.Maui.VirtualScroll/Platforms/Apple/VirtualScrollCollectionView.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ public class VirtualScrollCollectionView : UICollectionView, IVirtualScrollCells
2222
, Microsoft.Maui.Platform.IPlatformMeasureInvalidationController
2323
#endif
2424
{
25-
private readonly IVirtualScrollLayoutInfo _layoutInfo;
26-
private readonly VirtualScrollCellManager<VirtualScrollCell> _cellManager = new(cell => cell.VirtualView);
25+
private IVirtualScrollLayoutInfo _layoutInfo;
26+
private VirtualScrollCellManager<VirtualScrollCell> _cellManager = new(cell => cell.VirtualView);
2727
private readonly List<NSIndexPath> _invalidatedGlobalHeaders = new(2);
2828
private readonly List<NSIndexPath> _invalidatedGlobalFooters = new(2);
2929
private readonly List<NSIndexPath> _invalidatedSectionHeaders = new(8);
@@ -206,6 +206,13 @@ protected override void Dispose(bool disposing)
206206
if (disposing)
207207
{
208208
_cellManager.Dispose();
209+
_cellManager = null!;
210+
_layoutInfo = null!;
211+
_invalidatedGlobalFooters.Clear();
212+
_invalidatedGlobalHeaders.Clear();
213+
_invalidatedSectionFooters.Clear();
214+
_invalidatedSectionHeaders.Clear();
215+
_invalidatedPaths.Clear();
209216
}
210217
}
211218

Source/Nalu.Maui.VirtualScroll/Platforms/Apple/VirtualScrollHandler.cs

Lines changed: 88 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,6 @@ protected override UIView CreatePlatformView()
111111

112112
layoutSetup.ConfigureCollectionView(collectionView);
113113

114-
_dragGestureRecognizer = CreateDragGestureRecognizer(virtualScroll, collectionView);
115-
116114
_reuseIdManager = reuseIdManager;
117115
_collectionView = collectionView;
118116

@@ -170,49 +168,60 @@ private void RefreshControlEventHandler(object? sender, EventArgs e)
170168
}
171169
}
172170

173-
private static UILongPressGestureRecognizer CreateDragGestureRecognizer(IVirtualScroll virtualView, VirtualScrollCollectionView collectionView)
174-
=> new(gesture =>
171+
private static UILongPressGestureRecognizer CreateDragGestureRecognizer()
172+
=> new(HandleLongPress);
173+
174+
// ReSharper disable once MemberCanBeMadeStatic.Local
175+
private static void HandleLongPress(UILongPressGestureRecognizer gesture)
176+
{
177+
if (gesture.View is not VirtualScrollCollectionView cv)
178+
{
179+
return;
180+
}
181+
182+
switch (gesture.State)
183+
{
184+
case UIGestureRecognizerState.Began:
175185
{
176-
switch (gesture.State)
186+
var location = gesture.LocationInView(cv);
187+
var indexPath = cv.IndexPathForItemAtPoint(location);
188+
189+
if (indexPath is not null && cv.CellForItem(indexPath) is { } cell)
177190
{
178-
case UIGestureRecognizerState.Began:
179-
{
180-
var location = gesture.LocationInView(collectionView);
181-
var indexPath = collectionView.IndexPathForItemAtPoint(location);
182-
if (indexPath is not null && collectionView.CellForItem(indexPath) is { } cell)
183-
{
184-
((VirtualScrollDelegate)collectionView.Delegate).ItemDragInitiating(indexPath);
185-
indexPath = collectionView.IndexPathForCell(cell);
186-
if (indexPath is not null)
187-
{
188-
collectionView.BeginInteractiveMovementForItem(indexPath);
189-
}
190-
}
191-
192-
break;
193-
}
194-
case UIGestureRecognizerState.Changed:
195-
{
196-
var location = gesture.LocationInView(collectionView);
197-
collectionView.UpdateInteractiveMovement(location);
191+
((VirtualScrollDelegate) cv.Delegate).ItemDragInitiating(indexPath);
192+
indexPath = cv.IndexPathForCell(cell);
198193

199-
break;
200-
}
201-
case UIGestureRecognizerState.Ended:
202-
{
203-
collectionView.EndInteractiveMovement();
204-
((VirtualScrollDelegate)collectionView.Delegate).ItemDragEnded();
205-
break;
206-
}
207-
default:
194+
if (indexPath is not null)
208195
{
209-
collectionView.CancelInteractiveMovement();
210-
((VirtualScrollDelegate)collectionView.Delegate).ItemDragEnded();
211-
break;
196+
cv.BeginInteractiveMovementForItem(indexPath);
212197
}
213198
}
199+
200+
break;
214201
}
215-
);
202+
case UIGestureRecognizerState.Changed:
203+
{
204+
var location = gesture.LocationInView(cv);
205+
cv.UpdateInteractiveMovement(location);
206+
207+
break;
208+
}
209+
case UIGestureRecognizerState.Ended:
210+
{
211+
cv.EndInteractiveMovement();
212+
((VirtualScrollDelegate) cv.Delegate).ItemDragEnded();
213+
214+
break;
215+
}
216+
default:
217+
{
218+
cv.CancelInteractiveMovement();
219+
((VirtualScrollDelegate) cv.Delegate).ItemDragEnded();
220+
221+
break;
222+
}
223+
}
224+
}
216225

217226
private void OnBoundsChanged(object? sender, EventArgs e)
218227
{
@@ -316,9 +325,14 @@ protected override void DisconnectHandler(UIView platformView)
316325
_delegate?.Dispose();
317326
_delegate = null;
318327

328+
if (_dragGestureRecognizer is not null)
329+
{
330+
_collectionView?.RemoveGestureRecognizer(_dragGestureRecognizer);
331+
_dragGestureRecognizer.Dispose();
332+
_dragGestureRecognizer = null;
333+
}
319334
_collectionView?.Dispose();
320335
_collectionView = null;
321-
_dragGestureRecognizer?.Dispose();
322336
_containerView?.Dispose();
323337
_containerView = null;
324338
_lastBounds = CGSize.Empty;
@@ -336,16 +350,17 @@ public static void MapDragHandler(VirtualScrollHandler handler, IVirtualScroll v
336350
{
337351
var collectionView = handler.PlatformCollectionView;
338352
var isDragEnabled = virtualScroll.DragHandler is not null;
339-
var dragGestureRecognizer = handler._dragGestureRecognizer ?? throw new InvalidOperationException("Drag gesture recognizer is not initialized.");
340-
353+
341354
if (isDragEnabled)
342355
{
343-
344-
collectionView.AddGestureRecognizer(dragGestureRecognizer);
356+
handler._dragGestureRecognizer ??= CreateDragGestureRecognizer();
357+
collectionView.AddGestureRecognizer(handler._dragGestureRecognizer);
345358
}
346-
else
359+
else if (handler._dragGestureRecognizer is not null)
347360
{
348-
collectionView.RemoveGestureRecognizer(dragGestureRecognizer);
361+
collectionView.RemoveGestureRecognizer(handler._dragGestureRecognizer);
362+
handler._dragGestureRecognizer.Dispose();
363+
handler._dragGestureRecognizer = null;
349364
}
350365
}
351366

@@ -407,10 +422,37 @@ public static void MapLayout(VirtualScrollHandler handler, IVirtualScroll virtua
407422
handler._delegate?.UpdateFadingEdge(handler._collectionView);
408423
}
409424
}
425+
426+
private struct VirtualScrollLayoutInfo : IVirtualScrollLayoutInfo
427+
{
428+
public VirtualScrollLayoutInfo(IVirtualScrollLayoutInfo virtualScroll)
429+
{
430+
HasGlobalFooter = virtualScroll.HasGlobalFooter;
431+
HasGlobalHeader = virtualScroll.HasGlobalHeader;
432+
HasSectionFooter = virtualScroll.HasSectionFooter;
433+
HasSectionHeader = virtualScroll.HasSectionHeader;
434+
}
435+
436+
public bool Equals(IVirtualScrollLayoutInfo? other) =>
437+
other != null &&
438+
HasGlobalFooter == other.HasGlobalFooter &&
439+
HasGlobalHeader == other.HasGlobalHeader &&
440+
HasSectionFooter == other.HasSectionFooter &&
441+
HasSectionHeader == other.HasSectionHeader;
442+
443+
444+
public bool HasGlobalHeader { get; }
445+
public bool HasGlobalFooter { get; }
446+
public bool HasSectionHeader { get; }
447+
public bool HasSectionFooter { get; }
448+
}
410449

411450
private static VirtualScrollCollectionViewLayoutSetup CreatePlatformLayout(VirtualScrollHandler handler, IVirtualScroll virtualScroll)
412451
{
413-
var layoutInfo = virtualScroll as IVirtualScrollLayoutInfo ?? throw new InvalidOperationException("The provided IVirtualScroll does not implement IVirtualScrollLayoutInfo interface.");
452+
var layoutInfo = new VirtualScrollLayoutInfo(
453+
virtualScroll as IVirtualScrollLayoutInfo ??
454+
throw new InvalidOperationException("The provided IVirtualScroll does not implement IVirtualScrollLayoutInfo interface.")
455+
);
414456

415457
var layoutSetup = virtualScroll.ItemsLayout switch
416458
{

0 commit comments

Comments
 (0)