Cross Platform
Android
iOS
Mac
Test Cloud

Introduction to Collection Views

Collection Views allow content to be displayed using arbitrary layouts. They allow easily creating grid-like layouts out of the box, while supporting custom layouts as well.

Overview

Collection Views, available in the UICollectionView class, are a new concept in iOS 6 that introduce presenting multiple items on the screen using layouts. The patterns for providing data to a UICollectionView to create items and interact with those items follow the same delegation and data source patterns commonly used in iOS development.

However, Collection Views work with a layout subsystem that is independent of the UICollectionView itself. Therefore, simply providing a different layout can easily change the presentation of a Collection View.

iOS provides a layout class called UICollectionViewFlowLayout that allows line-based layouts such as a grid to be created with no additional work. Also, custom layouts can also be created that allow any presentation you can imagine.

UICollectionView Basics

The UICollectionView class is made up of three different items:

  • Cells – Data-driven views for each item
  • Supplementary Views – Data-driven views associated with a section.
  • Decoration Views – Non-data driven views created by a layout

Cells

Cells are objects that represent a single item in the data set that is being presented by the collection view. Each cell is an instance of the UICollectionViewCell class, which is composed of three different views, as shown in the figure below:

The UICollectionViewCell class has the following properties for each of these views:

  • ContentView – This view contains the content that the cell presents. It is rendered in the topmost z-order on the screen.
  • SelectedBackgroundView – Cells have built in support for selection. This view is used to visually denote that a cell is selected. It is rendered just below the ContentView when a cell is selected.
  • BackgroundView – Cells can also display a background, which is presented by the BackgroundView . This view is rendered beneath the SelectedBackgroundView .

By setting the ContentView such that it is smaller than the BackgroundView and SelectedBackgroundView, the BackgroundView can be used to visually frame the content, while the SelectedBackgroundView will be displayed when a cell is selected, as shown below:

The Cells in the screenshot above are created by inheriting from UICollectionViewCell and setting the ContentView, SelectedBackgroundView and BackgroundView properties, respectively, as shown in the following code:

public class AnimalCell : UICollectionViewCell
{
        UIImageView imageView;

        [Export ("initWithFrame:")]
        public AnimalCell (System.Drawing.RectangleF frame) : base (frame)
        {
                BackgroundView = new UIView{BackgroundColor = UIColor.Orange};

                SelectedBackgroundView = new UIView{BackgroundColor = UIColor.Green};

                ContentView.Layer.BorderColor = UIColor.LightGray.CGColor;
                ContentView.Layer.BorderWidth = 2.0f;
                ContentView.BackgroundColor = UIColor.White;
                ContentView.Transform = CGAffineTransform.MakeScale (0.8f, 0.8f);

                imageView = new UIImageView (UIImage.FromBundle ("placeholder.png"));
                imageView.Center = ContentView.Center;
                imageView.Transform = CGAffineTransform.MakeScale (0.7f, 0.7f);

                ContentView.AddSubview (imageView);
        }

        public UIImage Image {
                set {
                        imageView.Image = value;
                }
        }
}

Supplementary Views

Supplementary Views are views that present information associated with each section of a UICollectionView. Like Cells, Supplementary Views are data-driven. Where Cells present the item data from a data source, Supplementary Views present the section data, such as the categories of book in a bookshelf or the genre of music in a music library.

For example, a Supplementary View could be used to present a header for a particular section, as shown in the figure below:

However, Supplementary Views are more generic than just headers and footers. They can be positioned anywhere in the collection view and can be comprised of any views, making their appearance fully customizable.

Decoration Views

Decoration Views are purely visual views that can be displayed in a UICollectionView. Unlike Cells and Supplementary Views, they are not data-driven. They are created within the layout system that works with the UICollectionView and subsequently can change as the content’s layout changes. For example, a Decoration View could be used to present a background view that scrolls with the content in the UICollectionView, as shown below:

Data Source

As with other parts of iOS, such as UITableView and MKMapView, UICollectionView gets its data from a data source, which is exposed in Xamarin.iOS via the UICollectionViewDataSource class. This class is responsible for providing the content to the UICollectionView including:

  • Cells – Returned from GetCell method.
  • Supplementary Views – Returned from GetViewForSupplementaryElement method.
  • Number of sections – Returned from NumberOfSections method. Defaults to 1 if not implemented.
  • Number of items per section – Returned from GetItemsCount method.

Also, as a convenience, the UICollectionViewController class is available, which is automatically configured to be both the delegate, which is discussed in the next section, and data source for its view, which is a UICollectionView.

As with UITableView, the UICollectionView class will only call its data source to get Cells for items that are on the screen. Cells that scroll off the screen are placed in to a queue for reuse, as the following figure illustrates:

However, with both UICollectionView and UITableView in iOS 6, cell reuse has been simplified. You no longer need to create a Cell directly in the data source if one isn’t available in the reuse queue. Instead, in iOS 6, Cells are registered with the system. Then, when making the call to de-queue the Cell from the reuse queue, if a Cell is not available, iOS 6 will create it automatically based upon the type or nib that was registered. Also, the same technique is available for Supplementary Views as well.

For example, the consider the following code which registers the AnimalCell class shown earlier:

static NSString animalCellId = new NSString ("AnimalCell");
CollectionView.RegisterClassForCell (typeof(AnimalCell), animalCellId);

When a UICollectionView needs a cell because its item is on the screen, the UICollectionView calls its data source’s GetCell method. Similar to how this work with UITableView, this method is responsible for configuring a Cell from the backing data, which would be an Animal class in this case.

The following code shows an implementation of GetCell that returns an AnimalCell instance:

public override UICollectionViewCell GetCell (UICollectionView collectionView, MonoTouch.Foundation.NSIndexPath indexPath)
{
        var animalCell = (AnimalCell)collectionView.DequeueReusableCell (animalCellId, indexPath);

        var animal = animals [indexPath.Row];

        animalCell.Image = animal.Image;

        return animalCell;
}

The call to DequeReusableCell is where the cell will be either de-queued from the reuse queue or, if a cell is not available in the queue, created based upon the type registered in the call to CollectionView.RegisterClassForCell.

In this case, by registering the AnimalCell class, iOS will create a new AnimalCell internally and return it when a call to de-queue a cell is made, after which it is configured with the image contained in the animal class and returned for display to the UICollectionView.

Delegate

The UICollectionView class uses a delegate of type UICollectionViewDelegate to support interaction with content in the UICollectionView. This allows control of:

  • Cell Selection – Determines if a cell is selected.
  • Cell Highlighting – Determines if a cell is currently being touched.
  • Cell Menus – Menu displayed for a cell in response to a long press gesture.

As with the data source, the UICollectionViewController is configured by default to be the delegate for the UICollectionView.

Cell HighLighting

When a Cell is pressed, the cell transitions into a highlighted state, although it is not selected until the user lifts his or her finger from the Cell. This allows a temporary change in the appearance of the cell before it is actually selected. Upon selection, the Cell’s SelectedBackgroundView is displayed, as shown earlier. The figure below shows the highlighted state just before the selection occurs:

To implement highlighting, the ItemHighlighted and ItemUnhighlighted methods of the UICollectionViewDelegate are used. For example, the following code will apply a yellow background of the ContentView, as shown above, when the Cell is highlighted:

public override void ItemHighlighted (UICollectionView collectionView, NSIndexPath indexPath)
{
        var cell = collectionView.CellForItem(indexPath);
        cell.ContentView.BackgroundColor = UIColor.Yellow;
}

public override void ItemUnhighlighted (UICollectionView collectionView, NSIndexPath indexPath)
{
        var cell = collectionView.CellForItem(indexPath);
        cell.ContentView.BackgroundColor = UIColor.White;
}

Disabling Selection

Selection is enabled by default in UICollectionView. To disable selection, override ShouldHighlightItem and return false as shown below:

public override bool ShouldHighlightItem (UICollectionView collectionView, NSIndexPath indexPath)
{
        return false;
}

When highlighting is disabled, the process of selecting a cell is disabled as well. Additionally, there is also a ShouldSelectItem method that controls selection directly, although if ShouldHighlightItem is implemented and returns false, ShouldSelectItem is not called.

ShouldSelectItem allows selection to be turned on or off on an item-by-item basis, when ShouldHighlightItem is not implemented. It also allows highlighting without selection, if ShouldHighlightItem is implemented and returns true, while ShouldSelectItem returns false.

Cell Menus

Each Cell in a UICollectionView is capable of showing a menu that allows cut, copy and/or paste to optionally be supported. To create an edit menu on a cell:

  1. Override ShouldShowMenu and return true if the item should show a menu.
  2. Override CanPerformAction and return true for every action that the item can perform, which will be any of cut, copy or paste.
  3. Override PerformAction to perform the edit, copy of paste operation.

The following screenshot show the menu when a cell is long pressed:

Layout

UICollectionView supports a layout system that allows the positioning of all its elements, Cells, Supplementary Views and Decoration Views, to be managed independent of the UICollectionView itself. Using the layout system, an application can support layouts such as the grid-like one we’ve seen in this article, as well as provide custom layouts.

Layout Basics

Layouts in a UICollectionView are defined in a class that inherits from UICollectionViewLayout. The layout implementation is responsible for creating the layout attributes for every item in the UICollectionView. There are two ways to create a layout:

  • Use the built-in UICollectionViewFlowLayout .
  • Provide a custom layout by inheriting from UICollectionViewLayout .

Flow Layout

The UICollectionViewFlowLayout class provides a line-based layout that suitable for arranging content in a grid of Cells as we’ve seen.

To use a flow layout:

  • Create an instance of UICollectionViewFlowLayout :
var layout = new UICollectionViewFlowLayout ();
  • Pass the instance to the constructor of the UICollectionView :
simpleCollectionViewController = new SimpleCollectionViewController (layout);

This is all that is needed to layout content in a grid. Also, when the orientation changes, the UICollectionViewFlowLayout handles rearranging the content appropriately, as shown below:

Section Inset

In order to provide some space around the UIContentView, layouts have a SectionInset property of type UIEdgeInsets. For example, the following code provides a 50-pixel buffer around each section of the UIContentView when laid out by a UICollectionViewFlowLayout:

var layout = new UICollectionViewFlowLayout ();
layout.SectionInset = new UIEdgeInsets (50,50,50,50);

This results in spacing around the section as shown below:

Subclassing UICollectionViewFlowLayout

In edition to using UICollectionViewFlowLayout directly, it can also be subclassed to further customize the layout of content along a line. For example, this can be used to create a layout that does not wrap the Cells into a grid, but instead creates a single row with a horizontal scrolling effect, as shown below:

To implement this by subclassing UICollectionViewFlowLayout requires:

  • Initializing any layout properties that apply to the layout itself or all items in the layout in the constructor.
  • Overriding ShouldInvalidateLayoutForBoundsChange , returning true so that when bounds of the UICollectionView changes, the layout of the cells will be recalculated. This is used in this case ensure the code for transformation applied to the centermost cell will be applied during scrolling.
  • Overriding TargetContentOffset to make the centermost cell snap to the center of the UICollectionView as scrolling stops.
  • Overriding LayoutAttributesForElementsInRect to return an array of UICollectionViewLayoutAttributes . Each UICollectionViewLayoutAttribute contains information on how to layout the particular item, including properties such as its Center , Size , ZIndex and Transform3D .

The following code shows such an implementation:

public class LineLayout : UICollectionViewFlowLayout
{
        public const float ITEM_SIZE = 200.0f;
        public const int ACTIVE_DISTANCE = 200;
        public const float ZOOM_FACTOR = 0.3f;

        public LineLayout ()
                {
                ItemSize = new SizeF (ITEM_SIZE, ITEM_SIZE);
                ScrollDirection = UICollectionViewScrollDirection.Horizontal;
                SectionInset = new UIEdgeInsets (200, 0.0f, 200, 0.0f);
                                MinimumLineSpacing = 50.0f;
        }

        public override bool ShouldInvalidateLayoutForBoundsChange (RectangleF newBounds)
        {
                return true;
        }

        public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect (RectangleF rect)
        {
                var array = base.LayoutAttributesForElementsInRect (rect);
                var visibleRect = new RectangleF (CollectionView.ContentOffset, CollectionView.Bounds.Size);

                foreach (var attributes in array) {
                        if (attributes.Frame.IntersectsWith (rect)) {
                                float distance = visibleRect.GetMidX () - attributes.Center.X;
                                float normalizedDistance = distance / ACTIVE_DISTANCE;
                        if (Math.Abs (distance) < ACTIVE_DISTANCE) {
                                float zoom = 1 + ZOOM_FACTOR * (1 - Math.Abs (normalizedDistance));
                                attributes.Transform3D = CATransform3D.MakeScale (zoom, zoom, 1.0f);
                                attributes.ZIndex = 1;
                        }
                }
        }

        return array;
}

public override PointF TargetContentOffset (PointF proposedContentOffset, PointF scrollingVelocity)
{
        float offSetAdjustment = float.MaxValue;
        float horizontalCenter = (float)(proposedContentOffset.X + (this.CollectionView.Bounds.Size.Width / 2.0));
        RectangleF targetRect = new RectangleF (proposedContentOffset.X, 0.0f, this.CollectionView.Bounds.Size.Width, this.CollectionView.Bounds.Size.Height);
        var array = base.LayoutAttributesForElementsInRect (targetRect);
        foreach (var layoutAttributes in array) {
                float itemHorizontalCenter = layoutAttributes.Center.X;
                if (Math.Abs (itemHorizontalCenter - horizontalCenter) < Math.Abs (offSetAdjustment)) {
                        offSetAdjustment = itemHorizontalCenter - horizontalCenter;
                }
        }
        return new PointF (proposedContentOffset.X + offSetAdjustment, proposedContentOffset.Y);
        }
}

Custom Layout

In addition to using UICollectionViewFlowLayout, layouts can also be fully customized by inheriting directly from UICollectionViewLayout.

The key methods to override are:

  • PrepareLayout – Used for performing initial geometric calculations that will be used throughout the layout process.
  • CollectionViewContentSize – Returns the size of the area used to display content.
  • LayoutAttributesForElementsInRect – As with the UICollectionViewFlowLayout example shown earlier, this method is used to provide information to the UICollectionView regarding how to layout each item. However, unlike the UICollectionViewFlowLayout , when creating a custom layout, you can position items however you choose.

For example, the same content could be presented in a circular layout as shown below:

The powerful thing about layouts is in order to change from the grid-like layout, to a horizontal scrolling layout, and subsequently to this circular layout requires only the layout class provided to the UICollectionView be changed. Nothing in the UICollectionView, its delegate or data source code changes at all.

Summary

This article introduced Collection Views. It discussed the basic concepts of working with Collection Views and examined the primary classes that make up a Collection View. It then covered the mechanisms for providing data to a Collection View and handling item selection. Finally it discussed the layout system used with Collection Views.