Eventos, protocolos y delegados en Xamarin.iOS

Xamarin.iOS usa controles para exponer eventos para la mayoría de las interacciones del usuario. Las aplicaciones de Xamarin.iOS consumen estos eventos de la misma manera que las aplicaciones .NET tradicionales. Por ejemplo, la clase UIButton de Xamarin.iOS tiene un evento denominado TouchUpButton y consume este evento como si esta clase y evento estuvieran en una aplicación .NET.

Además de este enfoque de .NET, Xamarin.iOS expone otro modelo que se puede usar para una interacción y un enlace de datos más complejos. Esta metodología usa lo que Apple llama delegados y protocolos. Los delegados son similares en concepto a los delegados de C#, pero en lugar de definir y llamar a un único método, un delegado de es una clase completa que se Objective-C ajusta a un protocolo. Un protocolo es similar a una interfaz en C#, salvo que sus métodos pueden ser opcionales. Por ejemplo, para rellenar UITableView con datos, crearía una clase de delegado que implementa los métodos definidos en el protocolo UITableViewDataSource al que uiTableView llamaría para rellenarse.

En este artículo aprenderá todos estos temas, lo que le proporciona una base sólida para controlar escenarios de devolución de llamada en Xamarin.iOS, incluidos:

  • Eventos: uso de eventos de .NET con controles UIKit.
  • Protocolos: Learning qué protocolos son y cómo se usan, y crear un ejemplo que proporciona datos para una anotación de mapa.
  • Delegados: Learning acerca de los delegados ampliando el ejemplo de mapa para controlar la interacción del usuario que incluye una anotación y, a continuación, aprendiendo la diferencia entre delegados fuertes y débiles y cuándo usar cada uno de ellos.

Para ilustrar protocolos y delegados, crearemos una aplicación de mapa simple que agrega una anotación a un mapa como se muestra aquí:

Ejemplo de una aplicación de mapa simple que agrega una anotación aun mapaUna anotación de ejemplo agregada a un mapa

Antes de abordar esta aplicación, veamos los eventos de .NET en UIKit.

Eventos de .NET con UIKit

Xamarin.iOS expone eventos de .NET en controles UIKit. Por ejemplo, UIButton tiene un evento TouchUpButton, que se controla como lo haría normalmente en .NET, como se muestra en el código siguiente que usa una expresión lambda de C#:

aButton.TouchUpInside += (o,s) => {
    Console.WriteLine("button touched");
};

También podría implementar esto con un método anónimo de estilo C# 2.0 como este:

aButton.TouchUpInside += delegate {
    Console.WriteLine ("button touched");
};

El código anterior se cablea en el ViewDidLoad método de UIViewController. La aButton variable hace referencia a un botón, que se puede agregar en el Interface Builder Xcode o con código.

Xamarin.iOS también admite el estilo de acción de destino de conectar el código a una interacción que se produce con un control .

Para obtener más información sobre el patrón de acción de destino de iOS, consulte la sección Target-Action de Competencias principales de aplicaciones para iOS en la biblioteca para desarrolladores de iOS de Apple.

Para obtener más información, consulte Diseño de interfaces de usuario con Xcode.

Events

Si desea interceptar eventos de UIControl, tiene una variedad de opciones: desde el uso de las funciones lambda y delegada de C# hasta el uso de las API de Objective-C bajo nivel.

En la sección siguiente se muestra cómo capturaría el evento TouchDown en un botón, en función del control que necesite.

Estilo de C#

Uso de la sintaxis de delegado:

UIButton button = MakeTheButton ();
button.TouchDown += delegate {
    Console.WriteLine ("Touched");
};

Si en su lugar le gustan las lambdas:

button.TouchDown += () => {
   Console.WriteLine ("Touched");
};

Si desea tener varios botones, use el mismo controlador para compartir el mismo código:

void handler (object sender, EventArgs args)
{
   if (sender == button1)
      Console.WriteLine ("button1");
   else
      Console.WriteLine ("some other button");
}

button1.TouchDown += handler;
button2.TouchDown += handler;

Supervisión de más de un tipo de evento

Los eventos de C# para las marcas UIControlEvent tienen una asignación uno a uno a las marcas individuales. Si desea que el mismo fragmento de código controle dos o más eventos, use el UIControl.AddTarget método :

button.AddTarget (handler, UIControlEvent.TouchDown | UIControlEvent.TouchCancel);

Uso de la sintaxis lambda:

button.AddTarget ((sender, event)=> Console.WriteLine ("An event happened"), UIControlEvent.TouchDown | UIControlEvent.TouchCancel);

Si necesita usar características de bajo nivel de , como enlazar a una instancia de objeto determinada e Objective-C invocar un selector determinado:

[Export ("MySelector")]
void MyObjectiveCHandler ()
{
    Console.WriteLine ("Hello!");
}

// In some other place:

button.AddTarget (this, new Selector ("MySelector"), UIControlEvent.TouchDown);

Tenga en cuenta que si implementa el método de instancia en una clase base heredada, debe ser un método público.

Protocolos

Un protocolo es una Objective-C característica de lenguaje que proporciona una lista de declaraciones de método. Tiene un propósito similar a una interfaz en C#, la principal diferencia es que un protocolo puede tener métodos opcionales. No se llama a métodos opcionales si la clase que adopta un protocolo no los implementa. Además, una sola clase de puede implementar varios protocolos, del mismo modo que una clase Objective-C de C# puede implementar varias interfaces.

Apple usa protocolos en iOS para definir contratos para las clases que se adopten, a la vez que abstrae la clase de implementación del autor de la llamada, funcionando como una interfaz de C#. Los protocolos se usan tanto en escenarios no delegados (como con el ejemplo que se muestra a continuación) como con delegados (como se muestra más adelante en este documento, en la MKAnnotation sección Delegados).

Protocolos con Xamarin.ios

Echemos un vistazo a un ejemplo de uso de un Objective-C protocolo de Xamarin.iOS. En este ejemplo, usaremos el MKAnnotation protocolo , que forma parte del MapKit marco. MKAnnotation es un protocolo que permite que cualquier objeto que lo adopte proporcione información sobre una anotación que se puede agregar a un mapa. Por ejemplo, un objeto que implementa MKAnnotation proporciona la ubicación de la anotación y el título asociado a ella.

De esta manera, el protocolo se usa para proporcionar datos pertinentes MKAnnotation que acompañan a una anotación. La vista real de la propia anotación se basa en los datos del objeto que adopta el MKAnnotation protocolo. Por ejemplo, el texto de la llamada que aparece cuando el usuario pulsa en la anotación (como se muestra en la captura de pantalla siguiente) procede de la propiedad de la clase que implementa Title el protocolo:

Texto de ejemplo de la llamada cuando el usuario pulsa en la anotación

Como se describe en la sección siguiente, Análisis detallado de protocolos,Xamarin.iOS enlaza protocolos a clases abstractas. Para el protocolo, la clase enlazada de C# se denomina para imitar el nombre del protocolo y es una subclase de , la clase MKAnnotationMKAnnotation base raíz para NSObject CocoaTouch. El protocolo requiere que se implementen un establecedor y un establecedor para la coordenada. sin embargo, un título y un subtítulo son opcionales. Por lo tanto, en la clase , la propiedad es abstracta, lo que requiere su implementación y las propiedades y se marcan como virtuales, lo que las hace opcionales, como se muestra MKAnnotationCoordinate a MKAnnotationTitleSubtitleCoordinatecontinuación:

[Register ("MKAnnotation"), Model ]
public abstract class MKAnnotation : NSObject
{
    public abstract CLLocationCoordinate2D Coordinate
    {
        [Export ("coordinate")]
        get;
        [Export ("setCoordinate:")]
        set;
    }

    public virtual string Title
    {
        [Export ("title")]
        get
        {
            throw new ModelNotImplementedException ();
        }
    }

    public virtual string Subtitle
    {
        [Export ("subtitle")]
        get
        {
            throw new ModelNotImplementedException ();
        }
    }
...
}

Cualquier clase puede proporcionar datos de anotación simplemente derivando de , siempre y cuando se implemente al menos MKAnnotationCoordinate la propiedad . Por ejemplo, esta es una clase de ejemplo que toma la coordenada en el constructor y devuelve una cadena para el título:

/// <summary>
/// Annotation class that subclasses MKAnnotation abstract class
/// MKAnnotation is bound by Xamarin.iOS to the MKAnnotation protocol
/// </summary>
public class SampleMapAnnotation : MKAnnotation
{
    string title;

    public SampleMapAnnotation (CLLocationCoordinate2D coordinate)
    {
        Coordinate = coordinate;
        title = "Sample";
    }

    public override CLLocationCoordinate2D Coordinate { get; set; }

    public override string Title {
        get {
            return title;
        }
    }
}

A través del protocolo al que está enlazado, cualquier clase a la que las subclases puedan proporcionar datos pertinentes que usará el mapa cuando cree la vista MKAnnotation de la anotación. Para agregar una anotación a un mapa, simplemente llame al AddAnnotation método de una MKMapView instancia, como se muestra en el código siguiente:

//an arbitrary coordinate used for demonstration here
var sampleCoordinate =
    new CLLocationCoordinate2D (42.3467512, -71.0969456); // Boston

//create an annotation and add it to the map
map.AddAnnotation (new SampleMapAnnotation (sampleCoordinate));

La variable de mapa aquí es una instancia de MKMapView , que es la clase que representa el propio mapa. usará los datos derivados de la instancia de MKMapView para colocar la vista de CoordinateSampleMapAnnotation anotación en el mapa.

El protocolo proporciona un conjunto conocido de funcionalidades en cualquier objeto que lo implemente, sin que el consumidor (el mapa en este caso) necesite conocer los detalles de MKAnnotation implementación. Esto simplifica la adición de una variedad de anotaciones posibles a un mapa.

Análisis en profundidad de protocolos

Dado que las interfaces de C# no admiten métodos opcionales, Xamarin.iOS asigna protocolos a clases abstractas. Por lo tanto, la adopción de un protocolo en se realiza en Xamarin.iOS derivando de la clase abstracta enlazada al protocolo e implementando los Objective-C métodos necesarios. Estos métodos se exponen como métodos abstractos en la clase . Los métodos opcionales del protocolo se enlazarán a métodos virtuales de la clase de C#.

Por ejemplo, esta es una parte del UITableViewDataSource protocolo enlazada en Xamarin.iOS:

public abstract class UITableViewDataSource : NSObject
{
    [Export ("tableView:cellForRowAtIndexPath:")]
    public abstract UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath);
    [Export ("numberOfSectionsInTableView:")]
    public virtual int NumberOfSections (UITableView tableView){...}
...
}

Tenga en cuenta que la clase es abstracta. Xamarin.iOS hace que la clase sea abstracta para admitir métodos opcionales o obligatorios en protocolos. Sin embargo, a diferencia de los protocolos (o interfaces de C#), las clases de Objective-C C# no admiten la herencia múltiple. Esto afecta al diseño del código de C# que usa protocolos y, normalmente, conduce a clases anidadas. Más adelante en este documento, en la sección Delegados, se trata más información sobre este problema.

GetCell(…)es un método abstracto, enlazado al Objective-CGetCell(…), tableView:cellForRowAtIndexPath: , que es un método necesario del UITableViewDataSource protocolo. Selector es el Objective-C término para el nombre del método. Para aplicar el método según sea necesario, Xamarin.iOS lo declara abstracto. El otro método, NumberOfSections(…) , está enlazado a numberOfSectionsInTableview: . Este método es opcional en el protocolo, por lo que Xamarin.iOS lo declara como virtual, lo que hace que sea opcional invalidar en C#.

Xamarin.iOS se encarga de todos los enlaces de iOS. Sin embargo, si alguna vez necesita enlazar un protocolo desde manualmente, puede hacerlo decorando Objective-C una clase con ExportAttribute . Este es el mismo método que usa el propio Xamarin.iOS.

Para obtener más información sobre cómo enlazar Objective-C tipos en Xamarin.iOS, consulte el artículo Binding Objective-C Types .

Sin embargo, todavía no hemos terminado con los protocolos. También se usan en iOS como base para los Objective-C delegados, que es el tema de la sección siguiente.

Delegados

iOS usa delegados para implementar el patrón de delegación, en el que un objeto Objective-C pasa el trabajo a otro. El objeto que hace el trabajo es el delegado del primer objeto. Un objeto indica a su delegado que realice el trabajo enviándole mensajes después de que ocurran ciertas cosas. Enviar un mensaje como este en Objective-C es funcionalmente equivalente a llamar a un método en C#. Un delegado implementa métodos en respuesta a estas llamadas, por lo que proporciona funcionalidad a la aplicación.

Los delegados permiten ampliar el comportamiento de las clases sin necesidad de crear subclases. Las aplicaciones de iOS suelen usar delegados cuando una clase vuelve a llamar a otra después de que se produzca una acción importante. Por ejemplo, la clase vuelve a llamar a su delegado cuando el usuario pulsa una anotación en un mapa, lo que da al autor de la clase de delegado la oportunidad de responder MKMapView dentro de la aplicación. Puede trabajar con un ejemplo de este tipo de uso de delegado más adelante en este artículo, en Ejemplo de uso de un delegado con Xamarin.iOS.

En este punto, es posible que se pregunte cómo una clase determina a qué métodos llamar en su delegado. Este es otro lugar donde se usan protocolos. Normalmente, los métodos disponibles para un delegado proceden de los protocolos que adoptan.

Cómo se usan los protocolos con delegados

Anteriormente vimos cómo se usan los protocolos para admitir la adición de anotaciones a un mapa. Los protocolos también se usan para proporcionar un conjunto conocido de métodos para que las clases llamen después de que se produzcan determinados eventos, como después de que el usuario pulse una anotación en un mapa o seleccione una celda de una tabla. Las clases que implementan estos métodos se conocen como delegados de las clases que los llaman.

Las clases que admiten la delegación lo hacen mediante la exposición de una propiedad Delegate, a la que se asigna una clase que implementa el delegado. Los métodos que implemente para el delegado dependerán del protocolo que adopte el delegado determinado. Para el método , implemente el protocolo , para el método , implementaría , y así sucesivamente para cualquier otra clase en iOS para la que quiera exponer UITableViewUITableViewDelegate un UIAccelerometerUIAccelerometerDelegate delegado.

La clase que vimos en el ejemplo anterior también tiene una propiedad denominada Delegate, a la que llamará MKMapView después de que se produzcan varios eventos. El delegado de MKMapView es de tipo MKMapViewDelegate . Lo usará en breve en un ejemplo para responder a la anotación después de seleccionarla, pero primero vamos a analizar la diferencia entre los delegados fuertes y débiles.

Delegados fuertes frente a delegados débiles

Los delegados que hemos visto hasta ahora son delegados fuertes, lo que significa que están fuertemente tipos. Los enlaces de Xamarin.iOS se envían con una clase fuertemente con tipo para cada protocolo delegado de iOS. Sin embargo, iOS también tiene el concepto de delegado débil. En lugar de crear subclases de una clase enlazada al protocolo para un delegado determinado, iOS también le permite enlazar los métodos de protocolo usted mismo en cualquier clase que quiera que derive de NSObject, decorando los métodos con ExportAttribute y, a continuación, suministrando los Objective-C selectores adecuados. Cuando se toma este enfoque, se asigna una instancia de la clase a la propiedad WeakDelegate en lugar de a la propiedad Delegate. Un delegado débil ofrece la flexibilidad de quitar la clase de delegado de una jerarquía de herencia diferente. Echemos un vistazo a un ejemplo de Xamarin.iOS que usa delegados fuertes y débiles.

Ejemplo de uso de un delegado con Xamarin.iOS

Para ejecutar código en respuesta al usuario que pulsa la anotación en nuestro ejemplo, podemos crear subclases y asignar una instancia MKMapViewDelegate a la propiedad de MKMapViewDelegate . El MKMapViewDelegate protocolo solo contiene métodos opcionales. Por lo tanto, todos los métodos son virtuales que están enlazados a este protocolo en la clase MKMapViewDelegate Xamarin.iOS. Cuando el usuario selecciona una anotación, MKMapView la instancia enviará el mensaje a su mapView:didSelectAnnotationView: delegado. Para controlar esto en Xamarin.iOS, es necesario invalidar el método en la DidSelectAnnotationView (MKMapView mapView, MKAnnotationView annotationView) subclase MKMapViewDelegate de la siguiente forma:

public class SampleMapDelegate : MKMapViewDelegate
{
    public override void DidSelectAnnotationView (
        MKMapView mapView, MKAnnotationView annotationView)
    {
        var sampleAnnotation =
            annotationView.Annotation as SampleMapAnnotation;

        if (sampleAnnotation != null) {

            //demo accessing the coordinate of the selected annotation to
            //zoom in on it
            mapView.Region = MKCoordinateRegion.FromDistance(
                sampleAnnotation.Coordinate, 500, 500);

            //demo accessing the title of the selected annotation
            Console.WriteLine ("{0} was tapped", sampleAnnotation.Title);
        }
    }
}

La clase SampleMapDelegate mostrada anteriormente se implementa como una clase anidada en el controlador que contiene la MKMapView instancia de . En Objective-C , a menudo verá que el controlador adopta varios protocolos directamente dentro de la clase . Sin embargo, dado que los protocolos están enlazados a clases en Xamarin.iOS, las clases que implementan delegados fuertemente con tipo normalmente se incluyen como clases anidadas.

Con la implementación de la clase de delegado en su lugar, solo tiene que crear una instancia del delegado en el controlador y asignarla a la propiedad de , como se muestra MKMapViewDelegate aquí:

public partial class Protocols_Delegates_EventsViewController : UIViewController
{
    SampleMapDelegate _mapDelegate;
    ...
    public override void ViewDidLoad ()
    {
        base.ViewDidLoad ();

        //set the map's delegate
        _mapDelegate = new SampleMapDelegate ();
        map.Delegate = _mapDelegate;
        ...
    }
    class SampleMapDelegate : MKMapViewDelegate
    {
        ...
    }
}

Para usar un delegado débil para realizar lo mismo, debe enlazar el método usted mismo en cualquier clase que derive de y asignarlo a la propiedad NSObjectWeakDelegate de MKMapView . Puesto que la clase deriva en última instancia de (como todas las clases de CocoaTouch), simplemente podemos implementar un método enlazado a directamente en el controlador y asignar el controlador a , evitando la necesidad de la clase UIViewControllerNSObject anidada Objective-CmapView:didSelectAnnotationView:MKMapViewWeakDelegate adicional. El código siguiente muestra este enfoque:

public partial class Protocols_Delegates_EventsViewController : UIViewController
{
    ...
    public override void ViewDidLoad ()
    {
        base.ViewDidLoad ();
        //assign the controller directly to the weak delegate
        map.WeakDelegate = this;
    }
    //bind to the Objective-C selector mapView:didSelectAnnotationView:
    [Export("mapView:didSelectAnnotationView:")]
    public void DidSelectAnnotationView (MKMapView mapView,
        MKAnnotationView annotationView)
    {
        ...
    }
}

Al ejecutar este código, la aplicación se comporta exactamente igual que cuando se ejecuta la versión de delegado fuertemente con tipo. La ventaja de este código es que el delegado débil no requiere la creación de la clase adicional que se creó cuando se usó el delegado fuertemente con tipo. Sin embargo, esto se produce a costa de la seguridad de tipos. Si cometía un error en el selector que se pasó a , no lo averiguaría hasta ExportAttribute el tiempo de ejecución.

Eventos y delegados

Los delegados se usan para las devoluciones de llamada en iOS de forma similar a la forma en que .NET usa eventos. Para que las API de iOS y la forma en que usan delegados parezcan más similares a .NET, Xamarin.iOS expone eventos .NET en muchos lugares donde se usan delegados en Objective-C iOS.

Por ejemplo, la implementación anterior en la que respondió a una anotación seleccionada también se podría implementar en MKMapViewDelegate Xamarin.iOS mediante un evento de .NET. En ese caso, el evento se definiría en MKMapView y se llamaría DidSelectAnnotationView . Tendría una EventArgs subclase de tipo MKMapViewAnnotationEventsArgs . La propiedad de le proporcionaría una referencia a la vista de anotación, desde la que podría continuar con la misma implementación que tenía anteriormente, como se muestra ViewMKMapViewAnnotationEventsArgs aquí:

map.DidSelectAnnotationView += (s,e) => {
    var sampleAnnotation = e.View.Annotation as SampleMapAnnotation;
    if (sampleAnnotation != null) {
        //demo accessing the coordinate of the selected annotation to
        //zoom in on it
        mapView.Region = MKCoordinateRegion.FromDistance (
            sampleAnnotation.Coordinate, 500, 500);

        //demo accessing the title of the selected annotation
        Console.WriteLine ("{0} was tapped", sampleAnnotation.Title);
    }
};

Resumen

En este artículo se ha abordado cómo usar eventos, protocolos y delegados en Xamarin.iOS. Vimos cómo Xamarin.iOS expone eventos de estilo normal de .NET para controles. A continuación, hemos aprendido sobre los protocolos, incluido cómo son diferentes de las interfaces de C# y Objective-C cómo los usa Xamarin.iOS. Por último, hemos examinado Objective-C los delegados desde una perspectiva de Xamarin.iOS. Hemos visto cómo Xamarin.iOS admite delegados fuerte y débilmente con tipos, y cómo enlazar eventos de .NET a métodos delegados.