Rellenar una tabla con datos en Xamarin.iOS

Para agregar filas a , UITableView debe implementar una UITableViewSource subclase e invalidar los métodos a los que llama la vista de tabla para rellenarse.

En esta guía se explica lo siguiente:

  • Creación de subclases de uiTableViewSource
  • Reutilización de celdas
  • Agregar un índice
  • Agregar encabezados y pies de página

Subclase UITableViewSource

Se asigna una UITableViewSource subclase a cada UITableView. La vista de tabla consulta la clase de origen para determinar cómo representarse a sí misma (por ejemplo, cuántas filas son necesarias y el alto de cada fila si es diferente del valor predeterminado). Lo más importante es que el origen proporciona cada vista de celda rellenada con datos.

Solo hay dos métodos obligatorios necesarios para hacer que una tabla muestre los datos:

  • RowsInSection : devuelve un nint recuento del número total de filas de datos que debe mostrar la tabla.
  • GetCell : devuelve un UITableViewCell objeto rellenado con datos para el índice de fila correspondiente que se pasa al método .

El archivo de ejemplo BasicTable TableSource.cs tiene la implementación más sencilla posible de UITableViewSource. Puede ver en el fragmento de código siguiente que acepta una matriz de cadenas para mostrar en la tabla y devuelve un estilo de celda predeterminado que contiene cada cadena:

public class TableSource : UITableViewSource {

        string[] TableItems;
        string CellIdentifier = "TableCell";

        public TableSource (string[] items)
        {
            TableItems = items;
        }

        public override nint RowsInSection (UITableView tableview, nint section)
        {
            return TableItems.Length;
        }

        public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
        {
            UITableViewCell cell = tableView.DequeueReusableCell (CellIdentifier);
            string item = TableItems[indexPath.Row];

            //if there are no cells to reuse, create a new one
            if (cell == null)
            { 
                cell = new UITableViewCell (UITableViewCellStyle.Default, CellIdentifier); 
            }

            cell.TextLabel.Text = item;

            return cell;
        }
}

Un UITableViewSource puede usar cualquier estructura de datos, desde una matriz de cadenas simple (como se muestra en este ejemplo) a una lista <> u otra colección. La implementación de UITableViewSource métodos aísla la tabla de la estructura de datos subyacente.

Para usar esta subclase, cree una matriz de cadenas para construir el origen y asígnela a una instancia de UITableView:

public override void ViewDidLoad ()
{
    base.ViewDidLoad ();
    table = new UITableView(View.Bounds); // defaults to Plain style
    string[] tableItems = new string[] {"Vegetables","Fruits","Flower Buds","Legumes","Bulbs","Tubers"};
    table.Source = new TableSource(tableItems);
    Add (table);
}

La tabla resultante tiene el siguiente aspecto:

Tabla de ejemplo en ejecución

La mayoría de las tablas permiten al usuario tocar una fila para seleccionarla y realizar alguna otra acción (como reproducir una canción o llamar a un contacto o mostrar otra pantalla). Para lograrlo, hay algunas cosas que debemos hacer. En primer lugar, vamos a crear un AlertController para mostrar un mensaje cuando el usuario haga clic en una fila agregando lo siguiente al RowSelected método :

public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
    UIAlertController okAlertController = UIAlertController.Create ("Row Selected", tableItems[indexPath.Row], UIAlertControllerStyle.Alert);
    okAlertController.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
    ...

    tableView.DeselectRow (indexPath, true);
}

A continuación, cree una instancia de nuestro controlador de vista:

HomeScreen owner;

Agregue un constructor a la clase UITableViewSource que toma un controlador de vista como parámetro y lo guarda en un campo:

public TableSource (string[] items, HomeScreen owner)
{
    ...
    this.owner = owner;

}

Modifique el método ViewDidLoad donde se crea la clase UITableViewSource para pasar la this referencia:

table.Source = new TableSource(tableItems, this);

Por último, de nuevo en el RowSelected método , llame a PresentViewController en el campo almacenado en caché:

public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
    ...
    owner.PresentViewController (okAlertController, true, null);

    ...
}

Ahora el usuario puede tocar una fila y aparecerá una alerta:

Alerta seleccionada de fila

Reutilización de celdas

En este ejemplo solo hay seis elementos, por lo que no se requiere ninguna reutilización de celdas. Sin embargo, al mostrar cientos o miles de filas, sería un desperdicio de memoria para crear cientos o miles de UITableViewCell objetos cuando solo caben unos pocos en la pantalla a la vez.

Para evitar esta situación, cuando una celda desaparece de la pantalla, su vista se coloca en una cola para reutilizarla. A medida que el usuario se desplaza, la tabla llama GetCell a para solicitar nuevas vistas que se muestren: para reutilizar una celda existente (que no se muestra actualmente) simplemente llame al DequeueReusableCell método . Si hay una celda disponible para reutilizarla, se devolverá un valor NULL; de lo contrario, se devuelve un valor NULL y el código debe crear una nueva instancia de celda.

Este fragmento de código del ejemplo muestra el patrón:

// request a recycled cell to save memory
UITableViewCell cell = tableView.DequeueReusableCell (cellIdentifier);
// if there are no cells to reuse, create a new one
if (cell == null)
    cell = new UITableViewCell (UITableViewCellStyle.Default, cellIdentifier);

Crea cellIdentifier colas independientes para diferentes tipos de celdas. En este ejemplo, todas las celdas parecen iguales, por lo que solo se usa un identificador codificado de forma rígida. Si hubiera diferentes tipos de celda, cada una de ellas debería tener una cadena de identificador diferente, tanto cuando se crean instancias de ellos como cuando se solicitan desde la cola de reutilización.

Reutilización de celdas en iOS 6+

iOS 6 agregó un patrón de reutilización de celdas similar al de una introducción con vistas de colección. Aunque el patrón de reutilización existente mostrado anteriormente sigue siendo compatible con versiones anteriores, este nuevo patrón es preferible, ya que elimina la necesidad de la comprobación nula en la celda.

Con el nuevo patrón, una aplicación registra la clase de celda o xib que se va a usar mediante una llamada a RegisterClassForCellReuse o RegisterNibForCellReuse en el constructor del controlador. A continuación, al quitar la cola de la celda en el GetCell método, simplemente llame a DequeueReusableCell pasar el identificador que registró para la clase de celda o xib y la ruta de acceso del índice.

Por ejemplo, el código siguiente registra una clase de celda personalizada en un UITableViewController:

public class MyTableViewController : UITableViewController
{
  static NSString MyCellId = new NSString ("MyCellId");

  public MyTableViewController ()
  {
    TableView.RegisterClassForCellReuse (typeof(MyCell), MyCellId);
  }
  ...
}

Con la clase MyCell registrada, la celda se puede quitar de la cola en el GetCell método de sin necesidad de UITableViewSource la comprobación de null adicional, como se muestra a continuación:

class MyTableSource : UITableViewSource
{
  public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
  {
    // if cell is not available in reuse pool, iOS will create one automatically
    // no need to do null check and create cell manually
    var cell = (MyCell) tableView.DequeueReusableCell (MyCellId, indexPath);

    // do whatever you need to with cell, such as assigning properties, etc.

    return cell;
  }
}

Tenga en cuenta que, al usar el nuevo patrón de reutilización con una clase de celda personalizada, debe implementar el constructor que toma un IntPtr, como se muestra en el fragmento de código siguiente; de lo contrario Objective-C , no podrá construir una instancia de la clase de celda:

public class MyCell : UITableViewCell
{
  public MyCell (IntPtr p):base(p)
  {
  }
  ...
}

Puede ver ejemplos de los temas explicados anteriormente en el ejemplo BasicTable vinculado a este artículo.

Agregar un índice

Un índice ayuda al usuario a desplazarse por listas largas, normalmente ordenadas alfabéticamente, aunque puede indexar según los criterios que desee. El ejemplo BasicTableIndex carga una lista mucho más larga de elementos de un archivo para mostrar el índice. Cada elemento del índice corresponde a una "sección" de la tabla.

Presentación del índice

Para admitir "secciones", los datos detrás de la tabla deben agruparse, por lo que el ejemplo BasicTableIndex crea una Dictionary<> a partir de la matriz de cadenas mediante la primera letra de cada elemento como clave de diccionario:

indexedTableItems = new Dictionary<string, List<string>>();
foreach (var t in items) {
    if (indexedTableItems.ContainsKey (t[0].ToString ())) {
        indexedTableItems[t[0].ToString ()].Add(t);
    } else {
        indexedTableItems.Add (t[0].ToString (), new List<string>() {t});
    }
}
keys = indexedTableItems.Keys.ToArray ();

A continuación, la UITableViewSource subclase necesita los métodos siguientes agregados o modificados para usar :Dictionary<>

  • NumberOfSections : este método es opcional; de forma predeterminada, la tabla supone una sección. Al mostrar un índice, este método debe devolver el número de elementos del índice (por ejemplo, 26 si el índice contiene todas las letras del alfabeto inglés).
  • RowsInSection : devuelve el número de filas de una sección determinada.
  • SectionIndexTitles : devuelve la matriz de cadenas que se usarán para mostrar el índice. El código de ejemplo devuelve una matriz de letras.

Los métodos actualizados del archivo de ejemplo BasicTableIndex/TableSource.cs son similares a los siguientes:

public override nint NumberOfSections (UITableView tableView)
{
    return keys.Length;
}
public override nint RowsInSection (UITableView tableview, nint section)
{
    return indexedTableItems[keys[section]].Count;
}
public override string[] SectionIndexTitles (UITableView tableView)
{
    return keys;
}

Por lo general, los índices solo se usan con el estilo de tabla Plain.

Agregar encabezados y pies de página

Los encabezados y pies de página se pueden usar para agrupar visualmente las filas de una tabla. La estructura de datos necesaria es muy similar a agregar un índice: funciona Dictionary<> muy bien. En lugar de usar el alfabeto para agrupar las celdas, en este ejemplo se agruparán las verduras por tipo botánico. La salida es similar a esta:

Encabezados y pies de página de ejemplo

Para mostrar encabezados y pies de página, la UITableViewSource subclase requiere estos métodos adicionales:

  • TitleForHeader : devuelve el texto que se va a usar como encabezado.
  • TitleForFooter : devuelve el texto que se va a usar como pie de página.

Los métodos actualizados del archivo de ejemplo BasicTableHeaderFooter/Code/TableSource.cs son similares a los siguientes:

public override string TitleForHeader (UITableView tableView, nint section)
{
    return keys[section];
}
public override string TitleForFooter (UITableView tableView, nint section)
{
    return indexedTableItems[keys[section]].Count + " items";
}

Puede personalizar aún más la apariencia del encabezado y el pie de página con un objeto View, mediante las invalidaciones del GetViewForHeader método y GetViewForFooter en UITableViewSource.