diff --git a/VirtualListView/Apple/CvCell.ios.maccatalyst.cs b/VirtualListView/Apple/CvCell.ios.maccatalyst.cs index 7e0eff3..517b642 100644 --- a/VirtualListView/Apple/CvCell.ios.maccatalyst.cs +++ b/VirtualListView/Apple/CvCell.ios.maccatalyst.cs @@ -9,6 +9,8 @@ namespace Microsoft.Maui; internal class CvCell : UICollectionViewCell { + internal const string ReuseIdUnknown = "UNKNOWN"; + public VirtualListViewHandler Handler { get; set; } public WeakReference IndexPath { get; set; } diff --git a/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs b/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs index be6bf5a..2d2dd99 100644 --- a/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs +++ b/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs @@ -1,4 +1,5 @@ -using Foundation; +#nullable enable +using Foundation; using UIKit; namespace Microsoft.Maui; @@ -20,54 +21,74 @@ public CvDataSource(VirtualListViewHandler handler) public override nint NumberOfSections(UICollectionView collectionView) => 1; - + public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath) { var info = Handler?.PositionalViewSelector?.GetInfo(indexPath.Item.ToInt32()); - var data = Handler?.PositionalViewSelector?.Adapter?.DataFor(info.Kind, info.SectionIndex, info.ItemIndex); - - var reuseId = Handler?.PositionalViewSelector?.ViewSelector?.GetReuseId(info, data); + object? data = null; - var nativeReuseId = info.Kind switch + var nativeReuseId = CvCell.ReuseIdUnknown; + + if (info is not null) { - PositionKind.Item => itemIdManager.GetReuseId(collectionView, reuseId), - PositionKind.SectionHeader => sectionHeaderIdManager.GetReuseId(collectionView, reuseId), - PositionKind.SectionFooter => sectionFooterIdManager.GetReuseId(collectionView, reuseId), - PositionKind.Header => globalIdManager.GetReuseId(collectionView, reuseId), - PositionKind.Footer => globalIdManager.GetReuseId(collectionView, reuseId), - _ => "UNKNOWN", - }; - - var cell = (collectionView.DequeueReusableCell(nativeReuseId, indexPath) as CvCell)!; + data = Handler?.PositionalViewSelector?.Adapter?.DataFor(info.Kind, info.SectionIndex, info.ItemIndex); + + if (data is not null) + { + var reuseId = Handler?.PositionalViewSelector?.ViewSelector?.GetReuseId(info, data); + + nativeReuseId = info.Kind switch + { + PositionKind.Item => itemIdManager.GetReuseId(collectionView, reuseId), + PositionKind.SectionHeader => sectionHeaderIdManager.GetReuseId(collectionView, reuseId), + PositionKind.SectionFooter => sectionFooterIdManager.GetReuseId(collectionView, reuseId), + PositionKind.Header => globalIdManager.GetReuseId(collectionView, reuseId), + PositionKind.Footer => globalIdManager.GetReuseId(collectionView, reuseId), + _ => CvCell.ReuseIdUnknown, + }; + } + } + + var nativeCell = collectionView.DequeueReusableCell(nativeReuseId, indexPath); + if (nativeCell is not CvCell cell) + return (UICollectionViewCell)nativeCell; + cell.SetTapHandlerCallback(TapCellHandler); cell.Handler = Handler; 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 is not null && (cell.VirtualView?.TryGetTarget(out var cellView) ?? false)) + Handler?.VirtualView?.ViewSelector?.ViewDetached(info, cellView); }); - if (info.SectionIndex < 0 || info.ItemIndex < 0) - info.IsSelected = false; - else - info.IsSelected = Handler?.IsItemSelected(info.SectionIndex, info.ItemIndex) ?? false; + if (info is not null) + { + if (info.SectionIndex < 0 || info.ItemIndex < 0) + info.IsSelected = false; + else + info.IsSelected = Handler?.IsItemSelected(info.SectionIndex, info.ItemIndex) ?? false; + } - if (cell.NeedsView) + if (cell.NeedsView && info is not null && data is not null) { var view = Handler?.PositionalViewSelector?.ViewSelector?.CreateView(info, data); - cell.SetupView(view); + if (view is not null) + cell.SetupView(view); } - cell.UpdatePosition(info); - - if (cell.VirtualView.TryGetTarget(out var cellVirtualView)) + if (info is not null) { - Handler?.PositionalViewSelector?.ViewSelector?.RecycleView(info, data, cellVirtualView); + cell.UpdatePosition(info); + + if (data is not null && (cell.VirtualView?.TryGetTarget(out var cellVirtualView) ?? false)) + { + Handler?.PositionalViewSelector?.ViewSelector?.RecycleView(info, data, cellVirtualView); - Handler.VirtualView.ViewSelector.ViewAttached(info, cellVirtualView); + 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 c67e3f9..3870a0a 100644 --- a/VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs +++ b/VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs @@ -11,6 +11,7 @@ public CvDelegate(VirtualListViewHandler handler, UICollectionView collectionVie { Handler = handler; NativeCollectionView = new WeakReference(collectionView); + collectionView.RegisterClassForCell(typeof(CvCell), CvCell.ReuseIdUnknown); } internal readonly WeakReference NativeCollectionView; @@ -27,20 +28,22 @@ public override void ItemDeselected(UICollectionView collectionView, NSIndexPath void HandleSelection(UICollectionView collectionView, NSIndexPath indexPath, bool selected) { //UIView.AnimationsEnabled = false; - var selectedCell = collectionView.CellForItem(indexPath) as CvCell; - - if ((selectedCell?.PositionInfo?.Kind ?? PositionKind.Header) == PositionKind.Item) + if (collectionView.CellForItem(indexPath) is CvCell selectedCell + && (selectedCell.PositionInfo?.Kind ?? PositionKind.Header) == PositionKind.Item) { selectedCell.UpdateSelected(selected); - var itemPos = new ItemPosition( - selectedCell.PositionInfo.SectionIndex, - selectedCell.PositionInfo.ItemIndex); + if (selectedCell.PositionInfo is not null) + { + var itemPos = new ItemPosition( + selectedCell.PositionInfo.SectionIndex, + selectedCell.PositionInfo.ItemIndex); - if (selected) - Handler?.VirtualView?.SelectItem(itemPos); - else - Handler?.VirtualView?.DeselectItem(itemPos); + if (selected) + Handler?.VirtualView?.SelectItem(itemPos); + else + Handler?.VirtualView?.DeselectItem(itemPos); + } } } diff --git a/VirtualListView/Controls/VirtualListView.cs b/VirtualListView/Controls/VirtualListView.cs index fda0aa0..4eb3371 100644 --- a/VirtualListView/Controls/VirtualListView.cs +++ b/VirtualListView/Controls/VirtualListView.cs @@ -1,6 +1,6 @@ using System.Windows.Input; -using Microsoft.Maui.Adapters; - +using Microsoft.Maui.Adapters; + namespace Microsoft.Maui.Controls; public partial class VirtualListView : View, IVirtualListView, IVirtualListViewSelector @@ -30,13 +30,13 @@ public IView GlobalHeader public static readonly BindableProperty GlobalHeaderProperty = BindableProperty.Create(nameof(GlobalHeader), typeof(IView), typeof(VirtualListView), default); - public bool IsHeaderVisible - { - get => (bool)GetValue(IsHeaderVisibleProperty); - set => SetValue(IsHeaderVisibleProperty, value); - } - - public static readonly BindableProperty IsHeaderVisibleProperty = + public bool IsHeaderVisible + { + get => (bool)GetValue(IsHeaderVisibleProperty); + set => SetValue(IsHeaderVisibleProperty, value); + } + + public static readonly BindableProperty IsHeaderVisibleProperty = BindableProperty.Create(nameof(IsHeaderVisible), typeof(bool), typeof(VirtualListView), true); public IView GlobalFooter @@ -49,14 +49,14 @@ public IView GlobalFooter BindableProperty.Create(nameof(GlobalFooter), typeof(IView), typeof(VirtualListView), default); - public bool IsFooterVisible - { - get => (bool)GetValue(IsFooterVisibleProperty); - set => SetValue(IsFooterVisibleProperty, value); - } - - public static readonly BindableProperty IsFooterVisibleProperty = - BindableProperty.Create(nameof(IsFooterVisible), typeof(bool), typeof(VirtualListView), true); + public bool IsFooterVisible + { + get => (bool)GetValue(IsFooterVisibleProperty); + set => SetValue(IsFooterVisibleProperty, value); + } + + public static readonly BindableProperty IsFooterVisibleProperty = + BindableProperty.Create(nameof(IsFooterVisible), typeof(bool), typeof(VirtualListView), true); public DataTemplate ItemTemplate @@ -148,26 +148,26 @@ public ICommand RefreshCommand } public static readonly BindableProperty RefreshCommandProperty = - BindableProperty.Create(nameof(RefreshCommand), typeof(ICommand), typeof(VirtualListView), default); - - public Color RefreshAccentColor - { - get => (Color)GetValue(RefreshAccentColorProperty); - set => SetValue(RefreshAccentColorProperty, value); - } - - public static readonly BindableProperty RefreshAccentColorProperty = - BindableProperty.Create(nameof(RefreshAccentColor), typeof(Color), typeof(VirtualListView), null); - - public bool IsRefreshEnabled - { - get => (bool)GetValue(IsRefreshEnabledProperty); - set => SetValue(IsRefreshEnabledProperty, value); - } - - public static readonly BindableProperty IsRefreshEnabledProperty = - BindableProperty.Create(nameof(IsRefreshEnabled), typeof(bool), typeof(VirtualListView), false); - + BindableProperty.Create(nameof(RefreshCommand), typeof(ICommand), typeof(VirtualListView), default); + + public Color RefreshAccentColor + { + get => (Color)GetValue(RefreshAccentColorProperty); + set => SetValue(RefreshAccentColorProperty, value); + } + + public static readonly BindableProperty RefreshAccentColorProperty = + BindableProperty.Create(nameof(RefreshAccentColor), typeof(Color), typeof(VirtualListView), null); + + public bool IsRefreshEnabled + { + get => (bool)GetValue(IsRefreshEnabledProperty); + set => SetValue(IsRefreshEnabledProperty, value); + } + + public static readonly BindableProperty IsRefreshEnabledProperty = + BindableProperty.Create(nameof(IsRefreshEnabled), typeof(bool), typeof(VirtualListView), false); + public ListOrientation Orientation { get => (ListOrientation)GetValue(OrientationProperty); @@ -178,25 +178,25 @@ public ListOrientation Orientation BindableProperty.Create(nameof(Orientation), typeof(ListOrientation), typeof(VirtualListView), ListOrientation.Vertical); - public View EmptyView - { - get => (View)GetValue(EmptyViewProperty); - set => SetValue(EmptyViewProperty, value); - } - - public static readonly BindableProperty EmptyViewProperty = - BindableProperty.Create(nameof(EmptyView), typeof(View), typeof(VirtualListView), null, - propertyChanged: (bobj, oldValue, newValue) => - { - if (bobj is VirtualListView virtualListView) - { - if (oldValue is IView oldView) - virtualListView.RemoveLogicalChild(oldView); - - if (newValue is IView newView) - virtualListView.AddLogicalChild(newView); - } - }); + public View EmptyView + { + get => (View)GetValue(EmptyViewProperty); + set => SetValue(EmptyViewProperty, value); + } + + public static readonly BindableProperty EmptyViewProperty = + BindableProperty.Create(nameof(EmptyView), typeof(View), typeof(VirtualListView), null, + propertyChanged: (bobj, oldValue, newValue) => + { + if (bobj is VirtualListView virtualListView) + { + if (oldValue is IView oldView) + virtualListView.RemoveLogicalChild(oldView); + + if (newValue is IView newView) + virtualListView.AddLogicalChild(newView); + } + }); IView IVirtualListView.EmptyView => EmptyView; @@ -225,46 +225,46 @@ public ICommand ScrolledCommand { get => (ICommand)GetValue(ScrolledCommandProperty); set => SetValue(ScrolledCommandProperty, value); - } - + } + public static readonly BindableProperty SelectedItemsProperty = - BindableProperty.Create(nameof(SelectedItems), typeof(IList), typeof(VirtualListView), Array.Empty(), - propertyChanged: (bindableObj, oldValue, newValue) => - { - if (bindableObj is VirtualListView vlv - && oldValue is IList oldSelection - && newValue is IList newSelection) - { - vlv.RaiseSelectedItemsChanged(oldSelection.ToArray(), newSelection.ToArray()); - } - }); - public IList SelectedItems - { - get => (IList)GetValue(SelectedItemsProperty); - set => SetValue(SelectedItemsProperty, value ?? Array.Empty()); - } - + BindableProperty.Create(nameof(SelectedItems), typeof(IList), typeof(VirtualListView), Array.Empty(), + propertyChanged: (bindableObj, oldValue, newValue) => + { + if (bindableObj is VirtualListView vlv + && oldValue is IList oldSelection + && newValue is IList newSelection) + { + vlv.RaiseSelectedItemsChanged(oldSelection.ToArray(), newSelection.ToArray()); + } + }); + public IList SelectedItems + { + get => (IList)GetValue(SelectedItemsProperty); + set => SetValue(SelectedItemsProperty, value ?? Array.Empty()); + } + public static readonly BindableProperty SelectedItemProperty = - BindableProperty.Create(nameof(SelectedItem), typeof(ItemPosition?), typeof(VirtualListView), default, - propertyChanged: (bindableObj, oldValue, newValue) => - { - if (bindableObj is VirtualListView vlv) - { - if (newValue is null || newValue is not ItemPosition) - vlv.SelectedItems = null; - else if (newValue is ItemPosition p) - vlv.SelectedItems = new[] { p }; - } + BindableProperty.Create(nameof(SelectedItem), typeof(ItemPosition?), typeof(VirtualListView), default, + propertyChanged: (bindableObj, oldValue, newValue) => + { + if (bindableObj is VirtualListView vlv) + { + if (newValue is null || newValue is not ItemPosition) + vlv.SelectedItems = null; + else if (newValue is ItemPosition p) + vlv.SelectedItems = new[] { p }; + } }); - public ItemPosition? SelectedItem - { - get => (ItemPosition?)GetValue(SelectedItemProperty); - set => SetValue(SelectedItemProperty, value); - } - + public ItemPosition? SelectedItem + { + get => (ItemPosition?)GetValue(SelectedItemProperty); + set => SetValue(SelectedItemProperty, value); + } + - public void DeselectItem(ItemPosition itemPosition) + public void DeselectItem(ItemPosition itemPosition) { if (SelectionMode == Maui.SelectionMode.Single) { @@ -280,17 +280,17 @@ public void DeselectItem(ItemPosition itemPosition) { current.Remove(itemPosition); SelectedItems = current.ToArray(); - } - } + } + } } - public void SelectItem(ItemPosition itemPosition) + public void SelectItem(ItemPosition itemPosition) { if (SelectionMode == Maui.SelectionMode.Single) { - if (!SelectedItem.HasValue || !SelectedItem.Value.Equals(itemPosition)) - { - SelectedItem = itemPosition; + if (!SelectedItem.HasValue || !SelectedItem.Value.Equals(itemPosition)) + { + SelectedItem = itemPosition; } } else if (SelectionMode == Maui.SelectionMode.Multiple) @@ -300,15 +300,15 @@ public void SelectItem(ItemPosition itemPosition) { SelectedItems = current.Append(itemPosition).ToArray(); } - } + } } - public void ClearSelectedItems() + public void ClearSelectedItems() { if (SelectionMode == Maui.SelectionMode.Multiple) - SelectedItems = null; - else - SelectedItem = null; + SelectedItems = null; + else + SelectedItem = null; } public void ScrollToItem(ItemPosition itemPosition, bool animated) @@ -321,9 +321,9 @@ public bool SectionHasFooter(int sectionIndex) => SectionFooterTemplateSelector != null || SectionFooterTemplate != null; public IView CreateView(PositionInfo position, object data) - => position.Kind switch + => position.Kind switch { - PositionKind.Item => + PositionKind.Item => ItemTemplateSelector?.SelectTemplate(data, position.SectionIndex, position.ItemIndex)?.CreateContent() as View ?? ItemTemplate?.CreateContent() as View, PositionKind.SectionHeader => @@ -378,9 +378,9 @@ public void ViewDetached(PositionInfo position, IView view) => this.RemoveLogicalChild(view); public void ViewAttached(PositionInfo position, IView view) - => this.AddLogicalChild(view); - - void RaiseSelectedItemsChanged(ItemPosition[] previousSelection, ItemPosition[] newSelection) + => this.AddLogicalChild(view); + + void RaiseSelectedItemsChanged(ItemPosition[] previousSelection, ItemPosition[] newSelection) => this.OnSelectedItemsChanged?.Invoke(this, new SelectedItemsChangedEventArgs(previousSelection, newSelection)); }