Vistas de colección en Xamarin.Mac

En este artículo se describe cómo trabajar con vistas de colección en una aplicación de Xamarin.Mac. Abarca la creación y el mantenimiento de vistas de colección en Xcode e Interface Builder y cómo trabajar con ellas mediante programación.

Al trabajar con C# y .NET en una aplicación de Xamarin.Mac, el desarrollador tiene acceso a los mismos controles de vista de colección de AppKit que un desarrollador que trabaja en Objective-C y Xcode. Dado que Xamarin.Mac se integra directamente con Xcode, el desarrollador usa Interface Builder de Xcode para crear y mantener vistas de colección.

Un NSCollectionView muestra una cuadrícula de subvistas organizadas mediante un NSCollectionViewLayout. Cada subvista de la cuadrícula se representa mediante un NSCollectionViewItem que administra la carga del contenido de la vista desde un archivo .xib.

Una ejecución de aplicación de ejemplo

En este artículo se describen los conceptos básicos de trabajar con vistas de colección en una aplicación de Xamarin.Mac. Se recomienda encarecidamente trabajar primero en el artículo Hello, Mac, específicamente en las secciones Introducción a Xcode e Interface Builder y Salidas y acciones, ya que trata conceptos clave y técnicas que se usan en este artículo.

Es posible que quiera echar un vistazo a la sección Exponer clases o métodos de C# a Objective-C del documento sobre Aspecto internos de Xamarin.Mac, ya que explica los comandos Register y Export que se usan para conectar las clases de C# a objetos Objective-C y elementos de la interfaz de usuario.

Acerca de las vistas de colección

El objetivo principal de una vista de colección (NSCollectionView) es organizar visualmente un grupo de objetos de forma organizada mediante un diseño de vista de colección (NSCollectionViewLayout), con cada objeto individual (NSCollectionViewItem) obteniendo su propia vista en la colección más grande. Las vistas de colección funcionan mediante técnicas de vinculación de datos y codificación clave-valor y, como tal, debe leer la documentación sobre vinculación de datos y codificación clave-valor antes de continuar con este artículo.

La vista de colección no tiene ningún elemento de vista de colección integrado estándar (como un esquema o vista de tabla), por lo que el desarrollador es responsable de diseñar e implementar una vista prototipo con otros controles AppKit, como campos de imagen, campos de texto, etiquetas, etc. Esta vista prototipo se usará para mostrar y trabajar con cada elemento que administra la vista de colección y se almacena en un archivo .xib.

Dado que el desarrollador es responsable de la apariencia de un elemento de vista de colección, la vista de colección no tiene compatibilidad integrada para resaltar un elemento seleccionado en la cuadrícula. La implementación de esta característica se tratará en este artículo.

Definición del modelo de datos

Antes de enlazar datos a una vista de recopilación en Interface Builder, se debe definir una clase compatible con la codificación de clave-valor (KVC)/Observación de clave-valor (KVO) en la aplicación de Xamarin.Mac para que actúe como el modelo de datos para el enlace. El modelo de datos proporciona todos los datos que se mostrarán en la colección y recibe las modificaciones de los datos que realiza el usuario en la interfaz de usuario mientras ejecuta la aplicación.

Tome el ejemplo de una aplicación que administra un grupo de empleados, se podría usar la siguiente clase para definir el modelo de datos:

using System;
using Foundation;
using AppKit;

namespace MacDatabinding
{
    [Register("PersonModel")]
    public class PersonModel : NSObject
    {
        #region Private Variables
        private string _name = "";
        private string _occupation = "";
        private bool _isManager = false;
        private NSMutableArray _people = new NSMutableArray();
        #endregion

        #region Computed Properties
        [Export("Name")]
        public string Name {
            get { return _name; }
            set {
                WillChangeValue ("Name");
                _name = value;
                DidChangeValue ("Name");
            }
        }

        [Export("Occupation")]
        public string Occupation {
            get { return _occupation; }
            set {
                WillChangeValue ("Occupation");
                _occupation = value;
                DidChangeValue ("Occupation");
            }
        }

        [Export("isManager")]
        public bool isManager {
            get { return _isManager; }
            set {
                WillChangeValue ("isManager");
                WillChangeValue ("Icon");
                _isManager = value;
                DidChangeValue ("isManager");
                DidChangeValue ("Icon");
            }
        }

        [Export("isEmployee")]
        public bool isEmployee {
            get { return (NumberOfEmployees == 0); }
        }

        [Export("Icon")]
        public NSImage Icon
        {
            get
            {
                if (isManager)
                {
                    return NSImage.ImageNamed("IconGroup");
                }
                else
                {
                    return NSImage.ImageNamed("IconUser");
                }
            }
        }

        [Export("personModelArray")]
        public NSArray People {
            get { return _people; }
        }

        [Export("NumberOfEmployees")]
        public nint NumberOfEmployees {
            get { return (nint)_people.Count; }
        }
        #endregion

        #region Constructors
        public PersonModel ()
        {
        }

        public PersonModel (string name, string occupation)
        {
            // Initialize
            this.Name = name;
            this.Occupation = occupation;
        }

        public PersonModel (string name, string occupation, bool manager)
        {
            // Initialize
            this.Name = name;
            this.Occupation = occupation;
            this.isManager = manager;
        }
        #endregion

        #region Array Controller Methods
        [Export("addObject:")]
        public void AddPerson(PersonModel person) {
            WillChangeValue ("personModelArray");
            isManager = true;
            _people.Add (person);
            DidChangeValue ("personModelArray");
        }

        [Export("insertObject:inPersonModelArrayAtIndex:")]
        public void InsertPerson(PersonModel person, nint index) {
            WillChangeValue ("personModelArray");
            _people.Insert (person, index);
            DidChangeValue ("personModelArray");
        }

        [Export("removeObjectFromPersonModelArrayAtIndex:")]
        public void RemovePerson(nint index) {
            WillChangeValue ("personModelArray");
            _people.RemoveObject (index);
            DidChangeValue ("personModelArray");
        }

        [Export("setPersonModelArray:")]
        public void SetPeople(NSMutableArray array) {
            WillChangeValue ("personModelArray");
            _people = array;
            DidChangeValue ("personModelArray");
        }
        #endregion
    }
}

El modelo de datos PersonModel se usará en el resto de este artículo.

Trabajar con una vista de colección

El enlace de datos con una vista de recopilación es muy similar al enlace con una vista de tabla, ya que se usa NSCollectionViewDataSource para proporcionar datos para la colección. Dado que la vista de colección no tiene un formato de presentación preestablecido, se requiere más trabajo para proporcionar comentarios de interacción del usuario y realizar un seguimiento de la selección del usuario.

Creación del prototipo de celda

Dado que la vista de colección no incluye un prototipo de celda predeterminado, el desarrollador deberá agregar uno o varios archivos .xib a la aplicación Xamarin.Mac para definir el diseño y el contenido de las celdas individuales.

Haga lo siguiente:

  1. En el Explorador de soluciones, haga clic con el botón derecho en el nombre del proyecto y seleccione Agregar>Nuevo archivo...

  2. Seleccione Mac>Ver controlador, asígnele un nombre (como EmployeeItem en este ejemplo) y haga clic en el botón Nuevo para crear:

    Adición de un nuevo controlador de vista

    Esto agregará un archivo EmployeeItem.cs, EmployeeItemController.cs y EmployeeItemController.xib a la solución del proyecto.

  3. Haga doble clic en el archivo EmployeeItemController.xib para abrirlo para editarlo en Interface Builder de Xcode.

  4. Agregue un NSBox, NSImageView y dos controles NSLabel a la vista y distribúyalos de la siguiente manera:

    Diseño del diseño del prototipo de celda

  5. Abra el Editor del asistente y cree una Salida para el NSBox que se pueda usar para indicar el estado de selección de una celda:

    Exposición de NSBox en una salida

  6. Vuelva al Editor estándar y seleccione la Vista de imagen.

  7. En el Inspector de enlace, seleccione Enlazar a>Propietario del archivo e introduzca una ruta de acceso de clave de self.Person.Icon:

    Enlace del icono

  8. Seleccione la primera etiqueta en el Inspector de enlace, seleccione Enlazar a>Propietario del archivo e introduzca una ruta de acceso de clave de self.Person.Name:

    Enlace del nombre

  9. Seleccione la segunda etiqueta en el Inspector de enlace, seleccione Enlazar a>Propietario del archivo e introduzca una ruta de acceso de clave de self.Person.Occupation:

    Enlace de la ocupación

  10. Guarde los cambios en el archivo .xib y vuelva a Visual Studio para sincronizar los cambios.

A continuación, edite el archivo EmployeeItemController.cs y haga que tenga un aspecto similar al siguiente:

using System;
using System.Collections.Generic;
using System.Linq;
using Foundation;
using AppKit;

namespace MacCollectionNew
{
    /// <summary>
    /// The Employee item controller handles the display of the individual items that will
    /// be displayed in the collection view as defined in the associated .XIB file.
    /// </summary>
    public partial class EmployeeItemController : NSCollectionViewItem
    {
        #region Private Variables
        /// <summary>
        /// The person that will be displayed.
        /// </summary>
        private PersonModel _person;
        #endregion

        #region Computed Properties
        // strongly typed view accessor
        public new EmployeeItem View
        {
            get
            {
                return (EmployeeItem)base.View;
            }
        }

        /// <summary>
        /// Gets or sets the person.
        /// </summary>
        /// <value>The person that this item belongs to.</value>
        [Export("Person")]
        public PersonModel Person
        {
            get { return _person; }
            set
            {
                WillChangeValue("Person");
                _person = value;
                DidChangeValue("Person");
            }
        }

        /// <summary>
        /// Gets or sets the color of the background for the item.
        /// </summary>
        /// <value>The color of the background.</value>
        public NSColor BackgroundColor {
            get { return Background.FillColor; }
            set { Background.FillColor = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="T:MacCollectionNew.EmployeeItemController"/> is selected.
        /// </summary>
        /// <value><c>true</c> if selected; otherwise, <c>false</c>.</value>
        /// <remarks>This also changes the background color based on the selected state
        /// of the item.</remarks>
        public override bool Selected
        {
            get
            {
                return base.Selected;
            }
            set
            {
                base.Selected = value;

                // Set background color based on the selection state
                if (value) {
                    BackgroundColor = NSColor.DarkGray;
                } else {
                    BackgroundColor = NSColor.LightGray;
                }
            }
        }
        #endregion

        #region Constructors
        // Called when created from unmanaged code
        public EmployeeItemController(IntPtr handle) : base(handle)
        {
            Initialize();
        }

        // Called when created directly from a XIB file
        [Export("initWithCoder:")]
        public EmployeeItemController(NSCoder coder) : base(coder)
        {
            Initialize();
        }

        // Call to load from the XIB/NIB file
        public EmployeeItemController() : base("EmployeeItem", NSBundle.MainBundle)
        {
            Initialize();
        }

        // Added to support loading from XIB/NIB
        public EmployeeItemController(string nibName, NSBundle nibBundle) : base(nibName, nibBundle) {

            Initialize();
        }

        // Shared initialization code
        void Initialize()
        {
        }
        #endregion
    }
}

Al examinar este código con detalle, la clase hereda de NSCollectionViewItem para que pueda actuar como prototipo para una celda vista de colección. La propiedad Person expone la clase que se usó para enlazar datos a la vista de imágenes y etiquetas en Xcode. Se trata de una instancia del PersonModel creado anteriormente.

La propiedad BackgroundColor es un acceso directo al FillColor del control NSBox que se usará para mostrar el estado de selección de una celda. Al invalidar la propiedad Selected del NSCollectionViewItem, el código siguiente establece o borra este estado de selección:

public override bool Selected
{
    get
    {
        return base.Selected;
    }
    set
    {
        base.Selected = value;

        // Set background color based on the selection state
        if (value) {
            BackgroundColor = NSColor.DarkGray;
        } else {
            BackgroundColor = NSColor.LightGray;
        }
    }
}

Creación del origen de datos de la vista de recopilación

Un origen de datos de vista de recopilación (NSCollectionViewDataSource) proporciona todos los datos de una vista de recopilación y crea y rellena una celda de vista de colección (mediante el prototipo .xib) según sea necesario para cada elemento de la colección.

Agregue una nueva clase al proyecto, llámela CollectionViewDataSource y fíjela como la siguiente:

using System;
using System.Collections.Generic;
using AppKit;
using Foundation;

namespace MacCollectionNew
{
    /// <summary>
    /// Collection view data source provides the data for the collection view.
    /// </summary>
    public class CollectionViewDataSource : NSCollectionViewDataSource
    {
        #region Computed Properties
        /// <summary>
        /// Gets or sets the parent collection view.
        /// </summary>
        /// <value>The parent collection view.</value>
        public NSCollectionView ParentCollectionView { get; set; }

        /// <summary>
        /// Gets or sets the data that will be displayed in the collection.
        /// </summary>
        /// <value>A collection of PersonModel objects.</value>
        public List<PersonModel> Data { get; set; } = new List<PersonModel>();
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="T:MacCollectionNew.CollectionViewDataSource"/> class.
        /// </summary>
        /// <param name="parent">The parent collection that this datasource will provide data for.</param>
        public CollectionViewDataSource(NSCollectionView parent)
        {
            // Initialize
            ParentCollectionView = parent;

            // Attach to collection view
            parent.DataSource = this;

        }
        #endregion

        #region Override Methods
        /// <summary>
        /// Gets the number of sections.
        /// </summary>
        /// <returns>The number of sections.</returns>
        /// <param name="collectionView">The parent Collection view.</param>
        public override nint GetNumberOfSections(NSCollectionView collectionView)
        {
            // There is only one section in this view
            return 1;
        }

        /// <summary>
        /// Gets the number of items in the given section.
        /// </summary>
        /// <returns>The number of items.</returns>
        /// <param name="collectionView">The parent Collection view.</param>
        /// <param name="section">The Section number to count items for.</param>
        public override nint GetNumberofItems(NSCollectionView collectionView, nint section)
        {
            // Return the number of items
            return Data.Count;
        }

        /// <summary>
        /// Gets the item for the give section and item index.
        /// </summary>
        /// <returns>The item.</returns>
        /// <param name="collectionView">The parent Collection view.</param>
        /// <param name="indexPath">Index path specifying the section and index.</param>
        public override NSCollectionViewItem GetItem(NSCollectionView collectionView, NSIndexPath indexPath)
        {
            var item = collectionView.MakeItem("EmployeeCell", indexPath) as EmployeeItemController;
            item.Person = Data[(int)indexPath.Item];

            return item;
        }
        #endregion
    }
}

Al examinar este código con detalle, la clase hereda de NSCollectionViewDataSource y expone una lista de instancias de PersonModel a través de su propiedad Data.

Puesto que esta colección solo tiene una sección, el código invalida el método GetNumberOfSections y siempre devuelve 1. Además, el método GetNumberofItems se invalida en él devuelve el número de elementos de la lista de propiedades Data.

Se llama al método GetItem cada vez que se requiere una nueva celda y tiene un aspecto similar al siguiente:

public override NSCollectionViewItem GetItem(NSCollectionView collectionView, NSIndexPath indexPath)
{
    var item = collectionView.MakeItem("EmployeeCell", indexPath) as EmployeeItemController;
    item.Person = Data[(int)indexPath.Item];

    return item;
}

Se llama al método MakeItem de la vista de colección para crear o devolver una instancia reutilizable de EmployeeItemController y su propiedad Person se establece en el elemento que se muestra en la celda solicitada.

El EmployeeItemController debe registrarse con el controlador de vista de colección de antemano mediante el siguiente código:

EmployeeCollection.RegisterClassForItem(typeof(EmployeeItemController), "EmployeeCell");

El identificador (EmployeeCell) usado en la llamada MakeItemdebe coincidir con el nombre del controlador de vista que se registró con la vista de colección. Este paso se tratará en detalle a continuación.

Control de la selección de elementos

Para controlar la selección y anulación de elementos de la colección, se requerirá un NSCollectionViewDelegate. Dado que en este ejemplo se usará el tipo de diseño integrado NSCollectionViewFlowLayout, se requerirá una versión específica NSCollectionViewDelegateFlowLayout de este delegado.

Agregue una nueva clase al proyecto, llámela CollectionViewDelegate y haga que tenga un aspecto similar al siguiente:

using System;
using Foundation;
using AppKit;

namespace MacCollectionNew
{
    /// <summary>
    /// Collection view delegate handles user interaction with the elements of the
    /// collection view for the Flow-Based layout type.
    /// </summary>
    public class CollectionViewDelegate : NSCollectionViewDelegateFlowLayout
    {
        #region Computed Properties
        /// <summary>
        /// Gets or sets the parent view controller.
        /// </summary>
        /// <value>The parent view controller.</value>
        public ViewController ParentViewController { get; set; }
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="T:MacCollectionNew.CollectionViewDelegate"/> class.
        /// </summary>
        /// <param name="parentViewController">Parent view controller.</param>
        public CollectionViewDelegate(ViewController parentViewController)
        {
            // Initialize
            ParentViewController = parentViewController;
        }
        #endregion

        #region Override Methods
        /// <summary>
        /// Handles one or more items being selected.
        /// </summary>
        /// <param name="collectionView">The parent Collection view.</param>
        /// <param name="indexPaths">The Index paths of the items being selected.</param>
        public override void ItemsSelected(NSCollectionView collectionView, NSSet indexPaths)
        {
            // Dereference path
            var paths = indexPaths.ToArray<NSIndexPath>();
            var index = (int)paths[0].Item;

            // Save the selected item
            ParentViewController.PersonSelected = ParentViewController.Datasource.Data[index];

        }

        /// <summary>
        /// Handles one or more items being deselected.
        /// </summary>
        /// <param name="collectionView">The parent Collection view.</param>
        /// <param name="indexPaths">The Index paths of the items being deselected.</param>
        public override void ItemsDeselected(NSCollectionView collectionView, NSSet indexPaths)
        {
            // Dereference path
            var paths = indexPaths.ToArray<NSIndexPath>();
            var index = paths[0].Item;

            // Clear selection
            ParentViewController.PersonSelected = null;
        }
        #endregion
    }
}

Los métodos ItemsSelected y ItemsDeselected se invalidan y se usan para establecer o borrar la propiedad PersonSelected del controlador de vista que controla la vista de colección cuando el usuario selecciona o anula la selección de un elemento. Esto se mostrará en detalle a continuación.

Creación de la vista de recopilación en Interface Builder

Con todas las piezas auxiliares necesarias en su lugar, el guión gráfico principal se puede editar y una vista de colección agregada a ella.

Haga lo siguiente:

  1. Haga doble clic en el archivo Main.Storyboard del Explorador de soluciones para abrirlo para editarlo en Interface Builder de Xcode.

  2. Arrastre una vista de colección a la vista principal y cambie su tamaño para rellenar la vista:

    Adición de una vista de colección al diseño

  3. Con la vista de colección seleccionada, use el Editor de restricciones para anclarla a la vista cuando se cambie el tamaño:

    Captura de pantalla que muestra agregar nuevas restricciones.

  4. Asegúrese de que la vista de colección está seleccionada en la Superficie de diseño (y no la Vista de desplazamiento con borde o la Vista de clip que la contiene), cambie al Editor asistente y cree una Salida para la vista de colección:

    Captura de pantalla que muestra el editor del asistente, donde puede crear una salida.

  5. Guarde los cambios y vuelva a Visual Studio para sincronizarlo.

Resumen

Todas las piezas auxiliares ahora se han puesto en marcha con una clase para actuar como modelo de datos (PersonModel), se ha agregado un NSCollectionViewDataSource para proporcionar datos, se ha creado un NSCollectionViewDelegateFlowLayout para controlar la selección de elementos y se ha agregado un NSCollectionView al Guión gráfico principal y se expone como una salida (EmployeeCollection).

El último paso es editar el controlador de vista que contiene la vista de colección y reunir todas las piezas para rellenar la colección y controlar la selección de elementos.

A continuación, edite el archivo ViewController.cs y haga que tenga un aspecto similar al siguiente:

using System;
using AppKit;
using Foundation;
using CoreGraphics;

namespace MacCollectionNew
{
    /// <summary>
    /// The View controller controls the main view that houses the Collection View.
    /// </summary>
    public partial class ViewController : NSViewController
    {
        #region Private Variables
        private PersonModel _personSelected;
        private bool shouldEdit = true;
        #endregion

        #region Computed Properties
        /// <summary>
        /// Gets or sets the datasource that provides the data to display in the
        /// Collection View.
        /// </summary>
        /// <value>The datasource.</value>
        public CollectionViewDataSource Datasource { get; set; }

        /// <summary>
        /// Gets or sets the person currently selected in the collection view.
        /// </summary>
        /// <value>The person selected or <c>null</c> if no person is selected.</value>
        [Export("PersonSelected")]
        public PersonModel PersonSelected
        {
            get { return _personSelected; }
            set
            {
                WillChangeValue("PersonSelected");
                _personSelected = value;
                DidChangeValue("PersonSelected");
                RaiseSelectionChanged();
            }
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="T:MacCollectionNew.ViewController"/> class.
        /// </summary>
        /// <param name="handle">Handle.</param>
        public ViewController(IntPtr handle) : base(handle)
        {
        }
        #endregion

        #region Override Methods
        /// <summary>
        /// Called after the view has finished loading from the Storyboard to allow it to
        /// be configured before displaying to the user.
        /// </summary>
        public override void ViewDidLoad()
        {
            base.ViewDidLoad();

            // Initialize Collection View
            ConfigureCollectionView();
            PopulateWithData();
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Configures the collection view.
        /// </summary>
        private void ConfigureCollectionView()
        {
            EmployeeCollection.RegisterClassForItem(typeof(EmployeeItemController), "EmployeeCell");

            // Create a flow layout
            var flowLayout = new NSCollectionViewFlowLayout()
            {
                ItemSize = new CGSize(150, 150),
                SectionInset = new NSEdgeInsets(10, 10, 10, 20),
                MinimumInteritemSpacing = 10,
                MinimumLineSpacing = 10
            };
            EmployeeCollection.WantsLayer = true;

            // Setup collection view
            EmployeeCollection.CollectionViewLayout = flowLayout;
            EmployeeCollection.Delegate = new CollectionViewDelegate(this);

        }

        /// <summary>
        /// Populates the Datasource with data and attaches it to the collection view.
        /// </summary>
        private void PopulateWithData()
        {
            // Make datasource
            Datasource = new CollectionViewDataSource(EmployeeCollection);

            // Build list of employees
            Datasource.Data.Add(new PersonModel("Craig Dunn", "Documentation Manager", true));
            Datasource.Data.Add(new PersonModel("Amy Burns", "Technical Writer"));
            Datasource.Data.Add(new PersonModel("Joel Martinez", "Web & Infrastructure"));
            Datasource.Data.Add(new PersonModel("Kevin Mullins", "Technical Writer"));
            Datasource.Data.Add(new PersonModel("Mark McLemore", "Technical Writer"));
            Datasource.Data.Add(new PersonModel("Tom Opgenorth", "Technical Writer"));
            Datasource.Data.Add(new PersonModel("Larry O'Brien", "API Docs Manager", true));
            Datasource.Data.Add(new PersonModel("Mike Norman", "API Documentor"));

            // Populate collection view
            EmployeeCollection.ReloadData();
        }
        #endregion

        #region Events
        /// <summary>
        /// Selection changed delegate.
        /// </summary>
        public delegate void SelectionChangedDelegate();

        /// <summary>
        /// Occurs when selection changed.
        /// </summary>
        public event SelectionChangedDelegate SelectionChanged;

        /// <summary>
        /// Raises the selection changed event.
        /// </summary>
        internal void RaiseSelectionChanged() {
            // Inform caller
            if (this.SelectionChanged != null) SelectionChanged();
        }
        #endregion
    }
}

Al echar un vistazo detalladamente a este código, se define una propiedad Datasource para contener una instancia del CollectionViewDataSource que proporcionará los datos para la vista de recopilación. Se define una propiedad PersonSelected para contener el elemento PersonModel que representa el elemento seleccionado actualmente en la vista colección. Esta propiedad también genera el evento SelectionChanged cuando cambia la selección.

La clase ConfigureCollectionView se usa para registrar el controlador de vista que actúa como prototipo de celda con la vista de colección mediante la siguiente línea:

EmployeeCollection.RegisterClassForItem(typeof(EmployeeItemController), "EmployeeCell");

Observe que el identificador (EmployeeCell) usado para registrar el prototipo coincide con el llamado en el método GetItem del CollectionViewDataSource definido anteriormente:

var item = collectionView.MakeItem("EmployeeCell", indexPath) as EmployeeItemController;
...

Además, el tipo del controlador de vista debe coincidir con el nombre del archivo .xib que define el prototipo exactamente. En el caso de este ejemplo, EmployeeItemController y EmployeeItemController.xib.

El diseño real de los elementos de la vista de colección se controla mediante una clase de diseño de vista de colección y se puede cambiar dinámicamente en runtime asignando una nueva instancia a la propiedad CollectionViewLayout. Al cambiar esta propiedad, se actualiza la apariencia de la vista de colección sin animar el cambio.

Apple incluye dos tipos de diseño integrados con la vista de colección que controlará los usos más habituales: NSCollectionViewFlowLayout y NSCollectionViewGridLayout. Si el desarrollador necesita un formato personalizado, como colocar los elementos en un círculo, puede crear una instancia personalizada de NSCollectionViewLayout e invalidar los métodos necesarios para lograr el efecto deseado.

En este ejemplo se usa el diseño de flujo predeterminado, por lo que crea una instancia de la clase NSCollectionViewFlowLayout y la configura de la siguiente manera:

var flowLayout = new NSCollectionViewFlowLayout()
{
    ItemSize = new CGSize(150, 150),
    SectionInset = new NSEdgeInsets(10, 10, 10, 20),
    MinimumInteritemSpacing = 10,
    MinimumLineSpacing = 10
};

La propiedad ItemSize define el tamaño de cada celda individual de la colección. La propiedad SectionInset define los conjuntos desde el borde de la colección en la que se colocarán las celdas. MinimumInteritemSpacing define el espaciado mínimo entre elementos y MinimumLineSpacing define el espaciado mínimo entre líneas de la colección.

El diseño se asigna a la vista de colección y se adjunta una instancia del CollectionViewDelegate para controlar la selección de elementos:

// Setup collection view
EmployeeCollection.CollectionViewLayout = flowLayout;
EmployeeCollection.Delegate = new CollectionViewDelegate(this);

El método PopulateWithData crea una nueva instancia del CollectionViewDataSource, la rellena con datos, la adjunta a la Vista de recopilación y llama al método ReloadData para mostrar los elementos:

private void PopulateWithData()
{
    // Make datasource
    Datasource = new CollectionViewDataSource(EmployeeCollection);

    // Build list of employees
    Datasource.Data.Add(new PersonModel("Craig Dunn", "Documentation Manager", true));
    ...

    // Populate collection view
    EmployeeCollection.ReloadData();
}

El método ViewDidLoad se invalida y llama a los métodos ConfigureCollectionView y PopulateWithData para mostrar la vista de colección final al usuario:

public override void ViewDidLoad()
{
    base.ViewDidLoad();

    // Initialize Collection View
    ConfigureCollectionView();
    PopulateWithData();
}

Resumen

En este artículo se ha tomado un vistazo detallado al trabajo con vistas de colección en una aplicación de Xamarin.Mac. En primer lugar, examinó la exposición de una clase de C# a Objective-C mediante la codificación de clave-valor (KVC) y la observación de clave-valor (KVO). A continuación, se mostró cómo usar una clase compatible con KVO y Enlazar datos a vistas de colección en Interface Builder de Xcode. Por último, se mostró cómo interactuar con las vistas de colección en código de C#.