Enlace de datos y codificación de clave-valor en Xamarin.Mac

En este artículo, se describe el uso de la codificación de clave-valor y la observación de clave-valor para permitir el enlace de datos a los elementos de la interfaz de usuario en Interface Builder de Xcode.

Información general

Al trabajar con C# y .NET en una aplicación de Xamarin.Mac, tendrá acceso a las mismas técnicas de codificación clave-valor y de enlace de datos que un desarrollador que trabaje en Objective-C y Xcode. Como Xamarin.Mac se integra directamente con Xcode, puede usar Interface Builder de Xcode para enlazar datos con elementos de la interfaz de usuario en lugar de escribir código.

Mediante el uso de técnicas de codificación de clave-valor y de enlace de datos en la aplicación de Xamarin.Mac, puede reducir considerablemente la cantidad de código que tiene que escribir y mantener para rellenar y trabajar con elementos de la interfaz de usuario. También tiene la ventaja de poder desacoplar aún más los datos de respaldo (Modelo de datos) de la interfaz de usuario de front-end (Modelo-Vista-Controlador), lo que facilita el mantenimiento y hace que el diseño de las aplicaciones sea más flexible.

Un ejemplo de la aplicación en ejecución

En este artículo, abordaremos los conceptos básicos sobre el trabajo con codificación de clave-valor y enlace de datos en una aplicación de Xamarin.Mac. Se recomienda encarecidamente primero revisar el artículo Hello, Mac; específicamente las secciones Introducción a Xcode e Interface Builder y Salidas y acciones, ya que se abordan conceptos clave y técnicas que usaremos en este artículo.

Es posible que también deba consultar la sección Exponer clases o métodos de C# a Objective-C del documento Xamarin.Mac Internals, ya que explica los atributos Register y Export que se usan para conectar las clases de C# a objetos y elementos de la interfaz de usuario de Objective-C.

¿Qué es la codificación de clave-valor?

La codificación de clave-valor (KVC) es un mecanismo para acceder indirectamente a las propiedades de un objeto mediante claves (cadenas con formato especial) para identificar propiedades en lugar de acceder a ellas a través de variables de instancia o métodos de descriptor de acceso (get/set). Al implementar descriptores de acceso compatibles con la codificación de clave-valor en la aplicación de Xamarin.Mac, obtendrá acceso a otras características de macOS (antes conocido como OS X), como la observación de clave-valor (KVO), el enlace de datos, Core Data, enlaces de Cocoa y la capacidad de ejecución mediante scripts.

Mediante el uso de técnicas de codificación de clave-valor y de enlace de datos en la aplicación de Xamarin.Mac, puede reducir considerablemente la cantidad de código que tiene que escribir y mantener para rellenar y trabajar con elementos de la interfaz de usuario. También tiene la ventaja de poder desacoplar aún más los datos de respaldo (Modelo de datos) de la interfaz de usuario de front-end (Modelo-Vista-Controlador), lo que facilita el mantenimiento y hace que el diseño de las aplicaciones sea más flexible.

Por ejemplo, echemos un vistazo a la siguiente definición de clase de un objeto compatible con KVC:

using System;
using Foundation;

namespace MacDatabinding
{
    [Register("PersonModel")]
    public class PersonModel : NSObject
    {
        private string _name = "";

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

        public PersonModel ()
        {
        }
    }
}

En primer lugar, el atributo [Register("PersonModel")] registra la clase y la expone a Objective-C. Después, la clase necesita heredar de NSObject (o una subclase que herede de NSObject), esto agrega varios métodos base que permiten a la clase ser compatible con KVC. A continuación, el atributo [Export("Name")] expone la propiedad Name y define el valor de la clave que se usará posteriormente para acceder a la propiedad mediante las técnicas KVC y KVO.

Por último, para poder ser clave-valor observado los cambios en el valor de la propiedad, el descriptor de acceso debe encapsular los cambios en su valor en las llamadas a los métodos WillChangeValue y DidChangeValue (especificando la misma clave que el atributo Export). Por ejemplo:

set {
    WillChangeValue ("Name");
    _name = value;
    DidChangeValue ("Name");
}

Este paso es muy importante para enlazar datos en Interface Builder de Xcode (como veremos más adelante en este artículo).

Para más información, consulte la Guía de programación de codificación clave-valor de Apple.

Claves y rutas de acceso de clave

Una clave es una cadena que identifica una propiedad específica de un objeto. Normalmente, una clave corresponde al nombre de un método de descriptor de acceso en un objeto compatible con codificación de clave-valor. Las claves deben usar la codificación ASCII, suelen empezar por una letra minúscula y no pueden contener espacios en blanco. Así, dado el ejemplo anterior, Name sería un valor clave de la propiedad Name de la clase PersonModel. La clave y el nombre de la propiedad que exponen no tienen que ser iguales, pero en la mayoría de los casos son.

Una Ruta de acceso de clave es una cadena de claves separadas por puntos que se usa para especificar una jerarquía de propiedades de objetos que recorrer. La propiedad de la primera clave de la secuencia es relativa al receptor, y cada clave posterior se evalúa en relación con el valor de la propiedad anterior. De la misma forma que se usa la notación de puntos para recorrer un objeto y sus propiedades en una clase C#.

Por ejemplo, si expande la clase PersonModel y agrega la propiedad Child:

using System;
using Foundation;

namespace MacDatabinding
{
    [Register("PersonModel")]
    public class PersonModel : NSObject
    {
        private string _name = "";
        private PersonModel _child = new PersonModel();

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

        [Export("Child")]
        public PersonModel Child {
            get { return _child; }
            set {
                WillChangeValue ("Child");
                _child = value;
                DidChangeValue ("Child");
            }
        }

        public PersonModel ()
        {
        }
    }
}

La ruta de acceso de clave al nombre del elemento secundario sería self.Child.Name o simplemente Child.Name (en función de cómo se estuviera usando el valor de clave).

Obtención de valores mediante codificación de clave-valor

El método ValueForKey devuelve el valor de la clave especificada (como NSString), en relación con la instancia de la clase KVC que recibe la solicitud. Por ejemplo, si Person es una instancia de la clase PersonModel definida anteriormente:

// Read value
var name = Person.ValueForKey (new NSString("Name"));

Esto devolvería el valor de la propiedad Name para esa instancia de PersonModel.

Establecimiento de valores mediante codificación de clave-valor

Del mismo modo, el SetValueForKey establece el valor para la clave especificada (como NSString), relativa a la instancia de la clase KVC que recibe la solicitud. Por lo tanto, de nuevo, con una instancia de la clase PersonModel, como se muestra a continuación:

// Write value
Person.SetValueForKey(new NSString("Jane Doe"), new NSString("Name"));

Cambiaría el valor de la propiedad Name a Jane Doe.

Observación de cambios de valor

Usando la observación clave-valor (KVO), puede adjuntar un observador a una clave específica de una clase compatible con KVC y ser notificado cada vez que se modifique el valor de esa clave (ya sea usando técnicas de KVC o accediendo directamente a la propiedad dada en código C#). Por ejemplo:

// Watch for the name value changing
Person.AddObserver ("Name", NSKeyValueObservingOptions.New, (sender) => {
    // Inform caller of selection change
    Console.WriteLine("New Name: {0}", Person.Name)
});

Ahora, cada vez que se modifica la propiedad Name de la instancia Person de la clase PersonModel, el nuevo valor se escribe en la consola.

Para más información, consulte la Guía de programación de introducción a la observación de clave-valor de Apple.

Enlace de datos

Las siguientes secciones mostrarán cómo puede usar una clase que cumpla con la codificación clave-valor para enlazar datos a los elementos de la interfaz de usuario en Interface Builder de Xcode, en lugar de leer y escribir valores usando código C#. De este modo, separa su Modelo de datos de las vistas que se usan para mostrarlos, lo que hace que la aplicación Xamarin.Mac sea más flexible y fácil de mantener. También se reduce considerablemente la cantidad de código que se debe escribir.

Definición del modelo de datos

Antes de poder enlazar datos de un elemento de interfaz de usuario en Interface Builder, debe tener una clase compatible con KVC/KVO definida en su aplicación Xamarin.Mac para que actúe como Modelo de datos para el enlace. El Modelo de datos proporciona todos los datos que se mostrarán en la Interfaz de usuario y recibe cualquier modificación de los datos que el usuario realice en la interfaz de usuario mientras ejecuta la aplicación.

Por ejemplo, si estuviera escribiendo una aplicación que administrara un grupo de empleados, podría usar la siguiente clase para definir el Modelo de datos:

using System;
using Foundation;
using AppKit;

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

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

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

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

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

        [Export("Icon")]
        public NSImage Icon {
            get {
                if (isManager) {
                    return NSImage.ImageNamed ("group.png");
                } else {
                    return NSImage.ImageNamed ("user.png");
                }
            }
        }

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

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

        #region Constructors
        public PersonModel ()
        {
        }

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

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

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

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

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

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

La mayoría de las características de esta clase se trataron anteriormente en Qué es la codificación clave-valor. Sin embargo, veamos algunos elementos específicos y algunas adiciones que se hicieron para permitir que esta clase actúe como Modelo de datos para Controladores de matriz y Controladores de árbol (que usaremos más adelante para enlazar datos de Vistas de árbol, Vistas Esquema y Vistas de colección).

En primer lugar, dado que un empleado puede ser administrador, hemos usado un NSArray (concretamente un NSMutableArray para que los valores puedan modificarse) para permitir que los empleados que administra estén vinculados a ellos:

private NSMutableArray _people = new NSMutableArray();
...

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

Dos cosas que debe tener en cuenta aquí:

  1. Usamos un NSMutableArray en lugar de una matriz o colección estándar de C#, ya que es un requisito para enlazar datos a controles de AppKit como Vistas de tabla, Vistas Esquema y Colecciones.
  2. Hemos expuesto la matriz de empleados convirtiéndola en NSArray a efectos de enlace de datos y hemos cambiado su nombre con formato C#, People, por uno que espera el enlace de datos, personModelArray de la forma {nombre_clase}Matriz (observe que el primer carácter se ha puesto en minúsculas).

A continuación, tenemos que agregar algunos métodos públicos con nombres especiales para que sean compatibles con Controladores de matriz y Controladores de árbol:

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

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

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

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

Estos permiten a los controladores solicitar y modificar los datos que muestran. Al igual que la NSArray expuesta anteriormente, tienen una convención de nomenclatura muy específica (que difiere de las convenciones de nomenclatura típicas de C#):

  • addObject:: agrega un objeto a la matriz.
  • insertObject:in{class_name}ArrayAtIndex:: donde {class_name} es el nombre de la clase. Este método inserta un objeto en la matriz en un índice determinado.
  • removeObjectFrom{class_name}ArrayAtIndex:: donde {class_name} es el nombre de la clase. Este método quita el objeto de la matriz en un índice determinado.
  • set{class_name}Array:: donde {class_name} es el nombre de la clase. Este método permite reemplazar el transporte existente por uno nuevo.

Dentro de estos métodos, hemos encapsulado los cambios en la matriz en mensajes WillChangeValue y DidChangeValue para el cumplimiento de la KVO.

Por último, dado que la propiedad Icon depende del valor de la propiedad isManager, es posible que los cambios en la propiedad isManager no se reflejen en el Icon de los elementos de la interfaz de usuario vinculados a datos (durante la KVO):

[Export("Icon")]
public NSImage Icon {
    get {
        if (isManager) {
            return NSImage.ImageNamed ("group.png");
        } else {
            return NSImage.ImageNamed ("user.png");
        }
    }
}

Para corregirlo, usamos el código siguiente:

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

Observe que además de su propia Clave, el descriptor de acceso isManager también está enviando los mensajes WillChangeValue y DidChangeValue para la Clave Icon, por lo que también verá el cambio.

Usaremos el Modelo de datos PersonModel durante el resto de este artículo.

Enlace de datos simple

Con nuestro Modelo de datos definido, veamos un ejemplo sencillo de enlace de datos en Interface Builder de Xcode. Por ejemplo, vamos a agregar un formulario a nuestra aplicación de Xamarin.Mac que puede usarse para editar el PersonModel que definimos anteriormente. Agregaremos algunos campos de texto y una casilla para mostrar y editar las propiedades del modelo.

En primer lugar, vamos a agregar un nuevo Controlador de vista a nuestro archivo Main.storyboard en Interface Builder y a nombrar su clase SimpleViewController:

Adición de un nuevo controlador de vista con una clase denominada SimpleViewController.

A continuación, vuelva a Visual Studio para Mac, edite el archivo SimpleViewController.cs (que se añadió automáticamente a nuestro proyecto) y exponga una instancia del PersonModel al que enlazaremos los datos de nuestro formulario. Agregue el siguiente código:

private PersonModel _person = new PersonModel();
...

[Export("Person")]
public PersonModel Person {
    get {return _person; }
    set {
        WillChangeValue ("Person");
        _person = value;
        DidChangeValue ("Person");
    }
}

A continuación, cuando se cargue la Vista, vamos a crear una instancia de nuestro PersonModel y a rellenarla con este código:

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

    // Set a default person
    var Craig = new PersonModel ("Craig Dunn", "Documentation Manager");
    Craig.AddPerson (new PersonModel ("Amy Burns", "Technical Writer"));
    Craig.AddPerson (new PersonModel ("Joel Martinez", "Web & Infrastructure"));
    Craig.AddPerson (new PersonModel ("Kevin Mullins", "Technical Writer"));
    Craig.AddPerson (new PersonModel ("Mark McLemore", "Technical Writer"));
    Craig.AddPerson (new PersonModel ("Tom Opgenorth", "Technical Writer"));
    Person = Craig;

}

Ahora tenemos que crear nuestro formulario, haga doble clic en el archivo Main.storyboard para abrirlo y editarlo en Interface Builder. Diseñe el formulario de forma que se parezca a lo siguiente:

Edición del guión gráfico en Xcode

Para enlazar los datos del formulario al PersonModel que expusimos mediante la clave Person, haga lo siguiente:

  1. Seleccione el campo de texto Nombre del empleado y cambie al Inspector de enlaces.

  2. Marque la casilla Enlazar con y seleccione Controlador de vista simple en la lista desplegable. A continuación, escriba self.Person.Name para la Ruta de acceso:

    Escriba self.person.name para la ruta de acceso de la clave.

  3. Seleccione el campo de texto Ocupación y marque la casilla Enlazar a y seleccione Controlador de vista simple en la lista desplegable. A continuación, escriba self.Person.Occupation para la Ruta de acceso de clave:

    Escriba self.Person.Occupation para la ruta de acceso de la clave.

  4. Seleccione la casilla El empleado es un administrador, marque la casilla Enlazar a y seleccione Controlador de vista simple en la lista desplegable. A continuación, escriba self.Person.isManager para la Ruta de acceso de clave:

    Escriba self.Person.isManager para la ruta de acceso de la clave.

  5. Seleccione el campo de texto Número de empleados administrados, marque la casilla Enlazar a y seleccione Controlador de vista simple en la lista desplegable. A continuación, escriba self.Person.NumberOfEmployees para la Ruta de acceso de clave:

    Escriba self.Person.NumberOfEmployees para la ruta de acceso de la clave.

  6. Si el empleado no es administrador, queremos ocultar la etiqueta Número de empleados administrados y el campo de texto.

  7. Seleccione la etiqueta Número de empleados administrados, expanda la lista desplegable Ocultos y marque la casilla Enlazar con y seleccione Controlador de vista simple en el menú desplegable. A continuación, escriba self.Person.isManager para la Ruta de acceso de clave:

    Escriba self.Person.isManager para la ruta de acceso de la clave para los que no son administradores.

  8. Seleccione NSNegateBoolean en la lista desplegable Transformador de valores:

    Selección de la transformación de la clave NSNegateBoolean

  9. Esto indica al enlace de datos que la etiqueta se ocultará si el valor de la propiedad isManager es false.

  10. Repita los pasos 7 y 8 para el campo de texto Número de empleados administrados.

  11. Guarde los cambios y vuelva a Visual Studio para Mac para sincronizarlo con Xcode.

Si ejecuta la aplicación, los valores de la propiedad Person rellenarán automáticamente nuestro formulario:

Mostrar un formulario rellenado automáticamente

Cualquier cambio que el usuario realice en el formulario se volverá a escribir en la propiedad Person del Controlador de vista. Por ejemplo, al desmarcar El empleado es administrador se actualiza la instancia Person de nuestro PersonModel y la etiquetaNúmero de empleados administrados y el campo de texto se ocultan automáticamente (mediante el enlace de datos):

Ocultar el número de empleados para no administradores

Enlace de datos de vista de tabla

Ahora que ya conocemos los conceptos básicos del enlace de datos, veamos una tarea de enlace de datos más compleja usando un Controlador de matriz y enlazando datos a una vista de tabla. Para obtener más información sobre cómo trabajar con vistas de tabla, consulte nuestra documentación de Vistas de tabla.

En primer lugar, agreguemos un nuevo Controlador de vista a nuestro archivo Main.storyboard en Interface Builder y nombremos su clase TableViewController:

Adición de un nuevo controlador de vista con una clase denominada TableViewController.

A continuación, editemos el archivo TableViewController.cs (que se añadió automáticamente a nuestro proyecto) y expongamos una matriz (NSArray) de clases PersonModel a las que enlazaremos los datos de nuestro formulario. Agregue el siguiente código:

private NSMutableArray _people = new NSMutableArray();
...

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

[Export("addObject:")]
public void AddPerson(PersonModel person) {
    WillChangeValue ("personModelArray");
    _people.Add (person);
    DidChangeValue ("personModelArray");
}

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

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

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

Tal y como hicimos en la clase PersonModel anteriormente en la sección Definición de su Modelo de datos, hemos expuesto cuatro métodos públicos con nombres especiales para que el Controlador de matriz lea y escriba datos de nuestra colección de PersonModels.

A continuación, cuando se carga la vista, es necesario rellenar la matriz con este código:

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

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

}

Ahora tenemos que crear nuestra vista de tabla, haga doble clic en el archivo Main.storyboard para abrirlo y editarlo en Interface Builder. Diseñe la tabla de forma que se parezca a lo siguiente:

Diseño de una nueva vista de tabla

Necesitamos agregar un Controlador de matriz para proporcionar datos enlazados a nuestra tabla, haga lo siguiente:

  1. Arrastre un Controlador de matriz del Inspector de biblioteca al Editor de interfaz:

    Selección de un controlador de matriz en la biblioteca

  2. Seleccione Controlador de matriz en la Jerarquía de interfaces y cambie al Inspector de atributos:

    Selección del inspector de atributos

  3. Escriba PersonModel para el Nombre de la clase, haga clic en el botón Más y agregue tres claves. Nómbrelas Name, Occupation y isManager:

    Agregar las rutas de acceso de la clave necesarias al controlador de objetos.

  4. Esto indica al Controlador de matriz de qué está administrando una matriz y qué propiedades debe exponer (mediante claves).

  5. Cambie al Inspector de enlaces y en Matriz de contenido seleccione Enlazar con y Controlador de vista de tabla. Escriba una Ruta de acceso de modelo de self.personModelArray:

    Escritura de la ruta de acceso de la clave

  6. Esto vincula el Controlador de matriz a la matriz de PersonModels que expusimos en nuestro Controlador de vista.

Ahora tenemos que enlazar nuestra vista de tabla con el Controlador de matriz, haga lo siguiente:

  1. Seleccione la Vista de tabla y el Inspector de enlaces:

    Seleccione la vista de tabla y el inspector de enlace.

  2. En el menú desplegable Contenido de la tabla, seleccione Enlazar con y Controlador de matriz. Escriba arrangedObjects para el campo Clave del controlador:

    Definición de la clave del controlador

  3. Seleccione la Celda de vista de tabla bajo la columna Empleado. En el Inspector de enlaces, bajo el desplegable Valor, seleccione Enlazar a y Vista de celdas de tabla. Escriba objectValue.Name para la Ruta de acceso de la clave de modelo:

    Establezca la ruta de acceso de la clave del modelo para la columna Empleado.

  4. objectValue es el PersonModel actual en la matriz que está siendo administrada por el Controlador de matriz.

  5. Seleccione la Celda de vista de tabla bajo la columna Ocupación. En el Inspector de enlaces, bajo el desplegable Valor, seleccione Enlazar a y Vista de celdas de tabla. Escriba objectValue.Occupation para la Ruta de acceso de la clave de modelo:

    Establezca la ruta de acceso de la clave del modelo para la columna Ocupación.

  6. Guarde los cambios y vuelva a Visual Studio para Mac para sincronizarlo con Xcode.

Si ejecutamos la aplicación, la tabla se rellenará con nuestra matriz de PersonModels:

Ejecute la aplicación, que rellena la matriz de PersonModels.

Enlace de datos de vista Esquema

El enlace de datos con una Vista Esquema es muy similar al enlace con una Vista de tabla. La diferencia clave es que usaremos un Controlador de árbol en lugar de un Controlador de matriz para proporcionar los datos enlazados a la vista Esquema. Para obtener más información sobre cómo trabajar con vistas de esquema, consulte nuestra documentación de Vistas de esquema.

En primer lugar, agreguemos un nuevo Controlador de vista a nuestro archivo Main.storyboard en Interface Builder y nombremos su clase OutlineViewController:

Adición de un nuevo controlador de vista con una clase denominada OutlineViewController.

A continuación, editemos el archivo OutlineViewController.cs (que se añadió automáticamente a nuestro proyecto) y expongamos una matriz (NSArray) de clases PersonModel a las que enlazaremos los datos de nuestro formulario. Agregue el siguiente código:

private NSMutableArray _people = new NSMutableArray();
...

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

[Export("addObject:")]
public void AddPerson(PersonModel person) {
    WillChangeValue ("personModelArray");
    _people.Add (person);
    DidChangeValue ("personModelArray");
}

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

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

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

Tal y como hicimos en la clase PersonModel anteriormente en la sección Definición de su Modelo de datos, hemos expuesto cuatro métodos públicos con nombres especiales para que el Controlador de árbol lea y escriba datos de nuestra colección de PersonModels.

A continuación, cuando se carga la vista, es necesario rellenar la matriz con este código:

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

    // Build list of employees
    var Craig = new PersonModel ("Craig Dunn", "Documentation Manager");
    Craig.AddPerson (new PersonModel ("Amy Burns", "Technical Writer"));
    Craig.AddPerson (new PersonModel ("Joel Martinez", "Web & Infrastructure"));
    Craig.AddPerson (new PersonModel ("Kevin Mullins", "Technical Writer"));
    Craig.AddPerson (new PersonModel ("Mark McLemore", "Technical Writer"));
    Craig.AddPerson (new PersonModel ("Tom Opgenorth", "Technical Writer"));
    AddPerson (Craig);

    var Larry = new PersonModel ("Larry O'Brien", "API Documentation Manager");
    Larry.AddPerson (new PersonModel ("Mike Norman", "API Documenter"));
    AddPerson (Larry);

}

Ahora tenemos que crear nuestra vista Esquema, haga doble clic en el archivo Main.storyboard para abrirlo y editarlo en Interface Builder. Diseñe la tabla de forma que se parezca a lo siguiente:

Creación de la vista de esquema

Necesitamos agregar un Controlador de árbol para proporcionar datos enlazados a nuestro esquema, haga lo siguiente:

  1. Arrastre un Controlador de árbol desde el Inspector de biblioteca al Editor de interfaz:

    Selección de un controlador de árbol en la biblioteca

  2. Seleccione Controlador de árbol en la Jerarquía de interfaces y cambie al Inspector de atributos:

    Selección del inspector de atributos

  3. Escriba PersonModel para el Nombre de la clase, haga clic en el botón Más y agregue tres claves. Nómbrelas Name, Occupation y isManager:

    Agregue las rutas de acceso de la clave necesarias para PersonModel.

  4. Esto indica al Controlador de árbol de qué está administrando una matriz y qué propiedades debe exponer (mediante claves).

  5. En la sección Controlador de árbol, escriba personModelArray para Elementos secundarios, escriba NumberOfEmployees en Recuento y escriba isEmployee en Hoja:

    Establecimiento de las rutas de acceso de la clave del controlador de árbol

  6. Indica al Controlador del árbol dónde encontrar los nodos secundarios, cuántos hay y si el nodo actual tiene nodos secundarios.

  7. Cambie al Inspector de enlaces y en Matriz de contenido seleccione Enlazar con y Propietario del archivo. Escriba una Ruta de acceso de modelo de self.personModelArray:

    Edición de la ruta de acceso de la clave

  8. Esto vincula el Controlador de árbol a la matriz de PersonModels que expusimos en nuestro Controlador de vista.

Ahora tenemos que enlazar nuestra vista Esquema con el Controlador de árbol, haga lo siguiente:

  1. Seleccione la vista Esquema y en el Inspector de enlaces seleccione :

    Seleccione la vista esquema y el inspector de enlace.

  2. Bajo el menú desplegable Contenido de vista Esquema, seleccione Enlazar a y Controlador de árbol. Escriba arrangedObjects para el campo Clave del controlador:

    Establecimiento de la clave del controlador

  3. Seleccione la Celda de vista de tabla bajo la columna Empleado. En el Inspector de enlaces, bajo el desplegable Valor, seleccione Enlazar a y Vista de celdas de tabla. Escriba objectValue.Name para la Ruta de acceso de la clave de modelo:

    Escriba el valor de ruta de acceso de la clave del modelo objectValue.Name.

  4. objectValue es el PersonModel actual en la matriz que está siendo administrada por el Controlador de árbol.

  5. Seleccione la Celda de vista de tabla bajo la columna Ocupación. En el Inspector de enlaces, bajo el desplegable Valor, seleccione Enlazar a y Vista de celdas de tabla. Escriba objectValue.Occupation para la Ruta de acceso de la clave de modelo:

    Escriba el valor de ruta de acceso de la clave del modelo objectValue.Occupation.

  6. Guarde los cambios y vuelva a Visual Studio para Mac para sincronizarlo con Xcode.

Si ejecutamos la aplicación, el esquema se rellenará con nuestra matriz de PersonModels:

Ejecute la aplicación, que rellena nuestra matriz de PersonModels.

Enlace de datos de la vista de colección

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

Importante

Debido a un problema en Xcode 7 y macOS 10.11 (y superiores), las vistas de colección no pueden usarse dentro de un guion gráfico (.storyboard). Como resultado, tendrá que seguir usando archivos .xib para definir las vistas de colección de sus aplicaciones de Xamarin.Mac. Consulte nuestra documentación de Vistas de colección para obtener más información.

Depuración de bloqueos nativos

Cometer un error en sus enlaces de datos puede provocar un bloqueo nativo en código no administrado y hacer que su aplicación de Xamarin.Mac falle por completo con un error SIGABRT:

Ejemplo de un cuadro de diálogo de bloqueo nativo

Suele haber cuatro causas principales de bloqueos nativos durante el enlace de datos:

  1. Su Modelo de datos no hereda de NSObject ni es una subclase de NSObject.
  2. No ha expuesto su propiedad a Objective-C usando el atributo [Export("key-name")].
  3. No ha encapsulado los cambios en el valor del descriptor de acceso en las llamadas a los métodos WillChangeValue y DidChangeValue (especificando la misma clave que el atributo Export).
  4. Tiene una clave incorrecta o mal escrita en el Inspector de enlaces en Interface Builder.

Descodificación de un bloqueo

Provoquemos un bloqueo nativo en nuestro enlace de datos para poder mostrar cómo localizarlo y solucionarlo. En Interface Builder, vamos a cambiar nuestro enlace de la primera etiqueta en el ejemplo de la vista de colección de Name a Title:

Edición de la clave de enlace

Guardemos el cambio, volvamos a Visual Studio para Mac para sincronizarlo con Xcode y ejecutemos nuestra aplicación. Cuando se muestre la vista de colección, la aplicación se bloqueará momentáneamente con un error SIGABRT (como se muestra en la Salida de la aplicación en Visual Studio para Mac) ya que el PersonModel no expone una propiedad con la clave Title:

Ejemplo de un error de enlace

Si nos desplazamos hasta la parte superior del error en Salida de la aplicación podremos ver la clave para resolver este problema:

Búsqueda del problema en el registro de errores

Esta línea nos está diciendo que la clave Title no existe en el objeto al que estamos enlazando. Si volvemos a cambiar el enlace en Name en Interface Builder, guardamos, sincronizamos, recompilamos y ejecutamos, la aplicación se ejecutará como se esperaba sin ningún problema.

Resumen

En este artículo, se revisó en detalle el trabajo con el enlace de datos y la codificación de clave-valor en una aplicación de Xamarin.Mac. Primero, se ha examinado la exposición de una clase de C# a Objective-C mediante la codificación de clave-valor (KVC) y la observación de clave-valor (KVO). Luego, se ha mostrado cómo usar una clase compatible con KVO y enlazar datos a elementos de interfaz de usuario en Interface Builder de Xcode. Por último, ha mostrado el enlace de datos complejos usando Controladores de matriz y Controladores de árbol.