Vistas de colección en Xamarin.iOS

Las vistas de colección permiten mostrar contenido mediante diseños arbitrarios. Permiten crear fácilmente diseños tipo cuadrícula desde el primer momento, al tiempo que son compatibles con diseños personalizados.

Las vistas de colección, disponibles en la UICollectionView clase, son un nuevo concepto en iOS 6 que presentan varios elementos en la pantalla mediante diseños. Los patrones para proporcionar datos a para UICollectionView crear elementos e interactuar con esos elementos siguen los mismos patrones de delegación y origen de datos que se usan habitualmente en el desarrollo de iOS.

Sin embargo, las vistas de colección funcionan con un subsistema de diseño que es independiente del UICollectionView propio. Por lo tanto, simplemente proporcionar un diseño diferente puede cambiar fácilmente la presentación de una vista de colección.

iOS proporciona una clase de diseño denominada UICollectionViewFlowLayout que permite crear diseños basados en líneas como una cuadrícula sin trabajo adicional. Además, también se pueden crear diseños personalizados que permitan cualquier presentación que pueda imaginar.

Conceptos básicos de UICollectionView

La UICollectionView clase consta de tres elementos diferentes:

  • Celdas : vistas controladas por datos para cada elemento
  • Vistas complementarias: vistas controladas por datos asociadas a una sección.
  • Vistas de decoración: vistas no controladas por datos creadas por un diseño

Celdas

Las celdas son objetos que representan un único elemento del conjunto de datos que presenta la vista de colección. Cada celda es una instancia de la clase UICollectionViewCell, que se compone de tres vistas diferentes, como se muestra en la ilustración siguiente:

Each cell is composed of three different views, as shown here

La clase UICollectionViewCell tiene las siguientes propiedades para cada una de estas vistas:

  • ContentView : esta vista contiene el contenido que presenta la celda. Se representa en el orden z superior de la pantalla.
  • SelectedBackgroundView – Las celdas tienen compatibilidad integrada para la selección. Esta vista se usa para indicar visualmente que se selecciona una celda. Se representa justo debajo de cuando ContentView se selecciona una celda.
  • BackgroundView – Las celdas también pueden mostrar un fondo, que presenta . BackgroundView. Esta vista se representa debajo de SelectedBackgroundView.

Al establecer el ContentView de modo que sea menor que BackgroundView y SelectedBackgroundView, BackgroundView se puede usar para enmarcar visualmente el contenido, mientras que SelectedBackgroundView se mostrará cuando se seleccione una celda, como se muestra a continuación:

The different cell elements

Las celdas de la captura de pantalla anterior se crean heredando de UICollectionViewCell y estableciendo las propiedades ContentView, SelectedBackgroundView y BackgroundView, respectivamente, como se muestra en el código siguiente:

public class AnimalCell : UICollectionViewCell
{
        UIImageView imageView;

        [Export ("initWithFrame:")]
        public AnimalCell (CGRect 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;
            }
        }
}

Vistas complementarias

Las vistas complementarias son vistas que presentan información asociada a cada sección de un UICollectionView. Al igual que las celdas, las vistas complementarias están controladas por datos. Donde Las celdas presentan los datos del elemento de un origen de datos, las vistas complementarias presentan los datos de sección, como las categorías de libro en una estantería o el género de música en una biblioteca de música.

Por ejemplo, se podría usar una vista complementaria para presentar un encabezado para una sección determinada, como se muestra en la ilustración siguiente:

A Supplementary View used to present a header for a particular section, as shown here

Para usar una vista complementaria, primero debe registrarse en el ViewDidLoad método :

CollectionView.RegisterClassForSupplementaryView (typeof(Header), UICollectionElementKindSection.Header, headerId);

A continuación, la vista debe devolverse mediante GetViewForSupplementaryElement, creada mediante DequeueReusableSupplementaryView y hereda de UICollectionReusableView. El siguiente fragmento de código generará la SupplementaryView que se muestra en la captura de pantalla anterior:

public override UICollectionReusableView GetViewForSupplementaryElement (UICollectionView collectionView, NSString elementKind, NSIndexPath indexPath)
        {
            var headerView = (Header)collectionView.DequeueReusableSupplementaryView (elementKind, headerId, indexPath);
            headerView.Text = "Supplementary View";
            return headerView;
        }

Las vistas complementarias son más genéricas que solo encabezados y pies de página. Se pueden colocar en cualquier parte de la vista de colección y se pueden componer de cualquier vista, lo que hace que su apariencia sea totalmente personalizable.

Vistas de decoración

Las vistas de decoración son vistas puramente visuales que se pueden mostrar en un UICollectionView. A diferencia de las celdas y las vistas complementarias, no están controladas por datos. Siempre se crean dentro de la subclase de un diseño y, posteriormente, pueden cambiar como diseño del contenido. Por ejemplo, se podría usar una vista de decoración para presentar una vista de fondo que se desplaza con el contenido de UICollectionView, como se muestra a continuación:

Decoration View with a red background

El fragmento de código siguiente cambia el fondo a rojo en la clase samples CircleLayout:

public class MyDecorationView : UICollectionReusableView
 {
   [Export ("initWithFrame:")]
   public MyDecorationView (CGRect frame) : base (frame)
   {
     BackgroundColor = UIColor.Red;
   }
 }

Origen de datos

Al igual que con otras partes de iOS, como UITableView y MKMapView, UICollectionView obtiene sus datos de un origen de datos, que se expone en Xamarin.iOS a través de la clase UICollectionViewDataSource. Esta clase es responsable de proporcionar contenido a UICollectionView, como:

  • Celdas : se devuelve desde el método GetCell.
  • Vistas complementarias: se devuelve desde el método GetViewForSupplementaryElement.
  • Número de secciones: se devuelve desde el método NumberOfSections. El valor predeterminado es 1 si no se implementa.
  • Número de elementos por sección: devuelto desde el método GetItemsCount.

UICollectionViewController

Para mayor comodidad, la clase UICollectionViewController está disponible. Se configura automáticamente para que sea el delegado, que se describe en la sección siguiente y el origen de datos de su vista UICollectionView.

Al igual que con UITableView, la clase UICollectionView solo llamará a su origen de datos para obtener Celdas para los elementos que están en la pantalla. Las celdas que se desplazan fuera de la pantalla se colocan en una cola para su reutilización, como se muestra en la imagen siguiente:

Cells that scroll off the screen are placed in to a queue for reuse as shown here

La reutilización de celdas se ha simplificado con UICollectionView y UITableView. Ya no es necesario crear una celda directamente en el origen de datos si no está disponible en la cola de reutilización, ya que las celdas se registran en el sistema. Si una celda no está disponible al realizar la llamada a la desaplicación de la celda de la cola de reutilización, iOS la creará automáticamente en función del tipo o nib que se registró. La misma técnica también está disponible para vistas complementarias.

Por ejemplo, considere el código siguiente que registra la clase AnimalCell:

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

Cuando una UICollectionView necesita una celda porque su elemento está en la pantalla, llama al UICollectionView método del origen de datos GetCell. De forma similar a cómo funciona con UITableView, este método es responsable de configurar una celda a partir de los datos de respaldo, que sería una AnimalCell clase en este caso.

El código siguiente muestra una implementación de GetCell que devuelve una instancia AnimalCell:

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

        var animal = animals [indexPath.Row];

        animalCell.Image = animal.Image;

        return animalCell;
}

La llamada a DequeReusableCell es donde la celda se desconecta de la cola de reutilización o, si una celda no está disponible en la cola, creada en función del tipo registrado en la llamada a CollectionView.RegisterClassForCell.

En este caso, al registrar la AnimalCell clase, iOS creará un nuevo AnimalCell internamente y lo devolverá cuando se realice una llamada a la cola de una celda, después de lo cual se configura con la imagen contenida en la clase animal y se devuelve para que se muestre en UICollectionView.

Delegar

La clase UICollectionView usa un delegado de tipo UICollectionViewDelegate para admitir la interacción con el contenido de UICollectionView. Esto permite el control de:

  • Selección de celda: determina si se selecciona una celda.
  • Resaltado de celdas: determina si se está tocándose actualmente una celda.
  • Menús de celda: menú que se muestra para una celda en respuesta a un gesto de presión larga.

Al igual que con el origen de datos, UICollectionViewController se configura de forma predeterminada para que sea el delegado de UICollectionView.

HighLighting de celda

Cuando se presiona una celda, la celda pasa a un estado resaltado y no se selecciona hasta que el usuario levanta el dedo desde la celda. Esto permite un cambio temporal en la apariencia de la celda antes de que se seleccione realmente. Tras la selección, se muestra la celda SelectedBackgroundView. En la ilustración siguiente se muestra el estado resaltado justo antes de que se produzca la selección:

This figure shows the highlighted state just before the selection occurs

Para implementar el resaltado, se pueden usar los métodos ItemHighlighted y ItemUnhighlighted de UICollectionViewDelegate. Por ejemplo, el código siguiente aplicará un fondo amarillo de ContentView cuando la celda esté resaltada y un fondo blanco cuando se desactive, como se muestra en la imagen anterior:

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;
}

Deshabilitación de la selección

La selección está habilitada de forma predeterminada en UICollectionView. Para deshabilitar la selección, invalide ShouldHighlightItem y devuelva false como se muestra a continuación:

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

Cuando se deshabilita el resaltado, también se deshabilita el proceso de selección de una celda. Además, también hay un ShouldSelectItem método que controla la selección directamente, aunque si ShouldHighlightItem se implementa y regresa false, no se llama a ShouldSelectItem.

ShouldSelectItem permite activar o desactivar la selección en un elemento por elemento, cuando ShouldHighlightItem no se implementa. También permite resaltar sin selección, si ShouldHighlightItem se implementa y devuelve true, mientras que ShouldSelectItem devuelve false.

Menús de celda

Cada celda de un UICollectionView es capaz de mostrar un menú que permite cortar, copiar y pegar en opcionalmente. Para crear un menú de edición en una celda:

  1. Invalide ShouldShowMenu y devuelva true si el elemento debe mostrar un menú.
  2. Invalide CanPerformAction y devuelva true para cada acción que el elemento pueda realizar, que será cualquiera de cortar, copiar o pegar.
  3. Invalide PerformAction para realizar la operación de edición y copia de pegado.

En la captura de pantalla siguiente se muestra el menú cuando se presiona una celda durante mucho tiempo:

This screenshot show the menu when a cell is long pressed

Layout

UICollectionView admite un sistema de diseño que permite el posicionamiento de todos sus elementos, Celdas, Vistas complementarias y Vistas de decoración, para administrarse independientemente del propio UICollectionView. Con el sistema de diseño, una aplicación puede admitir diseños como el de cuadrícula que hemos visto en este artículo, así como proporcionar diseños personalizados.

Conceptos básicos de diseño

Los diseños de un UICollectionView objeto se definen en una clase que hereda de UICollectionViewLayout. La implementación de diseño es responsable de crear los atributos de diseño para cada elemento de UICollectionView. Hay dos formas de crear un objeto:

  • Use la instancia integrada de UICollectionViewFlowLayout.
  • Proporcione un diseño personalizado heredando de UICollectionViewLayout .

Diseño de flujo

La UICollectionViewFlowLayout clase proporciona un diseño basado en línea que es adecuado para organizar el contenido en una cuadrícula de Celdas como hemos visto.

Para usar un diseño de flujo:

  • Crea una instancia de UICollectionViewFlowLayout:
var layout = new UICollectionViewFlowLayout ();
  • Pase la instancia al constructor de UICollectionView :
simpleCollectionViewController = new SimpleCollectionViewController (layout);

Esto es todo lo que se necesita para diseñar contenido en una cuadrícula. Además, cuando cambia la orientación, controla UICollectionViewFlowLayout la reorganización adecuada del contenido, como se muestra a continuación:

Example of the orientation changes

Conjunto de secciones

Para proporcionar espacio alrededor de UIContentView, los diseños tienen una SectionInset propiedad de tipo UIEdgeInsets. Por ejemplo, el código siguiente proporciona un búfer de 50 píxeles alrededor de cada sección de UIContentView cuando se establece en UICollectionViewFlowLayout:

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

Esto da como resultado el espaciado alrededor de la sección, como se muestra a continuación:

Spacing around the section as shown here

Subclase UICollectionViewFlowLayout

En edición para usar UICollectionViewFlowLayout directamente, también se puede subclasar para personalizar aún más el diseño del contenido a lo largo de una línea. Por ejemplo, esto se puede usar para crear un diseño que no encapsula las celdas en una cuadrícula, sino que crea una sola fila con un efecto de desplazamiento horizontal, como se muestra a continuación:

A single row with a horizontal scrolling effect

Para implementar esto mediante subclases UICollectionViewFlowLayout es necesario:

  • Inicializar cualquier propiedad de diseño que se aplique al propio diseño o a todos los elementos del diseño en el constructor.
  • Invalidando ShouldInvalidateLayoutForBoundsChange , devolviendo true para que, cuando se produzcan los límites de los UICollectionView cambios, se volverá a calcular el diseño de las celdas. Esto se usa en este caso para asegurarse de que el código de transformación aplicado a la celda más central se aplicará durante el desplazamiento.
  • Invalidar TargetContentOffset para que la celda más central se ajuste al centro de como UICollectionView se detiene el desplazamiento.
  • Invalidación LayoutAttributesForElementsInRect para devolver una matriz de UICollectionViewLayoutAttributes. Cada UICollectionViewLayoutAttribute contiene información sobre cómo diseñar el elemento en particular, incluidas propiedades como sus Center, Size, ZIndex y Transform3D.

El siguiente código muestra tal implementación:

using System;
using CoreGraphics;
using Foundation;
using UIKit;
using CoreGraphics;
using CoreAnimation;

namespace SimpleCollectionView
{
  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 CGSize (ITEM_SIZE, ITEM_SIZE);
      ScrollDirection = UICollectionViewScrollDirection.Horizontal;
            SectionInset = new UIEdgeInsets (400,0,400,0);
      MinimumLineSpacing = 50.0f;
    }

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

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

      foreach (var attributes in array) {
        if (attributes.Frame.IntersectsWith (rect)) {
          float distance = (float)(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 CGPoint TargetContentOffset (CGPoint proposedContentOffset, CGPoint scrollingVelocity)
    {
      float offSetAdjustment = float.MaxValue;
      float horizontalCenter = (float)(proposedContentOffset.X + (this.CollectionView.Bounds.Size.Width / 2.0));
      CGRect targetRect = new CGRect (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 = (float)layoutAttributes.Center.X;
        if (Math.Abs (itemHorizontalCenter - horizontalCenter) < Math.Abs (offSetAdjustment)) {
          offSetAdjustment = itemHorizontalCenter - horizontalCenter;
        }
      }
            return new CGPoint (proposedContentOffset.X + offSetAdjustment, proposedContentOffset.Y);
    }

  }
}

Diseño personalizado

Además de usar UICollectionViewFlowLayout, los diseños también se pueden personalizar completamente heredando directamente de UICollectionViewLayout.

Los métodos clave que se van a invalidar son:

  • PrepareLayout – Se usa para realizar cálculos geométricos iniciales que se usarán en todo el proceso de diseño.
  • CollectionViewContentSize : devuelve el tamaño del área utilizada para mostrar el contenido.
  • LayoutAttributesForElementsInRect – Al igual que con el ejemplo UICollectionViewFlowLayout mostrado anteriormente, este método se usa para proporcionar información al UICollectionView respecto a cómo diseñar cada elemento. Sin embargo, a diferencia de UICollectionViewFlowLayout, al crear un diseño personalizado, puede colocar elementos sin embargo.

Por ejemplo, el mismo contenido podría presentarse en un diseño circular, como se muestra a continuación:

A circular custom layout as shown here

Lo eficaz de los diseños es que cambiar del diseño similar a la cuadrícula, a un diseño de desplazamiento horizontal y, posteriormente, a este diseño circular solo requiere que se cambie la clase de diseño proporcionada UICollectionView. Nada en UICollectionView, su delegado o código fuente de datos cambia en absoluto.

Cambios de web en iOS 9

En iOS 9, la vista de colección (UICollectionView) ahora admite la reordenación de elementos de serie mediante la adición de un nuevo reconocedor de gestos predeterminado y varios métodos auxiliares.

Con estos nuevos métodos, puede implementar fácilmente la acción de arrastrar para reordenar en la vista de colección y tiene la opción de personalizar la apariencia de los elementos durante cualquier fase del proceso de reordenación.

An example of the reordering process

En este artículo, veremos cómo implementar arrastrar a reordenar en una aplicación de Xamarin.iOS, así como algunos de los otros cambios que iOS 9 ha realizado en el control de vista de colección:

Reordenación de elementos

Como se indicó anteriormente, uno de los cambios más significativos en la vista de colección en iOS 9 era la adición de la funcionalidad fácil de arrastrar a reordenar fuera de la caja.

En iOS 9, la forma más rápida de agregar reordenamiento a una vista de colección es usar un UICollectionViewController. El controlador de vista de colección ahora tiene una InstallsStandardGestureForInteractiveMovement propiedad, que agrega un reconocedor de gestos estándar que admite arrastrar para reordenar los elementos de la colección. Dado que el valor predeterminado es true, solo tiene que implementar el MoveItem método de la clase UICollectionViewDataSource para admitir arrastrar a reordenar. Por ejemplo:

public override void MoveItem (UICollectionView collectionView, NSIndexPath sourceIndexPath, NSIndexPath destinationIndexPath)
{
  // Reorder our list of items
  ...
}

Ejemplo de reordenación simple

Como ejemplo rápido, inicie un nuevo proyecto de Xamarin.iOS y edite el archivo Main.storyboard. Arrástre UICollectionViewController a la superficie de diseño:

Adding a UICollectionViewController

Seleccione la Vista de colección (puede ser más fácil hacerlo desde el esquema del documento). En la pestaña diseño del Panel de propiedades, establezca los siguientes tamaños, como se muestra en la captura de pantalla siguiente:

  • Tamañode celda: Ancho – 60 | Alto : 60
  • Tamaño delencabezado: Ancho – 0 | Alto : 0
  • Tamañodel pie de página: Ancho – 0 | Alto : 0
  • Espaciadomínimo: para celdas – 8 | Para líneas – 8
  • Conjuntos de secciones: Top – 16 | Inferior – 16 | Izquierda – 16 | Derecha – 16

Set the Collection View sizes

A continuación, edite la celda predeterminada:

  • Cambiar su color de fondo a azul
  • Agregar una etiqueta para que actúe como título de la celda
  • Establecimiento del identificador de reutilización en la celda

Edit the default Cell

Agregue restricciones para mantener la etiqueta centrada dentro de la celda a medida que cambia el tamaño:

En el Panel de propiedades de CollectionViewCell y establezca la claseTextCollectionViewCellen :

Set the Class to TextCollectionViewCell

Establezca la vista reutilizable colección en Cell:

Set the Collection Reusable View to Cell

Por último, seleccione la etiqueta y asígnela TextLabelel nombre :

name label TextLabel

Edite la TextCollectionViewCell clase y agregue las siguientes propiedades:

using System;
using Foundation;
using UIKit;

namespace CollectionView
{
  public partial class TextCollectionViewCell : UICollectionViewCell
  {
    #region Computed Properties
    public string Title {
      get { return TextLabel.Text; }
      set { TextLabel.Text = value; }
    }
    #endregion

    #region Constructors
    public TextCollectionViewCell (IntPtr handle) : base (handle)
    {
    }
    #endregion
  }
}

Aquí la Text propiedad de la etiqueta se expone como título de la celda, por lo que se puede establecer desde el código.

Agregue una nueva clase de C# al proyecto y llámela WaterfallCollectionSource. A continuación, edite el archivo y haga que tenga un aspecto similar al siguiente:

using System;
using Foundation;
using UIKit;
using System.Collections.Generic;

namespace CollectionView
{
  public class WaterfallCollectionSource : UICollectionViewDataSource
  {
    #region Computed Properties
    public WaterfallCollectionView CollectionView { get; set;}
    public List<int> Numbers { get; set; } = new List<int> ();
    #endregion

    #region Constructors
    public WaterfallCollectionSource (WaterfallCollectionView collectionView)
    {
      // Initialize
      CollectionView = collectionView;

      // Init numbers collection
      for (int n = 0; n < 100; ++n) {
        Numbers.Add (n);
      }
    }
    #endregion

    #region Override Methods
    public override nint NumberOfSections (UICollectionView collectionView) {
      // We only have one section
      return 1;
    }

    public override nint GetItemsCount (UICollectionView collectionView, nint section) {
      // Return the number of items
      return Numbers.Count;
    }

    public override UICollectionViewCell GetCell (UICollectionView collectionView, NSIndexPath indexPath)
    {
      // Get a reusable cell and set {~~it's~>its~~} title from the item
      var cell = collectionView.DequeueReusableCell ("Cell", indexPath) as TextCollectionViewCell;
      cell.Title = Numbers [(int)indexPath.Item].ToString();

      return cell;
    }

    public override bool CanMoveItem (UICollectionView collectionView, NSIndexPath indexPath) {
      // We can always move items
      return true;
    }

    public override void MoveItem (UICollectionView collectionView, NSIndexPath sourceIndexPath, NSIndexPath destinationIndexPath)
    {
      // Reorder our list of items
      var item = Numbers [(int)sourceIndexPath.Item];
      Numbers.RemoveAt ((int)sourceIndexPath.Item);
      Numbers.Insert ((int)destinationIndexPath.Item, item);
    }
    #endregion
  }
}

Esta clase será el origen de datos de nuestra vista de recopilación y proporcionará la información de cada celda de la colección. Observe que el MoveItem método se implementa para permitir que los elementos de la colección se arrastren de nuevo.

Agregue otra nueva clase de C# al proyecto y llámela WaterfallCollectionDelegate. Edite este archivo y haga que tenga un aspecto similar al siguiente:

using System;
using Foundation;
using UIKit;
using System.Collections.Generic;

namespace CollectionView
{
  public class WaterfallCollectionDelegate : UICollectionViewDelegate
  {
    #region Computed Properties
    public WaterfallCollectionView CollectionView { get; set;}
    #endregion

    #region Constructors
    public WaterfallCollectionDelegate (WaterfallCollectionView collectionView)
    {

      // Initialize
      CollectionView = collectionView;

    }
    #endregion

    #region Overrides Methods
    public override bool ShouldHighlightItem (UICollectionView collectionView, NSIndexPath indexPath) {
      // Always allow for highlighting
      return true;
    }

    public override void ItemHighlighted (UICollectionView collectionView, NSIndexPath indexPath)
    {
      // Get cell and change to green background
      var cell = collectionView.CellForItem(indexPath);
      cell.ContentView.BackgroundColor = UIColor.FromRGB(183,208,57);
    }

    public override void ItemUnhighlighted (UICollectionView collectionView, NSIndexPath indexPath)
    {
      // Get cell and return to blue background
      var cell = collectionView.CellForItem(indexPath);
      cell.ContentView.BackgroundColor = UIColor.FromRGB(164,205,255);
    }
    #endregion
  }
}

Esto actuará como delegado para nuestra vista de colección. Los métodos se han invalidado para resaltar una celda a medida que el usuario interactúa con ella en la vista de colección.

Agregue una última clase de C# al proyecto y llámala WaterfallCollectionView. Edite este archivo y haga que tenga un aspecto similar al siguiente:

using System;
using UIKit;
using System.Collections.Generic;
using Foundation;

namespace CollectionView
{
  [Register("WaterfallCollectionView")]
  public class WaterfallCollectionView : UICollectionView
  {

    #region Constructors
    public WaterfallCollectionView (IntPtr handle) : base (handle)
    {
    }
    #endregion

    #region Override Methods
    public override void AwakeFromNib ()
    {
      base.AwakeFromNib ();

      // Initialize
      DataSource = new WaterfallCollectionSource(this);
      Delegate = new WaterfallCollectionDelegate(this);

    }
    #endregion
  }
}

Observe que DataSource y Delegate que hemos creado anteriormente se establecen cuando la vista de colección se construye a partir de su guión gráfico (o archivo .xib).

Vuelva a editar el archivo Main.storyboard y seleccione la vista de colección y cambie a propiedades . Establezca la clase en la clase personalizada WaterfallCollectionView que definimos anteriormente:

Guarde los cambios realizados en la interfaz de usuario y ejecute la aplicación. Si el usuario selecciona un elemento de la lista y lo arrastra a una nueva ubicación, los demás elementos se animarán automáticamente a medida que salen del camino del elemento. Cuando el usuario quita el elemento en una nueva ubicación, se pegará a esa ubicación. Por ejemplo:

An example of dragging an item to a new location

Usar un reconocedor de gestos personalizado

En los casos en los que no puede usar y debe usar UICollectionViewController un elemento normal UIViewController, o si desea tomar más control sobre el gesto de arrastrar y colocar, puede crear su propio reconocedor de gestos personalizado y agregarlo a la vista de colección cuando se cargue la vista. Por ejemplo:

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

  // Create a custom gesture recognizer
  var longPressGesture = new UILongPressGestureRecognizer ((gesture) => {

    // Take action based on state
    switch(gesture.State) {
    case UIGestureRecognizerState.Began:
      var selectedIndexPath = CollectionView.IndexPathForItemAtPoint(gesture.LocationInView(View));
      if (selectedIndexPath !=null) {
        CollectionView.BeginInteractiveMovementForItem(selectedIndexPath);
      }
      break;
    case UIGestureRecognizerState.Changed:
      CollectionView.UpdateInteractiveMovementTargetPosition(gesture.LocationInView(View));
      break;
    case UIGestureRecognizerState.Ended:
      CollectionView.EndInteractiveMovement();
      break;
    default:
      CollectionView.CancelInteractiveMovement();
      break;
    }

  });

  // Add the custom recognizer to the collection view
  CollectionView.AddGestureRecognizer(longPressGesture);
}

Aquí se usan varios métodos nuevos agregados a la vista de recopilación para implementar y controlar la operación de arrastre:

  • BeginInteractiveMovementForItem - Marca el inicio de una operación de movimiento.
  • UpdateInteractiveMovementTargetPosition - Se envía a medida que se actualiza la ubicación del elemento.
  • EndInteractiveMovement - Marca el final de un movimiento de elemento.
  • CancelInteractiveMovement - Marca al usuario que cancela la operación de movimiento.

Cuando se ejecuta la aplicación, la operación de arrastre funcionará exactamente igual que el reconocedor de gestos de arrastre predeterminado que viene con la vista de colección.

Diseños personalizados y reordenación

En iOS 9, se han agregado varios métodos nuevos para trabajar con diseños personalizados y de arrastrar a reordenar en una vista de colección. Para explorar esta característica, vamos a agregar un diseño personalizado a la colección.

En primer lugar, añada una nueva clase C# llamada WaterfallCollectionLayout al proyecto. Edítelo y fíjelo como el siguiente:

using System;
using Foundation;
using UIKit;
using System.Collections.Generic;
using CoreGraphics;

namespace CollectionView
{
  [Register("WaterfallCollectionLayout")]
  public class WaterfallCollectionLayout : UICollectionViewLayout
  {
    #region Private Variables
    private int columnCount = 2;
    private nfloat minimumColumnSpacing = 10;
    private nfloat minimumInterItemSpacing = 10;
    private nfloat headerHeight = 0.0f;
    private nfloat footerHeight = 0.0f;
    private UIEdgeInsets sectionInset = new UIEdgeInsets(0, 0, 0, 0);
    private WaterfallCollectionRenderDirection itemRenderDirection = WaterfallCollectionRenderDirection.ShortestFirst;
    private Dictionary<nint,UICollectionViewLayoutAttributes> headersAttributes = new Dictionary<nint, UICollectionViewLayoutAttributes>();
    private Dictionary<nint,UICollectionViewLayoutAttributes> footersAttributes = new Dictionary<nint, UICollectionViewLayoutAttributes>();
    private List<CGRect> unionRects = new List<CGRect>();
    private List<nfloat> columnHeights = new List<nfloat>();
    private List<UICollectionViewLayoutAttributes> allItemAttributes = new List<UICollectionViewLayoutAttributes>();
    private List<List<UICollectionViewLayoutAttributes>> sectionItemAttributes = new List<List<UICollectionViewLayoutAttributes>>();
    private nfloat unionSize = 20;
    #endregion

    #region Computed Properties
    [Export("ColumnCount")]
    public int ColumnCount {
      get { return columnCount; }
      set {
        WillChangeValue ("ColumnCount");
        columnCount = value;
        DidChangeValue ("ColumnCount");

        InvalidateLayout ();
      }
    }

    [Export("MinimumColumnSpacing")]
    public nfloat MinimumColumnSpacing {
      get { return minimumColumnSpacing; }
      set {
        WillChangeValue ("MinimumColumnSpacing");
        minimumColumnSpacing = value;
        DidChangeValue ("MinimumColumnSpacing");

        InvalidateLayout ();
      }
    }

    [Export("MinimumInterItemSpacing")]
    public nfloat MinimumInterItemSpacing {
      get { return minimumInterItemSpacing; }
      set {
        WillChangeValue ("MinimumInterItemSpacing");
        minimumInterItemSpacing = value;
        DidChangeValue ("MinimumInterItemSpacing");

        InvalidateLayout ();
      }
    }

    [Export("HeaderHeight")]
    public nfloat HeaderHeight {
      get { return headerHeight; }
      set {
        WillChangeValue ("HeaderHeight");
        headerHeight = value;
        DidChangeValue ("HeaderHeight");

        InvalidateLayout ();
      }
    }

    [Export("FooterHeight")]
    public nfloat FooterHeight {
      get { return footerHeight; }
      set {
        WillChangeValue ("FooterHeight");
        footerHeight = value;
        DidChangeValue ("FooterHeight");

        InvalidateLayout ();
      }
    }

    [Export("SectionInset")]
    public UIEdgeInsets SectionInset {
      get { return sectionInset; }
      set {
        WillChangeValue ("SectionInset");
        sectionInset = value;
        DidChangeValue ("SectionInset");

        InvalidateLayout ();
      }
    }

    [Export("ItemRenderDirection")]
    public WaterfallCollectionRenderDirection ItemRenderDirection {
      get { return itemRenderDirection; }
      set {
        WillChangeValue ("ItemRenderDirection");
        itemRenderDirection = value;
        DidChangeValue ("ItemRenderDirection");

        InvalidateLayout ();
      }
    }
    #endregion

    #region Constructors
    public WaterfallCollectionLayout ()
    {
    }

    public WaterfallCollectionLayout(NSCoder coder) : base(coder) {

    }
    #endregion

    #region Public Methods
    public nfloat ItemWidthInSectionAtIndex(int section) {

      var width = CollectionView.Bounds.Width - SectionInset.Left - SectionInset.Right;
      return (nfloat)Math.Floor ((width - ((ColumnCount - 1) * MinimumColumnSpacing)) / ColumnCount);
    }
    #endregion

    #region Override Methods
    public override void PrepareLayout ()
    {
      base.PrepareLayout ();

      // Get the number of sections
      var numberofSections = CollectionView.NumberOfSections();
      if (numberofSections == 0)
        return;

      // Reset collections
      headersAttributes.Clear ();
      footersAttributes.Clear ();
      unionRects.Clear ();
      columnHeights.Clear ();
      allItemAttributes.Clear ();
      sectionItemAttributes.Clear ();

      // Initialize column heights
      for (int n = 0; n < ColumnCount; n++) {
        columnHeights.Add ((nfloat)0);
      }

      // Process all sections
      nfloat top = 0.0f;
      var attributes = new UICollectionViewLayoutAttributes ();
      var columnIndex = 0;
      for (nint section = 0; section < numberofSections; ++section) {
        // Calculate section specific metrics
        var minimumInterItemSpacing = (MinimumInterItemSpacingForSection == null) ? MinimumColumnSpacing :
          MinimumInterItemSpacingForSection (CollectionView, this, section);

        // Calculate widths
        var width = CollectionView.Bounds.Width - SectionInset.Left - SectionInset.Right;
        var itemWidth = (nfloat)Math.Floor ((width - ((ColumnCount - 1) * MinimumColumnSpacing)) / ColumnCount);

        // Calculate section header
        var heightHeader = (HeightForHeader == null) ? HeaderHeight :
          HeightForHeader (CollectionView, this, section);

        if (heightHeader > 0) {
          attributes = UICollectionViewLayoutAttributes.CreateForSupplementaryView (UICollectionElementKindSection.Header, NSIndexPath.FromRowSection (0, section));
          attributes.Frame = new CGRect (0, top, CollectionView.Bounds.Width, heightHeader);
          headersAttributes.Add (section, attributes);
          allItemAttributes.Add (attributes);

          top = attributes.Frame.GetMaxY ();
        }

        top += SectionInset.Top;
        for (int n = 0; n < ColumnCount; n++) {
          columnHeights [n] = top;
        }

        // Calculate Section Items
        var itemCount = CollectionView.NumberOfItemsInSection(section);
        List<UICollectionViewLayoutAttributes> itemAttributes = new List<UICollectionViewLayoutAttributes> ();

        for (nint n = 0; n < itemCount; n++) {
          var indexPath = NSIndexPath.FromRowSection (n, section);
          columnIndex = NextColumnIndexForItem (n);
          var xOffset = SectionInset.Left + (itemWidth + MinimumColumnSpacing) * (nfloat)columnIndex;
          var yOffset = columnHeights [columnIndex];
          var itemSize = (SizeForItem == null) ? new CGSize (0, 0) : SizeForItem (CollectionView, this, indexPath);
          nfloat itemHeight = 0.0f;

          if (itemSize.Height > 0.0f && itemSize.Width > 0.0f) {
            itemHeight = (nfloat)Math.Floor (itemSize.Height * itemWidth / itemSize.Width);
          }

          attributes = UICollectionViewLayoutAttributes.CreateForCell (indexPath);
          attributes.Frame = new CGRect (xOffset, yOffset, itemWidth, itemHeight);
          itemAttributes.Add (attributes);
          allItemAttributes.Add (attributes);
          columnHeights [columnIndex] = attributes.Frame.GetMaxY () + MinimumInterItemSpacing;
        }
        sectionItemAttributes.Add (itemAttributes);

        // Calculate Section Footer
        nfloat footerHeight = 0.0f;
        columnIndex = LongestColumnIndex();
        top = columnHeights [columnIndex] - MinimumInterItemSpacing + SectionInset.Bottom;
        footerHeight = (HeightForFooter == null) ? FooterHeight : HeightForFooter(CollectionView, this, section);

        if (footerHeight > 0) {
          attributes = UICollectionViewLayoutAttributes.CreateForSupplementaryView (UICollectionElementKindSection.Footer, NSIndexPath.FromRowSection (0, section));
          attributes.Frame = new CGRect (0, top, CollectionView.Bounds.Width, footerHeight);
          footersAttributes.Add (section, attributes);
          allItemAttributes.Add (attributes);
          top = attributes.Frame.GetMaxY ();
        }

        for (int n = 0; n < ColumnCount; n++) {
          columnHeights [n] = top;
        }
      }

      var i =0;
      var attrs = allItemAttributes.Count;
      while(i < attrs) {
        var rect1 = allItemAttributes [i].Frame;
        i = (int)Math.Min (i + unionSize, attrs) - 1;
        var rect2 = allItemAttributes [i].Frame;
        unionRects.Add (CGRect.Union (rect1, rect2));
        i++;
      }

    }

    public override CGSize CollectionViewContentSize {
      get {
        if (CollectionView.NumberOfSections () == 0) {
          return new CGSize (0, 0);
        }

        var contentSize = CollectionView.Bounds.Size;
        contentSize.Height = columnHeights [0];
        return contentSize;
      }
    }

    public override UICollectionViewLayoutAttributes LayoutAttributesForItem (NSIndexPath indexPath)
    {
      if (indexPath.Section >= sectionItemAttributes.Count) {
        return null;
      }

      if (indexPath.Item >= sectionItemAttributes [indexPath.Section].Count) {
        return null;
      }

      var list = sectionItemAttributes [indexPath.Section];
      return list [(int)indexPath.Item];
    }

    public override UICollectionViewLayoutAttributes LayoutAttributesForSupplementaryView (NSString kind, NSIndexPath indexPath)
    {
      var attributes = new UICollectionViewLayoutAttributes ();

      switch (kind) {
      case "header":
        attributes = headersAttributes [indexPath.Section];
        break;
      case "footer":
        attributes = footersAttributes [indexPath.Section];
        break;
      }

      return attributes;
    }

    public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect (CGRect rect)
    {
      var begin = 0;
      var end = unionRects.Count;
      List<UICollectionViewLayoutAttributes> attrs = new List<UICollectionViewLayoutAttributes> ();

      for (int i = 0; i < end; i++) {
        if (rect.IntersectsWith(unionRects[i])) {
          begin = i * (int)unionSize;
        }
      }

      for (int i = end - 1; i >= 0; i--) {
        if (rect.IntersectsWith (unionRects [i])) {
          end = (int)Math.Min ((i + 1) * (int)unionSize, allItemAttributes.Count);
          break;
        }
      }

      for (int i = begin; i < end; i++) {
        var attr = allItemAttributes [i];
        if (rect.IntersectsWith (attr.Frame)) {
          attrs.Add (attr);
        }
      }

      return attrs.ToArray();
    }

    public override bool ShouldInvalidateLayoutForBoundsChange (CGRect newBounds)
    {
      var oldBounds = CollectionView.Bounds;
      return (newBounds.Width != oldBounds.Width);
    }
    #endregion

    #region Private Methods
    private int ShortestColumnIndex() {
      var index = 0;
      var shortestHeight = nfloat.MaxValue;
      var n = 0;

      // Scan each column for the shortest height
      foreach (nfloat height in columnHeights) {
        if (height < shortestHeight) {
          shortestHeight = height;
          index = n;
        }
        ++n;
      }

      return index;
    }

    private int LongestColumnIndex() {
      var index = 0;
      var longestHeight = nfloat.MinValue;
      var n = 0;

      // Scan each column for the shortest height
      foreach (nfloat height in columnHeights) {
        if (height > longestHeight) {
          longestHeight = height;
          index = n;
        }
        ++n;
      }

      return index;
    }

    private int NextColumnIndexForItem(nint item) {
      var index = 0;

      switch (ItemRenderDirection) {
      case WaterfallCollectionRenderDirection.ShortestFirst:
        index = ShortestColumnIndex ();
        break;
      case WaterfallCollectionRenderDirection.LeftToRight:
        index = ColumnCount;
        break;
      case WaterfallCollectionRenderDirection.RightToLeft:
        index = (ColumnCount - 1) - ((int)item / ColumnCount);
        break;
      }

      return index;
    }
    #endregion

    #region Events
    public delegate CGSize WaterfallCollectionSizeDelegate(UICollectionView collectionView, WaterfallCollectionLayout layout, NSIndexPath indexPath);
    public delegate nfloat WaterfallCollectionFloatDelegate(UICollectionView collectionView, WaterfallCollectionLayout layout, nint section);
    public delegate UIEdgeInsets WaterfallCollectionEdgeInsetsDelegate(UICollectionView collectionView, WaterfallCollectionLayout layout, nint section);

    public event WaterfallCollectionSizeDelegate SizeForItem;
    public event WaterfallCollectionFloatDelegate HeightForHeader;
    public event WaterfallCollectionFloatDelegate HeightForFooter;
    public event WaterfallCollectionEdgeInsetsDelegate InsetForSection;
    public event WaterfallCollectionFloatDelegate MinimumInterItemSpacingForSection;
    #endregion
  }
}

Se puede usar la clase para proporcionar un diseño de tipo de cascada personalizado de dos columnas a la vista de colección. El código usa la codificación clave-valor (a través de los WillChangeValue métodos y DidChangeValue ) para proporcionar enlace de datos para nuestras propiedades calculadas en esta clase.

A continuación, edite y WaterfallCollectionSource realice los siguientes cambios y adiciones:

private Random rnd = new Random();
...

public List<nfloat> Heights { get; set; } = new List<nfloat> ();
...

public WaterfallCollectionSource (WaterfallCollectionView collectionView)
{
  // Initialize
  CollectionView = collectionView;

  // Init numbers collection
  for (int n = 0; n < 100; ++n) {
    Numbers.Add (n);
    Heights.Add (rnd.Next (0, 100) + 40.0f);
  }
}

Esto creará un alto aleatorio para cada uno de los elementos que se mostrarán en la lista.

A continuación, edite la WaterfallCollectionView clase y agregue la siguiente propiedad auxiliar:

public WaterfallCollectionSource Source {
  get { return (WaterfallCollectionSource)DataSource; }
}

Esto hará que sea más fácil obtener en nuestro origen de datos (y los alto del elemento) desde el diseño personalizado.

Por último, edite el controlador de vista y agregue el código siguiente:

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

  var waterfallLayout = new WaterfallCollectionLayout ();

  // Wireup events
  waterfallLayout.SizeForItem += (collectionView, layout, indexPath) => {
    var collection = collectionView as WaterfallCollectionView;
    return new CGSize((View.Bounds.Width-40)/3,collection.Source.Heights[(int)indexPath.Item]);
  };

  // Attach the custom layout to the collection
  CollectionView.SetCollectionViewLayout(waterfallLayout, false);
}

Esto crea una instancia de nuestro diseño personalizado, establece el evento para proporcionar el tamaño de cada elemento y adjunta el nuevo diseño a nuestra vista de colección.

Si se vuelve a ejecutar la aplicación de Xamarin.iOS, la vista de recopilación será similar a la siguiente:

The collection view will now look like this

Todavía podemos arrastrar a reordenar elementos como antes, pero los elementos ahora cambiarán el tamaño para ajustarse a su nueva ubicación cuando se quiten.

Cambios en la vista de colección

En las secciones siguientes, echaremos un vistazo detallado a los cambios realizados en cada clase de la vista de colección de iOS 9.

UICollectionView

Se han realizado los siguientes cambios o adiciones a la UICollectionView clase para iOS 9:

  • BeginInteractiveMovementForItem : marca el inicio de una operación de arrastre.
  • CancelInteractiveMovement : informa a la vista de recopilación de que el usuario ha cancelado una operación de arrastre.
  • EndInteractiveMovement : informa a la vista de colección de que el usuario ha finalizado una operación de arrastre.
  • GetIndexPathsForVisibleSupplementaryElements : devuelve el indexPath de un encabezado o pie de página en una sección de vista de colección.
  • GetSupplementaryView : devuelve el encabezado o pie de página especificados.
  • GetVisibleSupplementaryViews : devuelve una lista de todos los encabezados y pies de página visibles.
  • UpdateInteractiveMovementTargetPosition – Informa a la vista de colección que el usuario ha movido o está moviendo un elemento durante una operación de arrastre.

UICollectionViewController

Se han realizado los siguientes cambios o adiciones a la UICollectionViewController clase en iOS 9:

  • InstallsStandardGestureForInteractiveMovement – Si true se usará el nuevo reconocedor de gestos que admite automáticamente arrastrar a reordenar.
  • CanMoveItem : informa a la vista de colección si se puede arrastrar un elemento determinado de nuevo.
  • GetTargetContentOffset : se usa para obtener el desplazamiento de un elemento de vista de colección determinado.
  • GetTargetIndexPathForMove : obtiene el indexPath de un elemento determinado para una operación de arrastre.
  • MoveItem : mueve el orden de un elemento determinado en la lista.

UICollectionViewDataSource

Se han realizado los siguientes cambios o adiciones a la UICollectionViewDataSource clase en iOS 9:

  • CanMoveItem : informa a la vista de colección si se puede arrastrar un elemento determinado de nuevo.
  • MoveItem : mueve el orden de un elemento determinado en la lista.

UICollectionViewDelegate

Se han realizado los siguientes cambios o adiciones a la UICollectionViewDelegate clase en iOS 9:

  • GetTargetContentOffset : se usa para obtener el desplazamiento de un elemento de vista de colección determinado.
  • GetTargetIndexPathForMove : obtiene el indexPath de un elemento determinado para una operación de arrastre.

UICollectionViewFlowLayout

Se han realizado los siguientes cambios o adiciones a la UICollectionViewFlowLayout clase en iOS 9:

  • SectionFootersPinToVisibleBounds : pega los pies de página de sección a los límites de la vista de colección visibles.
  • SectionHeadersPinToVisibleBounds : se pegan los encabezados de sección a los límites de la vista de colección visibles.

UICollectionViewLayout

Se han realizado los siguientes cambios o adiciones a la UICollectionViewLayout clase en iOS 9:

  • GetInvalidationContextForEndingInteractiveMovementOfItems : devuelve el contexto de invalidación al final de una operación de arrastre cuando el usuario finaliza el arrastre o lo cancela.
  • GetInvalidationContextForInteractivelyMovingItems : devuelve el contexto de invalidación al principio de una operación de arrastre.
  • GetLayoutAttributesForInteractivelyMovingItem : obtiene los atributos de diseño de un elemento determinado mientras arrastra un elemento.
  • GetTargetIndexPathForInteractivelyMovingItem : devuelve el indexPath del elemento que se encuentra en el punto especificado al arrastrar un elemento.

UICollectionViewLayoutAttributes

Se han realizado los siguientes cambios o adiciones a la UICollectionViewLayoutAttributes clase en iOS 9:

  • CollisionBoundingPath : devuelve la ruta de colisión de dos elementos durante una operación de arrastre.
  • CollisionBoundsType : devuelve el tipo de colisión (como ) UIDynamicItemCollisionBoundsTypeque se ha producido durante una operación de arrastre.

UICollectionViewLayoutInvalidationContext

Se han realizado los siguientes cambios o adiciones a la UICollectionViewLayoutInvalidationContext clase en iOS 9:

  • InteractiveMovementTarget : devuelve el elemento de destino de una operación de arrastre.
  • PreviousIndexPathsForInteractivelyMovingItems : devuelve el indexPaths de otros elementos implicados en una operación de reordenación.
  • TargetIndexPathsForInteractivelyMovingItems : devuelve los indexPaths elementos que se reordenarán como resultado de una operación de arrastrar a reordenar.

UICollectionViewSource

Se han realizado los siguientes cambios o adiciones a la UICollectionViewSource clase en iOS 9:

  • CanMoveItem : informa a la vista de colección si se puede arrastrar un elemento determinado de nuevo.
  • GetTargetContentOffset : devuelve los desplazamientos de los elementos que se moverán a través de una operación de arrastrar a reordenar.
  • GetTargetIndexPathForMove : devuelve el indexPath de un elemento que se moverá durante una operación de arrastrar a reordenar.
  • MoveItem : mueve el orden de un elemento determinado en la lista.

Resumen

En este artículo se han tratado los cambios en las vistas de colección de iOS 9 y se ha descrito cómo implementarlos en Xamarin.iOS. Se ha tratado la implementación de una acción simple de arrastrar a reordenar en una vista de colección; usar un reconocedor de gestos personalizado con arrastrar a reordenar; y cómo el arrastrar a reordenar afecta a un diseño de vista de colección personalizado.