Exibições de coleção no Xamarin.iOS

As Exibições de Coleção permitem que o conteúdo seja exibido usando layouts arbitrários. Eles permitem criar facilmente layouts semelhantes a grades prontos para uso, ao mesmo tempo em que oferecem suporte a layouts personalizados.

As Visualizações de UICollectionView Coleção, disponíveis na classe, são um novo conceito no iOS 6 que introduz a apresentação de vários itens na tela usando layouts. Os padrões para fornecer dados a um UICollectionView para criar itens e interagir com esses itens seguem os mesmos padrões de delegação e fonte de dados comumente usados no desenvolvimento do iOS.

No entanto, as Exibições de Coleção funcionam com um subsistema de layout que é independente do UICollectionView próprio. Portanto, o simples fornecimento de um layout diferente pode alterar facilmente a apresentação de um Modo de Exibição de Coleção.

O iOS fornece uma classe de layout chamada UICollectionViewFlowLayout que permite que layouts baseados em linha, como uma grade, sejam criados sem trabalho adicional. Além disso, layouts personalizados também podem ser criados que permitem qualquer apresentação que você possa imaginar.

Noções básicas de UICollectionView

A UICollectionView classe é composta por três itens diferentes:

  • Células – exibições controladas por dados para cada item
  • Exibições suplementares – exibições controladas por dados associadas a uma seção.
  • Modos de exibição de decoração – modos de exibição não controlados por dados criados por um layout

Cells

As células são objetos que representam um único item no conjunto de dados que está sendo apresentado pelo modo de exibição de coleção. Cada célula é uma instância da UICollectionViewCell classe, que é composta por três modos de exibição diferentes, como mostra a figura abaixo:

Cada célula é composta por três modos de exibição diferentes, como mostrado aqui

A UICollectionViewCell classe tem as seguintes propriedades para cada um desses modos de exibição:

  • ContentView – Esta vista contém o conteúdo que a célula apresenta. Ele é renderizado na ordem z mais alta na tela.
  • SelectedBackgroundView – As células têm suporte embutido para seleção. Esse modo de exibição é usado para indicar visualmente que uma célula está selecionada. Ele é renderizado logo abaixo do ContentView quando uma célula é selecionada.
  • BackgroundView – As células também podem exibir um plano de fundo, que é apresentado pelo BackgroundView . Essa exibição é renderizada abaixo do SelectedBackgroundView .

Ao definir o ContentView tal que ele é menor que o BackgroundView e SelectedBackgroundView, o BackgroundView pode ser usado para enquadrar visualmente o conteúdo, enquanto o SelectedBackgroundView será exibido quando uma célula é selecionada, como mostrado abaixo:

Os diferentes elementos da célula

As células na captura de tela acima são criadas herdando e definindo as ContentViewpropriedades , UICollectionViewCell SelectedBackgroundView e BackgroundView , respectivamente, conforme mostrado no código a seguir:

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

Visões complementares

Exibições suplementares são exibições que apresentam informações associadas a cada seção de um UICollectionViewarquivo . Assim como as Células, as Exibições Suplementares são orientadas por dados. Onde as Células apresentam os dados do item de uma fonte de dados, as Exibições Suplementares apresentam os dados da seção, como as categorias de livro em uma estante ou o gênero de música em uma biblioteca de música.

Por exemplo, um Modo de Exibição Suplementar pode ser usado para apresentar um cabeçalho para uma seção específica, como mostra a figura abaixo:

Um Modo de Exibição Suplementar usado para apresentar um cabeçalho para uma seção específica, conforme mostrado aqui

Para usar uma Visão Suplementar, ela primeiro precisa ser registrada no ViewDidLoad método:

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

Em seguida, o modo de exibição precisa ser retornado usando GetViewForSupplementaryElement, criado usando DequeueReusableSupplementaryView, e herda de UICollectionReusableView. O trecho de código a seguir produzirá o SupplementaryView mostrado na captura de tela acima:

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

As Exibições Suplementares são mais genéricas do que apenas cabeçalhos e rodapés. Eles podem ser posicionados em qualquer lugar na visualização da coleção e podem ser compostos por quaisquer modos de exibição, tornando sua aparência totalmente personalizável.

Vistas da decoração

As Vistas de Decoração são vistas puramente visuais que podem ser exibidas em um UICollectionViewarquivo . Ao contrário das Células e das Exibições Suplementares, elas não são orientadas por dados. Eles são sempre criados dentro da subclasse de um layout e, posteriormente, podem ser alterados conforme o layout do conteúdo. Por exemplo, um Modo de Exibição de Decoração pode ser usado para apresentar um modo de exibição de plano de fundo que rola com o conteúdo no UICollectionView, conforme mostrado abaixo:

Vista da decoração com um fundo vermelho

O trecho de código abaixo altera o plano de fundo para vermelho na classe de exemplos CircleLayout :

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

Fonte de dados

Assim como acontece com outras partes do iOS, como UITableView e MKMapView, UICollectionView obtém seus dados de uma fonte de dados, que é exposta no Xamarin.iOS por meio da UICollectionViewDataSource classe. Esta classe é responsável por fornecer conteúdo para os UICollectionView tais como:

  • Células – Retornadas do GetCell método.
  • Exibições suplementares – Retornadas do GetViewForSupplementaryElement método.
  • Número de seções – Retornado do NumberOfSections método. O padrão é 1 se não for implementado.
  • Número de itens por seção – Retornado do GetItemsCount método.

UICollectionViewController

Para maior comodidade, a UICollectionViewController classe está disponível. Isso é configurado automaticamente para ser o representante, que é discutido na próxima seção, e a fonte de dados para sua UICollectionView exibição.

Assim como no UITableView, a UICollectionView classe chamará apenas sua fonte de dados para obter Células para itens que estão na tela. As células que rolam para fora da tela são colocadas em uma fila para reutilização, como ilustra a imagem a seguir:

As células que rolam para fora da tela são colocadas em uma fila para reutilização, conforme mostrado aqui

A reutilização de células foi simplificada com UICollectionView e UITableView. Você não precisará mais criar uma Célula diretamente na fonte de dados se uma não estiver disponível na fila de reutilização, pois as Células estão registradas no sistema. Se uma Célula não estiver disponível ao fazer a chamada para retirar a Célula da fila de reutilização, o iOS a criará automaticamente com base no tipo ou nib que foi registrado. A mesma técnica também está disponível para Visões Suplementares.

Por exemplo, considere o seguinte código que registra a AnimalCell classe:

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

Quando um UICollectionView precisa de uma célula porque seu item está na tela, o chama o UICollectionView método de sua fonte de GetCell dados. Semelhante a como isso funciona com UITableView, esse método é responsável por configurar uma célula a partir dos dados de backup, que seria uma AnimalCell classe neste caso.

O código a seguir mostra uma implementação que GetCell retorna uma AnimalCell instância:

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

A chamada para DequeReusableCell é onde a célula será removida da fila de reutilização ou, se uma célula não estiver disponível na fila, criada com base no tipo registrado na chamada para CollectionView.RegisterClassForCell.

Nesse caso, ao registrar a classe, o AnimalCell iOS criará uma nova AnimalCell internamente e a retornará quando uma chamada para desfilar uma célula for feita, após o que ela é configurada com a imagem contida na classe animal e retornada para exibição no UICollectionView.

Delegar

A UICollectionView classe usa um delegado do tipo UICollectionViewDelegate para dar suporte à interação com o conteúdo no UICollectionView. Isso permite o controle de:

  • Seleção de Célula – Determina se uma célula está selecionada.
  • Realce de Célula – Determina se uma célula está sendo tocada no momento.
  • Menus de célula – Menu exibido para uma célula em resposta a um gesto de pressionar longo.

Assim como acontece com a fonte de dados, o UICollectionViewController é configurado por padrão para ser o delegado do UICollectionView.

Cell HighLighting

Quando uma célula é pressionada, a célula faz a transição para um estado realçado e não é selecionada até que o usuário levante o dedo da célula. Isso permite uma alteração temporária na aparência da célula antes que ela seja realmente selecionada. Após a seleção, a célula é SelectedBackgroundView exibida. A figura abaixo mostra o estado destacado pouco antes da seleção ocorrer:

Esta figura mostra o estado destacado imediatamente antes da seleção ocorrer

Para implementar o realce, os ItemHighlighted métodos e ItemUnhighlighted métodos do UICollectionViewDelegate podem ser usados. Por exemplo, o código a seguir aplicará um plano de fundo amarelo quando ContentView a célula estiver realçada e um plano de fundo branco quando não realçado, conforme mostrado na imagem acima:

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

Desativando a seleção

A seleção é habilitada por padrão no UICollectionView. Para desativar a seleção, substitua ShouldHighlightItem e retorne false conforme mostrado abaixo:

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

Quando o realce está desabilitado, o processo de seleção de uma célula também é desabilitado. Além disso, há também um método que controla a ShouldSelectItem seleção diretamente, embora se ShouldHighlightItem for implementado e retornar false, ShouldSelectItem não é chamado.

ShouldSelectItem Permite que a seleção seja ativada ou desativada item a item, quando ShouldHighlightItem não for implementada. Ele também permite realce sem seleção, se ShouldHighlightItem é implementado e retorna true, enquanto ShouldSelectItem retorna false.

Menus de célula

Cada célula em um UICollectionView é capaz de mostrar um menu que permite recortar, copiar e colar para opcionalmente ser suportado. Para criar um menu de edição em uma célula:

  1. Substitua ShouldShowMenu e retorne true se o item mostrar um menu.
  2. Substitua CanPerformAction e retorne true para cada ação que o item pode executar, que será qualquer uma de recortar, copiar ou colar.
  3. Substituir PerformAction para executar a operação de edição, cópia de colagem.

A captura de tela a seguir mostra o menu quando uma célula é pressionada por muito tempo:

Esta captura de tela mostra o menu quando uma célula é pressionada por muito tempo

Layout

UICollectionView suporta um sistema de layout que permite que o posicionamento de todos os seus elementos, Células, Vistas Complementares e Vistas de Decoração, sejam gerenciados independentemente do UICollectionView próprio. Usando o sistema de layout, um aplicativo pode oferecer suporte a layouts como o semelhante à grade que vimos neste artigo, bem como fornecer layouts personalizados.

Noções básicas de layout

Os layouts em um UICollectionView são definidos em uma classe que herda do UICollectionViewLayout. A implementação de layout é responsável por criar os atributos de layout para cada item no UICollectionView. Há duas maneiras de criar um layout:

  • Use o UICollectionViewFlowLayout arquivo interno .
  • Forneça um layout personalizado herdando do UICollectionViewLayout .

Layout de fluxo

A UICollectionViewFlowLayout classe fornece um layout baseado em linha adequado para organizar o conteúdo em uma grade de células, como vimos.

Para usar um layout de fluxo:

  • Crie uma instância de UICollectionViewFlowLayout :
var layout = new UICollectionViewFlowLayout ();
  • Passe a instância para o construtor do UICollectionView :
simpleCollectionViewController = new SimpleCollectionViewController (layout);

Isso é tudo o que é necessário para esquematizar o conteúdo em uma grade. Além disso, quando a orientação muda, os UICollectionViewFlowLayout identificadores reorganizam o conteúdo adequadamente, como mostrado abaixo:

Exemplo das mudanças de orientação

Seção Inset

Para fornecer algum espaço ao redor do UIContentView, os layouts têm uma SectionInset propriedade do tipo UIEdgeInsets. Por exemplo, o código a seguir fornece um buffer de 50 pixels ao redor de cada seção do UIContentView quando disposto por um UICollectionViewFlowLayout:

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

Isso resulta em espaçamento ao redor da seção, conforme mostrado abaixo:

Espaçamento ao redor da seção como mostrado aqui

Subclassificando UICollectionViewFlowLayout

Na edição para usar UICollectionViewFlowLayout diretamente, ele também pode ser subclassificado para personalizar ainda mais o layout do conteúdo ao longo de uma linha. Por exemplo, isso pode ser usado para criar um layout que não encapsula as células em uma grade, mas cria uma única linha com um efeito de rolagem horizontal, conforme mostrado abaixo:

Uma única linha com um efeito de rolagem horizontal

Para implementar isso por subclassificação UICollectionViewFlowLayout requer:

  • Inicializar quaisquer propriedades de layout que se apliquem ao próprio layout ou a todos os itens no layout no construtor.
  • Substituindo ShouldInvalidateLayoutForBoundsChange , retornando true para que, quando os limites das UICollectionView alterações, o layout das células seja recalculado. Isso é usado nesse caso, certifique-se de que o código para transformação aplicado à célula mais central será aplicado durante a rolagem.
  • Substituição TargetContentOffset para fazer com que a célula mais central se encaixe no centro da rolagem à medida que a UICollectionView rolagem para.
  • Substituindo LayoutAttributesForElementsInRect para retornar uma matriz de UICollectionViewLayoutAttributes . Cada um UICollectionViewLayoutAttribute contém informações sobre como esquematizar o item específico, incluindo propriedades como seu Center , Size ZIndex e Transform3D .

O código a seguir mostra essa implementação:

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

  }
}

Layout personalizado

Além de usar UICollectionViewFlowLayouto , os layouts também podem ser totalmente personalizados herdando diretamente do UICollectionViewLayout.

Os principais métodos a serem substituídos são:

  • PrepareLayout – Utilizado para a realização de cálculos geométricos iniciais que serão utilizados durante todo o processo de layout.
  • CollectionViewContentSize – Retorna o tamanho da área usada para exibir o conteúdo.
  • LayoutAttributesForElementsInRect – Assim como no exemplo UICollectionViewFlowLayout mostrado anteriormente, esse método é usado para fornecer informações sobre UICollectionView como esquematizar cada item. No entanto, ao contrário do UICollectionViewFlowLayout , ao criar um layout personalizado, você pode posicionar os itens da maneira que quiser.

Por exemplo, o mesmo conteúdo poderia ser apresentado em um layout circular, como mostrado abaixo:

Um layout personalizado circular, como mostrado aqui

A coisa poderosa sobre layouts é que para mudar do layout de grade, para um layout de rolagem horizontal e, posteriormente, para esse layout circular requer apenas a classe de layout fornecida para ser UICollectionView alterada. Nada no UICollectionView, seu delegado ou código-fonte de dados é alterado.

Alterações no iOS 9

No iOS 9, a visualização de coleção (UICollectionView) agora oferece suporte à reordenação de arrastar itens para fora da caixa, adicionando um novo reconhecedor de gestos padrão e vários novos métodos de suporte.

Usando esses novos métodos, você pode facilmente implementar arrastar para reordenar em sua exibição de coleção e ter a opção de personalizar a aparência dos itens durante qualquer estágio do processo de reordenação.

Um exemplo do processo de reordenação

Neste artigo, vamos dar uma olhada na implementação de arrastar para reordenar em um aplicativo Xamarin.iOS, bem como algumas das outras alterações que o iOS 9 fez no controle de exibição de coleção:

Reordenação de Itens

Como dito acima, uma das mudanças mais significativas na visualização de coleção no iOS 9 foi a adição da funcionalidade fácil de arrastar para reordenar fora da caixa.

No iOS 9, a maneira mais rápida de adicionar reordenação a uma exibição de coleção é usar um UICollectionViewControllerarquivo . O controlador de exibição de coleção agora tem uma InstallsStandardGestureForInteractiveMovement propriedade, que adiciona um reconhecedor de gesto padrão que oferece suporte a arrastar para reordenar itens na coleção. Como o valor padrão é true, você só precisa implementar o MoveItem UICollectionViewDataSource método da classe para dar suporte a arrastar para reordenar. Por exemplo:

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

Exemplo de reordenação simples

Como um exemplo rápido, inicie um novo projeto Xamarin.iOS e edite o arquivo Main.storyboard . Arraste um para a superfície de UICollectionViewController design:

Adicionando um UICollectionViewController

Selecione o Modo de Exibição de Coleção (pode ser mais fácil fazer isso a partir da estrutura de tópicos do documento). Na guia layout do Bloco de propriedades, defina os seguintes tamanhos, conforme ilustrado na captura de tela abaixo:

  • Tamanho do alvéolo: Largura – 60 | Altura – 60
  • Tamanho do cabeçalho: Largura – 0 | Altura – 0
  • Tamanho do rodapé: Largura – 0 | Altura – 0
  • Espaçamento mínimo: para células – 8 | Para Linhas – 8
  • Seções Inserções: Topo – 16 | Fundo – 16 | Esquerda – 16 | Direito – 16

Definir os tamanhos do Modo de Exibição da Coleção

Em seguida, edite a célula padrão:

  • Alterar a cor do plano de fundo para azul
  • Adicionar um rótulo para atuar como o título da célula
  • Definir o identificador de reutilização como célula

Editar a célula padrão

Adicione restrições para manter o Rótulo centralizado dentro da célula à medida que ele muda de tamanho:

No bloco de propriedades para o CollectionViewCell e defina a classe como TextCollectionViewCell:

Definir a classe como TextCollectionViewCell

Defina o Modo de Exibição Reutilizável da Coleção comoCell:

Definir o Modo de Exibição Reutilizável da Coleção como Célula

Finalmente, selecione o Rótulo e nomeie-o TextLabel:

rótulo de nome TextLabel

Edite a TextCollectionViewCell classe e adicione as seguintes propriedades.:

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

Aqui, a Text propriedade do rótulo é exposta como o título da célula, portanto, ele pode ser definido a partir do código.

Adicione uma nova classe C# ao projeto e chame-a WaterfallCollectionSourcede . Edite o arquivo e faça com que ele tenha a seguinte aparência:

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

Essa classe será a fonte de dados para nossa exibição de coleção e fornecerá as informações para cada célula na coleção. Observe que o MoveItem método é implementado para permitir que os itens na coleção sejam arrastados reordenados.

Adicione outra nova classe C# ao projeto e chame-a WaterfallCollectionDelegatede . Edite este arquivo e torne-o parecido com o seguinte:

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

Isso atuará como o delegado para nossa visão de coleção. Os métodos foram substituídos para realçar uma célula à medida que o usuário interage com ela no modo de exibição de coleção.

Adicione uma última classe C# ao projeto e chame-a WaterfallCollectionViewde . Edite este arquivo e torne-o parecido com o seguinte:

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 e Delegate que criamos acima são definidos quando a exibição de coleção é construída a partir de seu storyboard (ou arquivo .xib ).

Edite o arquivo Main.storyboard novamente e selecione a exibição da coleção e alterne para as Propriedades. Defina a classe para a classe personalizada WaterfallCollectionView que definimos acima:

Salve as alterações feitas na interface do usuário e execute o aplicativo. Se o usuário selecionar um item da lista e arrastá-lo para um novo local, os outros itens serão animados automaticamente à medida que saírem do caminho do item. Quando o usuário soltar o item em um novo local, ele ficará nesse local. Por exemplo:

Um exemplo de arrastar um item para um novo local

Usando um reconhecedor de gestos personalizado

Nos casos em que você não pode usar um UICollectionViewController e deve usar um UIViewController, ou se você deseja ter mais controle sobre o gesto de arrastar e soltar, você pode criar seu próprio Reconhecedor de gestos personalizado e adicioná-lo ao Modo de Exibição de Coleção quando o Modo de Exibição for carregado. Por exemplo:

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

Aqui estamos usando vários novos métodos adicionados à exibição de coleção para implementar e controlar a operação de arrastar:

  • BeginInteractiveMovementForItem - Marca o início de uma operação de movimentação.
  • UpdateInteractiveMovementTargetPosition - É enviado à medida que a localização do item é atualizada.
  • EndInteractiveMovement - Marca o final de uma movimentação de item.
  • CancelInteractiveMovement - Marca o usuário cancelando a operação de movimentação.

Quando o aplicativo for executado, a operação de arrastar funcionará exatamente como o reconhecedor de gesto de arrastar padrão que acompanha o modo de exibição de coleção.

Layouts personalizados e reordenação

No iOS 9, vários novos métodos foram adicionados para trabalhar com layouts de arrastar para reordenar e personalizados em um modo de exibição de coleção. Para explorar esse recurso, vamos adicionar um layout personalizado à coleção.

Primeiro, adicione uma nova classe C# chamada WaterfallCollectionLayout ao projeto. Edite-o e torne-o parecido com o seguinte:

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

Essa classe pode ser usada para fornecer um layout personalizado de duas colunas, tipo cascata para o modo de exibição de coleção. O código usa a codificação Key-Value (por meio dos WillChangeValue métodos e DidChangeValue ) para fornecer vinculação de dados para nossas propriedades computadas nessa classe.

Em seguida, edite o WaterfallCollectionSource e faça as seguintes alterações e adições:

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

Isso criará uma altura aleatória para cada um dos itens que serão exibidos na lista.

Em seguida, edite a WaterfallCollectionView classe e adicione a seguinte propriedade auxiliar:

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

Isso facilitará a obtenção de nossa fonte de dados (e das alturas dos itens) a partir do layout personalizado.

Finalmente, edite o controlador de exibição e adicione o seguinte código:

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

Isso cria uma instância de nosso layout personalizado, define o evento para fornecer o tamanho de cada item e anexa o novo layout à nossa exibição de coleção.

Se executarmos o aplicativo Xamarin.iOS novamente, a exibição de coleção agora terá a seguinte aparência:

O modo de exibição da coleção agora terá a seguinte aparência:

Ainda podemos arrastar para reordenar itens como antes, mas os itens agora mudarão de tamanho para se ajustarem ao novo local quando forem soltos.

Alterações no Modo de Exibição de Coleção

Nas seções a seguir, daremos uma olhada detalhada nas alterações feitas em cada classe na visualização de coleção pelo iOS 9.

UICollectionView

As seguintes alterações ou adições foram feitas na UICollectionView classe para iOS 9:

  • BeginInteractiveMovementForItem – Marca o início de uma operação de arrastar.
  • CancelInteractiveMovement – Informa à exibição de coleção que o usuário cancelou uma operação de arrastar.
  • EndInteractiveMovement – Informa à exibição de coleção que o usuário concluiu uma operação de arrastar.
  • GetIndexPathsForVisibleSupplementaryElements – Retorna o indexPath de um cabeçalho ou rodapé em uma seção de exibição de coleção.
  • GetSupplementaryView – Retorna o cabeçalho ou rodapé fornecido.
  • GetVisibleSupplementaryViews – Retorna uma lista de todos os cabeçalhos e rodapés visíveis.
  • UpdateInteractiveMovementTargetPosition – Informa à exibição de coleção que o usuário moveu ou está movendo um item durante uma operação de arrastar.

UICollectionViewController

As seguintes alterações ou adições foram feitas na UICollectionViewController classe no iOS 9:

  • InstallsStandardGestureForInteractiveMovement – Se true o novo Gesture Recognizer que suporta automaticamente arrastar para reordenar será usado.
  • CanMoveItem – Informa a visualização da coleção se um determinado item pode ser arrastado reordenado.
  • GetTargetContentOffset – Usado para obter o deslocamento de um determinado item de exibição de coleção.
  • GetTargetIndexPathForMove – Obtém o indexPath de um determinado item para uma operação de arrastar.
  • MoveItem – Move a ordem de um determinado item na lista.

UICollectionViewDataSource

As seguintes alterações ou adições foram feitas na UICollectionViewDataSource classe no iOS 9:

  • CanMoveItem – Informa a visualização da coleção se um determinado item pode ser arrastado reordenado.
  • MoveItem – Move a ordem de um determinado item na lista.

UICollectionViewDelegate

As seguintes alterações ou adições foram feitas na UICollectionViewDelegate classe no iOS 9:

  • GetTargetContentOffset – Usado para obter o deslocamento de um determinado item de exibição de coleção.
  • GetTargetIndexPathForMove – Obtém o indexPath de um determinado item para uma operação de arrastar.

UICollectionViewFlowLayout

As seguintes alterações ou adições foram feitas na UICollectionViewFlowLayout classe no iOS 9:

  • SectionFootersPinToVisibleBounds – Cola os rodapés da seção aos limites visíveis da visualização da coleção.
  • SectionHeadersPinToVisibleBounds – Adere os cabeçalhos de seção aos limites visíveis da exibição da coleção.

UICollectionViewLayout

As seguintes alterações ou adições foram feitas na UICollectionViewLayout classe no iOS 9:

  • GetInvalidationContextForEndingInteractiveMovementOfItems – Retorna o contexto de invalidação no final de uma operação de arrastar quando o usuário termina o arrasto ou o cancela.
  • GetInvalidationContextForInteractivelyMovingItems – Retorna o contexto de invalidação no início de uma operação de arrastar.
  • GetLayoutAttributesForInteractivelyMovingItem – Obtém os atributos de layout para um determinado item enquanto arrasta um item.
  • GetTargetIndexPathForInteractivelyMovingItem – Retorna o indexPath do item que está no ponto determinado ao arrastar um item.

UICollectionViewLayoutAttributes

As seguintes alterações ou adições foram feitas na UICollectionViewLayoutAttributes classe no iOS 9:

  • CollisionBoundingPath – Retorna o caminho de colisão de dois itens durante uma operação de arraste.
  • CollisionBoundsType – Retorna o tipo de colisão (como um UIDynamicItemCollisionBoundsType) que ocorreu durante uma operação de arrasto.

UICollectionViewLayoutInvalidationContext

As seguintes alterações ou adições foram feitas na UICollectionViewLayoutInvalidationContext classe no iOS 9:

  • InteractiveMovementTarget – Retorna o item de destino de uma operação de arrastar.
  • PreviousIndexPathsForInteractivelyMovingItems – Retorna o indexPaths de outros itens envolvidos em uma operação de arrastar para reordenar.
  • TargetIndexPathsForInteractivelyMovingItems – Retorna o indexPaths de itens que serão reordenados como resultado de uma operação de arrastar para reordenar.

UICollectionViewSource

As seguintes alterações ou adições foram feitas na UICollectionViewSource classe no iOS 9:

  • CanMoveItem – Informa a visualização da coleção se um determinado item pode ser arrastado reordenado.
  • GetTargetContentOffset – Retorna os deslocamentos de itens que serão movidos por meio de uma operação de arrastar para reordenar.
  • GetTargetIndexPathForMove – Retorna o de um item que será movido indexPath durante uma operação de arrastar para reordenar.
  • MoveItem – Move a ordem de um determinado item na lista.

Resumo

Este artigo abordou as alterações nas exibições de coleção no iOS 9 e descreveu como implementá-las no Xamarin.iOS. Abrangeu a implementação de uma ação simples de arrastar para reordenar em uma exibição de coleção; usando um Reconhecedor de Gestos personalizado com arrastar para reordenar; e como arrastar para reordenar afeta um layout de exibição de coleção personalizado.