From 60609482585e3fcadf40781fed8e44c5bba827e8 Mon Sep 17 00:00:00 2001 From: redth Date: Tue, 28 Nov 2023 12:12:26 -0500 Subject: [PATCH 1/5] Add missing package refs --- Sample/VirtualListViewSample/VirtualListViewSample.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sample/VirtualListViewSample/VirtualListViewSample.csproj b/Sample/VirtualListViewSample/VirtualListViewSample.csproj index 0904335..5db89e0 100644 --- a/Sample/VirtualListViewSample/VirtualListViewSample.csproj +++ b/Sample/VirtualListViewSample/VirtualListViewSample.csproj @@ -27,6 +27,8 @@ + + From da610dd6aa42bd20f5e57c06b49d38363948295b Mon Sep 17 00:00:00 2001 From: redth Date: Tue, 28 Nov 2023 12:12:36 -0500 Subject: [PATCH 2/5] Use targetSdkVersion 34 for android --- .../VirtualListViewSample/Platforms/Android/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sample/VirtualListViewSample/Platforms/Android/AndroidManifest.xml b/Sample/VirtualListViewSample/Platforms/Android/AndroidManifest.xml index 7b7d77a..e064787 100644 --- a/Sample/VirtualListViewSample/Platforms/Android/AndroidManifest.xml +++ b/Sample/VirtualListViewSample/Platforms/Android/AndroidManifest.xml @@ -1,6 +1,6 @@ - + From 76db7a1a2de2bd6f7f389ea1b2f4bc92dad33c13 Mon Sep 17 00:00:00 2001 From: redth Date: Tue, 28 Nov 2023 12:12:49 -0500 Subject: [PATCH 3/5] Fix nullable warning --- .../Platforms/Android/VirtualListViewHandler.android.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VirtualListView/Platforms/Android/VirtualListViewHandler.android.cs b/VirtualListView/Platforms/Android/VirtualListViewHandler.android.cs index 295a76c..2c2b009 100644 --- a/VirtualListView/Platforms/Android/VirtualListViewHandler.android.cs +++ b/VirtualListView/Platforms/Android/VirtualListViewHandler.android.cs @@ -90,7 +90,7 @@ public static void MapViewSelector(VirtualListViewHandler handler, IVirtualListV public static void MapSelectionMode(VirtualListViewHandler handler, IVirtualListView virtualListView) { } - public static void MapInvalidateData(VirtualListViewHandler handler, IVirtualListView virtualListView, object? parameter) + public static void MapInvalidateData(VirtualListViewHandler handler, IVirtualListView virtualListView, object parameter) => handler.InvalidateData(); void PlatformUpdateItemSelection(ItemPosition itemPosition, bool selected) From 515ae01fbb169e603b679989362c1419a6c0469c Mon Sep 17 00:00:00 2001 From: redth Date: Tue, 28 Nov 2023 12:13:00 -0500 Subject: [PATCH 4/5] Start tackling memory analyzer warnings --- .../Apple/CvCell.ios.maccatalyst.cs | 111 ++++++++++++---- .../Apple/CvDataSource.ios.maccatalyst.cs | 24 ++-- .../Apple/CvDelegate.ios.maccatalyst.cs | 13 +- .../VirtualListViewHandler.ios.maccatalyst.cs | 122 ++++++++++-------- VirtualListView/VirtualListView.csproj | 1 + 5 files changed, 174 insertions(+), 97 deletions(-) diff --git a/VirtualListView/Apple/CvCell.ios.maccatalyst.cs b/VirtualListView/Apple/CvCell.ios.maccatalyst.cs index d8ec7e0..63fc3ce 100644 --- a/VirtualListView/Apple/CvCell.ios.maccatalyst.cs +++ b/VirtualListView/Apple/CvCell.ios.maccatalyst.cs @@ -1,6 +1,7 @@ using CoreGraphics; using Foundation; using Microsoft.Maui.Platform; +using System.Diagnostics.CodeAnalysis; using UIKit; namespace Microsoft.Maui; @@ -9,11 +10,11 @@ internal class CvCell : UICollectionViewCell { public VirtualListViewHandler Handler { get; set; } - public NSIndexPath IndexPath { get; set; } + public WeakReference IndexPath { get; set; } public PositionInfo PositionInfo { get; set; } - public Action ReuseCallback { get; set; } + public WeakReference> ReuseCallback { get; set; } [Export("initWithFrame:")] public CvCell(CGRect frame) : base(frame) @@ -21,15 +22,29 @@ public CvCell(CGRect frame) : base(frame) this.ContentView.AddGestureRecognizer(new UITapGestureRecognizer(() => InvokeTap())); } - public Action TapHandler { get; set; } + public TapHandlerProxy TapHandler { get; set; } - UIKeyCommand[] keyCommands; + WeakReference keyCommands; - public override UIKeyCommand[] KeyCommands => keyCommands ??= new[] + public override UIKeyCommand[] KeyCommands { - UIKeyCommand.Create(new NSString("\r"), 0, new ObjCRuntime.Selector("keyCommandSelect")), - UIKeyCommand.Create(new NSString(" "), 0, new ObjCRuntime.Selector("keyCommandSelect")), - }; + get + { + if (keyCommands?.TryGetTarget(out var commands) ?? false) + return commands; + + var v = new[] + { + UIKeyCommand.Create(new NSString("\r"), 0, new ObjCRuntime.Selector("keyCommandSelect")), + UIKeyCommand.Create(new NSString(" "), 0, new ObjCRuntime.Selector("keyCommandSelect")), + }; + + keyCommands = new WeakReference(v); + + return v; + } + + } [Export("keyCommandSelect")] public void KeyCommandSelect() @@ -43,51 +58,93 @@ void InvokeTap() TapHandler?.Invoke(this); } + public void UpdateSelected(bool selected) + { + PositionInfo.IsSelected = selected; + + if (VirtualView?.TryGetTarget(out var virtualView) ?? false) + { + if (virtualView is IPositionInfo positionInfo) + { + positionInfo.IsSelected = selected; + virtualView.Handler?.UpdateValue(nameof(PositionInfo.IsSelected)); + } + } + } + public override UICollectionViewLayoutAttributes PreferredLayoutAttributesFittingAttributes(UICollectionViewLayoutAttributes layoutAttributes) { - if (NativeView == null || VirtualView == null) - return layoutAttributes; + if ((NativeView is not null && NativeView.TryGetTarget(out var _)) + && (VirtualView is not null && VirtualView.TryGetTarget(out var virtualView))) + { + var measure = virtualView.Measure(layoutAttributes.Size.Width, double.PositiveInfinity); - var measure = VirtualView.Measure(layoutAttributes.Size.Width, double.PositiveInfinity); + layoutAttributes.Frame = new CGRect(0, layoutAttributes.Frame.Y, layoutAttributes.Frame.Width, measure.Height); - layoutAttributes.Frame = new CGRect(0, layoutAttributes.Frame.Y, layoutAttributes.Frame.Width, measure.Height); + return layoutAttributes; + } return layoutAttributes; } public bool NeedsView - => NativeView == null; + => NativeView == null || !NativeView.TryGetTarget(out var _); - public IView VirtualView { get; private set; } + public WeakReference VirtualView { get; set; } - public UIView NativeView { get; private set; } + public WeakReference NativeView { get; set; } public override void PrepareForReuse() { base.PrepareForReuse(); // TODO: Recycle - if (VirtualView != null) - ReuseCallback?.Invoke(VirtualView); + if ((VirtualView?.TryGetTarget(out var virtualView) ?? false) + && (ReuseCallback?.TryGetTarget(out var reuseCallback) ?? false)) + { + reuseCallback?.Invoke(virtualView); + } } public void SwapView(IView newView) { - if (VirtualView == null || VirtualView.Handler == null || NativeView == null) + // Create a new platform native view if we don't have one yet + if (!(NativeView?.TryGetTarget(out var nativeView) ?? false)) + { + nativeView = newView.ToPlatform(this.Handler.MauiContext); + nativeView.Frame = this.ContentView.Frame; + nativeView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth; + this.ContentView.AddSubview(nativeView); + NativeView = new WeakReference(nativeView); + } + + // Create a new virtual view if we don't have one yet + if (!(VirtualView?.TryGetTarget(out var virtualView) ?? false) || (virtualView?.Handler is null)) { - NativeView = newView.ToPlatform(this.Handler.MauiContext); - VirtualView = newView; - NativeView.Frame = this.ContentView.Frame; - NativeView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth; - this.ContentView.AddSubview(NativeView); + virtualView = newView; + VirtualView = new WeakReference(virtualView); } else { - var handler = VirtualView.Handler; - VirtualView.Handler = null; + var handler = virtualView.Handler; + virtualView.Handler = null; newView.Handler = handler; handler.SetVirtualView(newView); - VirtualView = newView; + VirtualView.SetTarget(newView); } - } + } + + public class TapHandlerProxy + { + public TapHandlerProxy(Action tapHandler) + { + TapHandler = tapHandler; + } + + public Action TapHandler { get; set; } + + public void Invoke(CvCell cell) + => TapHandler?.Invoke(cell); + + } } \ No newline at end of file diff --git a/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs b/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs index 31eeca0..24f807b 100644 --- a/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs +++ b/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs @@ -40,12 +40,15 @@ public override UICollectionViewCell GetCell(UICollectionView collectionView, NS }; var cell = collectionView.DequeueReusableCell(nativeReuseId, indexPath) as CvCell; - cell.TapHandler = TapCellHandler; + cell.TapHandler = new CvCell.TapHandlerProxy(TapCellHandler); cell.Handler = Handler; - cell.IndexPath = indexPath; - - cell.ReuseCallback = rv => - Handler.VirtualView.ViewSelector.ViewDetached(info, cell.VirtualView); + cell.IndexPath = new WeakReference(indexPath); + + cell.ReuseCallback = new WeakReference>((rv) => + { + if (cell?.VirtualView?.TryGetTarget(out var cellVirtualView) ?? false) + Handler.VirtualView.ViewSelector.ViewDetached(info, cellVirtualView); + }); if (info.SectionIndex < 0 || info.ItemIndex < 0) info.IsSelected = false; @@ -60,12 +63,15 @@ public override UICollectionViewCell GetCell(UICollectionView collectionView, NS cell.PositionInfo = info; - if (cell.VirtualView is IPositionInfo viewPositionInfo) - viewPositionInfo.IsSelected = info.IsSelected; + if (cell.VirtualView.TryGetTarget(out var cellVirtualView)) + { + if (cellVirtualView is IPositionInfo viewPositionInfo) + viewPositionInfo.IsSelected = info.IsSelected; - Handler?.PositionalViewSelector?.ViewSelector?.RecycleView(info, data, cell.VirtualView); + Handler?.PositionalViewSelector?.ViewSelector?.RecycleView(info, data, cellVirtualView); - Handler.VirtualView.ViewSelector.ViewAttached(info, cell.VirtualView); + Handler.VirtualView.ViewSelector.ViewAttached(info, cellVirtualView); + } return cell; } diff --git a/VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs b/VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs index 9495bdf..c67e3f9 100644 --- a/VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs +++ b/VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs @@ -10,13 +10,13 @@ public CvDelegate(VirtualListViewHandler handler, UICollectionView collectionVie : base() { Handler = handler; - NativeCollectionView = collectionView; + NativeCollectionView = new WeakReference(collectionView); } - internal readonly UICollectionView NativeCollectionView; + internal readonly WeakReference NativeCollectionView; internal readonly VirtualListViewHandler Handler; - public Action ScrollHandler { get; set; } + public WeakReference> ScrollHandler { get; set; } public override void ItemSelected(UICollectionView collectionView, NSIndexPath indexPath) => HandleSelection(collectionView, indexPath, true); @@ -31,7 +31,7 @@ void HandleSelection(UICollectionView collectionView, NSIndexPath indexPath, boo if ((selectedCell?.PositionInfo?.Kind ?? PositionKind.Header) == PositionKind.Item) { - selectedCell.PositionInfo.IsSelected = selected; + selectedCell.UpdateSelected(selected); var itemPos = new ItemPosition( selectedCell.PositionInfo.SectionIndex, @@ -45,7 +45,10 @@ void HandleSelection(UICollectionView collectionView, NSIndexPath indexPath, boo } public override void Scrolled(UIScrollView scrollView) - => ScrollHandler?.Invoke(scrollView.ContentOffset.X, scrollView.ContentOffset.Y); + { + if (ScrollHandler?.TryGetTarget(out var handler) ?? false) + handler?.Invoke(scrollView.ContentOffset.X, scrollView.ContentOffset.Y); + } public override bool ShouldSelectItem(UICollectionView collectionView, NSIndexPath indexPath) => IsRealItem(indexPath); diff --git a/VirtualListView/Apple/VirtualListViewHandler.ios.maccatalyst.cs b/VirtualListView/Apple/VirtualListViewHandler.ios.maccatalyst.cs index 5062a17..38d698d 100644 --- a/VirtualListView/Apple/VirtualListViewHandler.ios.maccatalyst.cs +++ b/VirtualListView/Apple/VirtualListViewHandler.ios.maccatalyst.cs @@ -1,4 +1,5 @@ -using CoreGraphics; +#nullable enable +using CoreGraphics; using Foundation; using Microsoft.Maui.Handlers; using Microsoft.Maui.Platform; @@ -8,11 +9,10 @@ namespace Microsoft.Maui; public partial class VirtualListViewHandler : ViewHandler { - CvDataSource dataSource; - CvLayout layout; - CvDelegate cvdelegate; - UICollectionView collectionView; - UIRefreshControl refreshControl; + CvDataSource? dataSource; + CvLayout? layout; + CvDelegate? cvdelegate; + UIRefreshControl? refreshControl; protected override UICollectionView CreatePlatformView() { @@ -29,7 +29,7 @@ protected override UICollectionView CreatePlatformView() layout.MinimumInteritemSpacing = 0f; layout.MinimumLineSpacing = 0f; - collectionView = new UICollectionView(CGRect.Empty, layout); + var collectionView = new UICollectionView(CGRect.Empty, layout); //collectionView.ContentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentBehavior.Never; collectionView.AllowsMultipleSelection = false; collectionView.AllowsSelection = false; @@ -61,41 +61,51 @@ protected override void ConnectHandler(UICollectionView nativeView) dataSource = new CvDataSource(this); - cvdelegate = new CvDelegate(this, collectionView); - cvdelegate.ScrollHandler = (x, y) => - VirtualView?.Scrolled(x, y); + cvdelegate = new CvDelegate(this, nativeView); + cvdelegate.ScrollHandler = new WeakReference>((x, y) => + VirtualView?.Scrolled(x, y)); - collectionView.DataSource = dataSource; - collectionView.Delegate = cvdelegate; + nativeView.DataSource = dataSource; + nativeView.Delegate = cvdelegate; - collectionView.ReloadData(); + nativeView.ReloadData(); } protected override void DisconnectHandler(UICollectionView nativeView) { - collectionView.DataSource = null; - dataSource.Dispose(); - dataSource = null; + if (dataSource is not null) + { + dataSource.Dispose(); + dataSource = null; + } + + if (cvdelegate is not null) + { + cvdelegate.Dispose(); + cvdelegate = null; + } - collectionView.Delegate = null; - cvdelegate.Dispose(); - cvdelegate = null; + if (refreshControl is not null) + { + refreshControl.RemoveFromSuperview(); + refreshControl.Dispose(); + refreshControl = null; + } - refreshControl.RemoveFromSuperview(); - refreshControl.Dispose(); - refreshControl = null; - collectionView.Dispose(); - collectionView = null; - - layout.Dispose(); - layout = null; + nativeView.Dispose(); + + if (layout is not null) + { + layout.Dispose(); + layout = null; + } base.DisconnectHandler(nativeView); } - internal CvCell GetCell(NSIndexPath indexPath) - => dataSource?.GetCell(collectionView, indexPath) as CvCell; + internal CvCell? GetCell(NSIndexPath indexPath) + => dataSource?.GetCell(PlatformView, indexPath) as CvCell; public static void MapHeader(VirtualListViewHandler handler, IVirtualListView virtualListView) => handler?.InvalidateData(); @@ -120,43 +130,42 @@ void PlatformUpdateItemSelection(ItemPosition itemPosition, bool selected) if (realIndex < 0) return; - var cell = collectionView.CellForItem(NSIndexPath.FromItemSection(realIndex, 0)); + var cell = PlatformView.CellForItem(NSIndexPath.FromItemSection(realIndex, 0)); if (cell is CvCell cvcell) { - cvcell.PositionInfo.IsSelected = selected; - - if (cvcell.VirtualView is IPositionInfo positionInfo) + PlatformView.InvokeOnMainThread(() => { - collectionView.InvokeOnMainThread(() => - { - positionInfo.IsSelected = selected; - }); - } + cvcell.UpdateSelected(selected); + }); } } public static void MapOrientation(VirtualListViewHandler handler, IVirtualListView virtualListView) { - handler.layout.ScrollDirection = virtualListView.Orientation switch + if (handler.layout is not null) { - ListOrientation.Vertical => UICollectionViewScrollDirection.Vertical, - ListOrientation.Horizontal => UICollectionViewScrollDirection.Horizontal, - _ => UICollectionViewScrollDirection.Vertical - }; + handler.layout.ScrollDirection = virtualListView.Orientation switch + { + ListOrientation.Vertical => UICollectionViewScrollDirection.Vertical, + ListOrientation.Horizontal => UICollectionViewScrollDirection.Horizontal, + _ => UICollectionViewScrollDirection.Vertical + }; + } - handler?.InvalidateData(); + handler.InvalidateData(); } public static void MapRefreshAccentColor(VirtualListViewHandler handler, IVirtualListView virtualListView) { - if (virtualListView.RefreshAccentColor is not null) + if (virtualListView.RefreshAccentColor is not null && handler.refreshControl is not null) handler.refreshControl.TintColor = virtualListView.RefreshAccentColor.ToPlatform(); } public static void MapIsRefreshEnabled(VirtualListViewHandler handler, IVirtualListView virtualListView) { - handler.refreshControl.Enabled = virtualListView.IsRefreshEnabled; + if (handler.refreshControl is not null) + handler.refreshControl.Enabled = virtualListView.IsRefreshEnabled; } @@ -167,25 +176,26 @@ public static void MapEmptyView(VirtualListViewHandler handler, IVirtualListView void UpdateEmptyViewVisibility() { - if (collectionView is not null && collectionView.BackgroundView is not null) + if (PlatformView is not null && PlatformView.BackgroundView is not null) { var visibility = ShouldShowEmptyView ? Visibility.Visible : Visibility.Collapsed; - collectionView.BackgroundView?.UpdateVisibility(visibility); + PlatformView.BackgroundView?.UpdateVisibility(visibility); } } void UpdateEmptyView() { - if (collectionView is not null) + if (PlatformView is not null) { - if (collectionView.BackgroundView is not null) + if (PlatformView.BackgroundView is not null) { - collectionView.BackgroundView.RemoveFromSuperview(); - collectionView.BackgroundView.Dispose(); + PlatformView.BackgroundView.RemoveFromSuperview(); + PlatformView.BackgroundView.Dispose(); } - collectionView.BackgroundView = VirtualView?.EmptyView?.ToPlatform(MauiContext); + if (MauiContext is not null) + PlatformView.BackgroundView = VirtualView?.EmptyView?.ToPlatform(MauiContext); UpdateEmptyViewVisibility(); } @@ -198,9 +208,9 @@ public void InvalidateData() UpdateEmptyViewVisibility(); - collectionView?.SetNeedsLayout(); - collectionView?.ReloadData(); - collectionView?.LayoutIfNeeded(); + PlatformView?.SetNeedsLayout(); + PlatformView?.ReloadData(); + PlatformView?.LayoutIfNeeded(); }); } diff --git a/VirtualListView/VirtualListView.csproj b/VirtualListView/VirtualListView.csproj index d24bbc6..04f3bce 100644 --- a/VirtualListView/VirtualListView.csproj +++ b/VirtualListView/VirtualListView.csproj @@ -35,6 +35,7 @@ + From 3fb3bd8a9b80351d02c9eb18896f6e38a40af874 Mon Sep 17 00:00:00 2001 From: redth Date: Tue, 28 Nov 2023 13:53:10 -0500 Subject: [PATCH 5/5] Remove proxy class, use weakref --- .../Apple/CvCell.ios.maccatalyst.cs | 21 +++++-------------- .../Apple/CvDataSource.ios.maccatalyst.cs | 2 +- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/VirtualListView/Apple/CvCell.ios.maccatalyst.cs b/VirtualListView/Apple/CvCell.ios.maccatalyst.cs index 63fc3ce..0fdc3d8 100644 --- a/VirtualListView/Apple/CvCell.ios.maccatalyst.cs +++ b/VirtualListView/Apple/CvCell.ios.maccatalyst.cs @@ -22,7 +22,7 @@ public CvCell(CGRect frame) : base(frame) this.ContentView.AddGestureRecognizer(new UITapGestureRecognizer(() => InvokeTap())); } - public TapHandlerProxy TapHandler { get; set; } + public WeakReference> TapHandler { get; set; } WeakReference keyCommands; @@ -55,7 +55,10 @@ public void KeyCommandSelect() void InvokeTap() { if (PositionInfo.Kind == PositionKind.Item) - TapHandler?.Invoke(this); + { + if (TapHandler?.TryGetTarget(out var handler) ?? false) + handler?.Invoke(this); + } } public void UpdateSelected(bool selected) @@ -133,18 +136,4 @@ public void SwapView(IView newView) VirtualView.SetTarget(newView); } } - - public class TapHandlerProxy - { - public TapHandlerProxy(Action tapHandler) - { - TapHandler = tapHandler; - } - - public Action TapHandler { get; set; } - - public void Invoke(CvCell cell) - => TapHandler?.Invoke(cell); - - } } \ No newline at end of file diff --git a/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs b/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs index 24f807b..96119e4 100644 --- a/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs +++ b/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs @@ -40,7 +40,7 @@ public override UICollectionViewCell GetCell(UICollectionView collectionView, NS }; var cell = collectionView.DequeueReusableCell(nativeReuseId, indexPath) as CvCell; - cell.TapHandler = new CvCell.TapHandlerProxy(TapCellHandler); + cell.TapHandler = new WeakReference>(TapCellHandler); cell.Handler = Handler; cell.IndexPath = new WeakReference(indexPath);