xamarin.iosuicollectionviewxamarin.formsdatatemplatedatatemplateselector

Xamarin Forms UICollectionView with DataTemplate


I'm trying make a Custom View in Xamarin Forms that translates to an UICollectionView in IOS.

This first thing is fairly simple to do:

View:

public class CollectionView : View
{

}

Renderer:

public class CollectionViewRenderer : ViewRenderer<CollectionView, UICollectionView>
{

    protected override void OnElementChanged(ElementChangedEventArgs<CollectionView> e)
    {
        base.OnElementChanged(e);

        if (Control == null)
        {
            SetNativeControl(new UICollectionView(new CGRect(0, 0, 200, 200), new UICollectionViewFlowLayout()));
        }

        if (e.NewElement != null)
        {
            ...
            Control.Source = new CollectionViewSource(a, this);
            Control.ReloadData();
        }
    }
}

Now I would like to feed this CollectionView with DataTemplates (from a DataTemplateSelector). But I'm unable to find a way to register the classes:

From the template you can do:

Template.CreateContent();

to get the UI element.

But how can I register it in the collectionView for dequeue'ing in the CollectionSource

E.G.:

CollectionView.RegisterClassForCell(typeof(????), "CellId");

Solution

  • Hope, it will help you!!!

    CustomControl

    GridCollectionView.cs

    using System;
    using CoreGraphics;
    using Foundation;
    using UIKit;
    
    
    namespace MyApp.Forms.Controls
    {
        public class GridCollectionView : UICollectionView
        {
            public GridCollectionView () : this (default(CGRect))
            {
            }
    
    
            public GridCollectionView(CGRect frm)
                : base(frm, new UICollectionViewFlowLayout())
            {
                AutoresizingMask = UIViewAutoresizing.All;
                ContentMode = UIViewContentMode.ScaleToFill;
                RegisterClassForCell(typeof(GridViewCell), new NSString (GridViewCell.Key));
            }
    
    
            public bool SelectionEnable
            {
                get;
                set;
            }
    
    
            public double RowSpacing
            {
                get
                {
                    return ((UICollectionViewFlowLayout)this.CollectionViewLayout).MinimumLineSpacing;
                }
                set
                {
                    ((UICollectionViewFlowLayout)this.CollectionViewLayout).MinimumLineSpacing = (nfloat)value;
                }
            }
    
    
            public double ColumnSpacing
            {
                get
                {
                    return ((UICollectionViewFlowLayout)this.CollectionViewLayout).MinimumInteritemSpacing;
                }
                set
                {
                    ((UICollectionViewFlowLayout)this.CollectionViewLayout).MinimumInteritemSpacing = (nfloat)value;
                }
            }
    
    
            public CGSize ItemSize
            {
                get
                {
                    return ((UICollectionViewFlowLayout)this.CollectionViewLayout).ItemSize;
                }
                set
                {
                    ((UICollectionViewFlowLayout)this.CollectionViewLayout).ItemSize = value;
                }
            }
    
    
            public override UICollectionViewCell CellForItem(NSIndexPath indexPath)
            {
                if (indexPath == null)
                {
                    //calling base.CellForItem(indexPath) when indexPath is null causes an exception.
                    //indexPath could be null in the following scenario:
                    // - GridView is configured to show 2 cells per row and there are 3 items in ItemsSource collection
                    // - you're trying to drag 4th cell (empty) like you're trying to scroll
                    return null;
                }
                return base.CellForItem(indexPath);
            }
    
    
            public override void Draw (CGRect rect)
            {
                this.CollectionViewLayout.InvalidateLayout ();
    
    
                base.Draw (rect);
            }
    
    
            public override CGSize SizeThatFits(CGSize size)
            {
                return ItemSize;
            }
        }
    }
    

    Renderer Class

    GridViewRenderer.cs

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Linq;
    using Foundation;
    using UIKit;
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.iOS;
    using MyApp.Controls;
    using System.Collections.Generic;
    
    
    [assembly: ExportRenderer (typeof(GridView), typeof(GridViewRenderer))]
    namespace MyApp.Controls
    {
        public class GridViewRenderer: ViewRenderer<GridView,GridCollectionView>
        {
            private GridDataSource _dataSource;
    
        public GridViewRenderer ()
            {
            }
    
            public int RowsInSection(UICollectionView collectionView, nint section)
            {
                return ((ICollection) this.Element.ItemsSource).Count;
            }
    
            public void ItemSelected(UICollectionView tableView, NSIndexPath indexPath)
            {
                var item = this.Element.ItemsSource.Cast<object>().ElementAt(indexPath.Row);
                this.Element.InvokeItemSelectedEvent(this, item);
            }
    
            public UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
            {
                var item = this.Element.ItemsSource.Cast<object>().ElementAt(indexPath.Row);
                var viewCellBinded = (this.Element.ItemTemplate.CreateContent() as ViewCell);
                if (viewCellBinded == null) return null;
    
    
                viewCellBinded.BindingContext = item;
                return this.GetCell(collectionView, viewCellBinded, indexPath);
            }
    
    
            protected virtual UICollectionViewCell GetCell(UICollectionView collectionView, ViewCell item, NSIndexPath indexPath)
            {
                var collectionCell = collectionView.DequeueReusableCell(new NSString(GridViewCell.Key), indexPath) as GridViewCell;
    
    
                if (collectionCell == null) return null;
    
    
                collectionCell.ViewCell = item;
    
    
                return collectionCell;
            }
    
    
            protected override void OnElementChanged (ElementChangedEventArgs<GridView> e)
            {
                base.OnElementChanged (e);
                if (e.OldElement != null)
                {
                    Unbind (e.OldElement);
                }
                if (e.NewElement != null)
                {
                    if (Control == null)
                    {
                        var collectionView = new GridCollectionView() {
                            AllowsMultipleSelection = false,
                            SelectionEnable = e.NewElement.SelectionEnabled,
                            ContentInset =  new UIEdgeInsets ((float)this.Element.Padding.Top, (float)this.Element.Padding.Left, (float)this.Element.Padding.Bottom, (float)this.Element.Padding.Right),
                            BackgroundColor = this.Element.BackgroundColor.ToUIColor (),
                            ItemSize = new CoreGraphics.CGSize ((float)this.Element.ItemWidth, (float)this.Element.ItemHeight),
                            RowSpacing = this.Element.RowSpacing,
                            ColumnSpacing = this.Element.ColumnSpacing
                        };
    
    
                        Bind (e.NewElement);
    
    
                        collectionView.Source = this.DataSource;
                        //collectionView.Delegate = this.GridViewDelegate;
    
    
                        SetNativeControl (collectionView);
                    }
                }
    
    
    
    
            }
    
    
            private void Unbind (GridView oldElement)
            {
                if (oldElement == null) return;
    
    
                oldElement.PropertyChanging -= this.ElementPropertyChanging;
                oldElement.PropertyChanged -= this.ElementPropertyChanged;
    
    
                var itemsSource = oldElement.ItemsSource as INotifyCollectionChanged;
                if (itemsSource != null) 
                {
                    itemsSource.CollectionChanged -= this.DataCollectionChanged;
                }
            }
    
    
            private void Bind (GridView newElement)
            {
                if (newElement == null) return;
    
    
                newElement.PropertyChanging += this.ElementPropertyChanging;
                newElement.PropertyChanged += this.ElementPropertyChanged;
    
    
                var source = newElement.ItemsSource as INotifyCollectionChanged;
                if (source != null) 
                {
                    source.CollectionChanged += this.DataCollectionChanged;
                }
            }
    
    
            private void ElementPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
            {
                if (e.PropertyName == GridView.ItemsSourceProperty.PropertyName)
                {
                    var newItemsSource = this.Element.ItemsSource as INotifyCollectionChanged;
                    if (newItemsSource != null) 
                    {
                        newItemsSource.CollectionChanged += DataCollectionChanged;
                        this.Control.ReloadData();
                    }
                }
                else if(e.PropertyName == "ItemWidth" || e.PropertyName == "ItemHeight")
                {
                    this.Control.ItemSize = new CoreGraphics.CGSize ((float)this.Element.ItemWidth, (float)this.Element.ItemHeight);
                }
            }
    
    
            private void ElementPropertyChanging (object sender, PropertyChangingEventArgs e)
            {
                if (e.PropertyName == "ItemsSource")
                {
                    var oldItemsSource = this.Element.ItemsSource as INotifyCollectionChanged;
                    if (oldItemsSource != null) 
                    {
                        oldItemsSource.CollectionChanged -= DataCollectionChanged;
                    }
                }
            }
    
    
            private void DataCollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
            {
                InvokeOnMainThread (()=> {
                    try 
                    {
                        if(this.Control == null)
                            return;
    
    
                        this.Control.ReloadData();
    
    
                        // TODO: try to handle add or remove operations gracefully, just reload the whole collection for other changes
                        // InsertItems, DeleteItems or ReloadItems can cause
                        // *** Assertion failure in -[XLabs_Forms_Controls_GridCollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:],
                        // BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3512.30.14/UICollectionView.m:4324
    
    
    //                    var indexes = new List<NSIndexPath>();
    //                    switch (e.Action) {
    //                        case NotifyCollectionChangedAction.Add:
    //                            for (int i = 0; i < e.NewItems.Count; i++) {
    //                                indexes.Add(NSIndexPath.FromRowSection((nint)(e.NewStartingIndex + i),0));
    //                            }
    //                            this.Control.InsertItems(indexes.ToArray());
    //                            break;
    //                        case NotifyCollectionChangedAction.Remove:
    //                            for (int i = 0; i< e.OldItems.Count; i++) {
    //                                indexes.Add(NSIndexPath.FromRowSection((nint)(e.OldStartingIndex + i),0));
    //                            }
    //                            this.Control.DeleteItems(indexes.ToArray());
    //                            break;
    //                        default:
    //                            this.Control.ReloadData();
    //                            break;
    //                    }
                    } 
                    catch { } // todo: determine why we are hiding a possible exception here
                });
            }
    
            private GridDataSource DataSource 
            {
                get 
                {
                    return _dataSource ?? (_dataSource = new GridDataSource (GetCell, RowsInSection,ItemSelected));
                }
            }
    
    
            protected override void Dispose (bool disposing)
            {
                base.Dispose (disposing);
                if (disposing && _dataSource != null)
                {
                    Unbind (Element);
                    _dataSource.Dispose ();
                    _dataSource = null;
                }
            }
        }
    }
    

    For more information Click here for custom class and click here for renderer class