Compartir vía


Rellenar una tabla con datos en Xamarin.iOS

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

En esta guía se explica lo siguiente:

  • Subclasificación 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 (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 rellenado con datos para el índice de fila correspondiente pasado 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 este aspecto:

Ejecución de una tabla de ejemplo

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 necesitamos 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 método RowSelected:

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 referencia de this:

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

Por último, vuelva al método RowSelected, 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:

La fila seleccionada alerta

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 crear cientos o miles de objetos UITableViewCell cuando solo caben algunos 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 su reutilización. A medida que el usuario se desplaza, la tabla llama a GetCellpara solicitar nuevas vistas que mostrar - para reutilizar una celda existente (que no se esté mostrando en ese momento) basta con llamar al métodoDequeueReusableCell. Si una celda está disponible para reutilizarla se devolverá; 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);

El cellIdentifier crea colas independientes para distintos tipos de celdas. En este ejemplo, todas las celdas tienen el mismo aspecto, por lo que solo se usa un identificador codificado de forma rígida. Si hay diferentes tipos de celda, cada una debe tener una cadena de identificador diferente, tanto cuando se crean instancias 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 que se muestra 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, cuando ponga en cola la celda en el métodoGetCell, simplemente llame DequeueReusableCell pasando el identificador que registró para la clase de celda o xib y la ruta de acceso al í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 poner en cola en el método GetCell del UITableViewSource sin necesidad de la comprobación extra null, 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 de 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.

La presentación del índice

Para admitir ‘secciones’, es necesario agrupar los datos detrás de la tabla, por lo que el ejemplo BasicTableIndex crea un 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 subclase UITableViewSource necesita los métodos siguientes agregados o modificados para usar el 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á 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 tienen este aspecto:

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 (Simple).

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 la de agregar un índice - a Dictionary<> funciona realmente bien. En lugar de usar el alfabeto para agrupar las celdas, este ejemplo agrupará las verduras por tipo botánico. La salida es similar a esta:

Encabezados y pies de ejemplo

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

  • TitleForHeader: devuelve el texto que se va a usar como encabezad
  • 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 tienen este aspecto:

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 método GetViewForHeader y GetViewForFooter en UITableViewSource.