Événements, protocoles et délégués dans Xamarin.iOS

Xamarin.iOS utilise des contrôles pour exposer des événements pour la plupart des interactions utilisateur. Les applications Xamarin.iOS consomment ces événements de la même manière que les applications .NET traditionnelles. Par exemple, la classe UIButton Xamarin.iOS a un événement appelé TouchUpInside et consomme cet événement comme si cette classe et cet événement se trouvaient dans une application .NET.

Outre cette approche .NET, Xamarin.iOS expose un autre modèle qui peut être utilisé pour une interaction et une liaison de données plus complexes. Cette méthodologie utilise ce qu’Apple appelle des délégués et des protocoles. Le concept des délégués est similaire à celui des délégués en C#, mais au lieu de définir et d’appeler une seule méthode, un délégué dans Objective-C est une classe entière conforme à un protocole. Un protocole est similaire à une interface en C#, sauf que ses méthodes peuvent être facultatives. Par exemple, pour remplir un UITableView avec des données, vous devez créer une classe déléguée qui implémente les méthodes définies dans le protocole UITableViewDataSource que l’UITableView appellerait pour se remplir.

Dans cet article, vous allez découvrir toutes ces rubriques, ce qui vous donne une base solide pour la gestion des scénarios de rappel dans Xamarin.iOS, notamment :

  • Événements : utilisation d’événements .NET avec des contrôles UIKit.
  • Protocoles : apprentissage des protocoles et de leur utilisation, et création d’un exemple qui fournit des données pour une annotation de carte.
  • Délégués : apprenez-en plus sur Objective-C les délégués en étendant l’exemple de carte pour gérer l’interaction utilisateur qui inclut une annotation, puis en apprenant la différence entre les délégués forts et faibles et quand utiliser chacun d’eux.

Pour illustrer les protocoles et les délégués, nous allons créer une application de carte simple qui ajoute une annotation à une carte, comme illustré ici :

Exemple d’application de carte simple qui ajoute une annotation à une carteExemple d’annotation ajoutée à une carte

Avant d’aborder cette application, commençons par examiner les événements .NET sous UIKit.

Événements .NET avec UIKit

Xamarin.iOS expose les événements .NET sur les contrôles UIKit. Par exemple, UIButton a un événement TouchUpInside, que vous gérez comme vous le feriez normalement dans .NET, comme indiqué dans le code suivant qui utilise une expression lambda C# :

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

Vous pouvez également implémenter ceci avec une méthode anonyme de style C# 2.0 comme celle-ci :

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

Le code précédent est câblé dans la ViewDidLoad méthode du UIViewController. La aButton variable fait référence à un bouton que vous pouvez ajouter dans le Générateur d’interface Xcode ou avec du code.

Xamarin.iOS prend également en charge le style d’action cible de connexion de votre code à une interaction qui se produit avec un contrôle.

Pour plus d’informations sur le modèle d’action cible iOS, consultez la section Target-Action des compétences principales de l’application pour iOS dans la bibliothèque de développeurs iOS d’Apple.

Pour plus d’informations, consultez Conception d’interfaces utilisateur avec Xcode.

Événements

Si vous souhaitez intercepter des événements à partir d’UIControl, vous disposez d’une gamme d’options : de l’utilisation des lambdas C# et des fonctions de délégué à l’utilisation des API de bas niveau Objective-C .

La section suivante montre comment capturer l’événement TouchDown sur un bouton, en fonction de la quantité de contrôle dont vous avez besoin.

C# Style

À l’aide de la syntaxe de délégué :

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

Si vous aimez les lambdas à la place :

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

Si vous souhaitez avoir plusieurs boutons, utilisez le même gestionnaire pour partager le même code :

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

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

Surveillance de plusieurs types d’événements

Les événements C# pour les indicateurs UIControlEvent ont un mappage un-à-un à des indicateurs individuels. Lorsque vous souhaitez que le même morceau de code gère deux événements ou plus, utilisez la UIControl.AddTarget méthode :

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

À l’aide de la syntaxe lambda :

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

Si vous devez utiliser des fonctionnalités de bas niveau de Objective-C, comme le raccordement à un objet particulier instance et l’appel d’un sélecteur particulier :

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

// In some other place:

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

Notez que si vous implémentez la méthode instance dans une classe de base héritée, il doit s’agir d’une méthode publique.

Protocoles

Un protocole est une Objective-C fonctionnalité de langage qui fournit une liste de déclarations de méthode. Il sert un objectif similaire à une interface en C#, la main différence étant qu’un protocole peut avoir des méthodes facultatives. Les méthodes facultatives ne sont pas appelées si la classe qui adopte un protocole ne les implémente pas. En outre, une seule classe dans Objective-C peut implémenter plusieurs protocoles, tout comme une classe C# peut implémenter plusieurs interfaces.

Apple utilise des protocoles dans iOS pour définir des contrats pour les classes à adopter, tout en éloignant la classe d’implémentation de l’appelant, fonctionnant ainsi comme une interface C#. Les protocoles sont utilisés à la fois dans les scénarios non délégués (comme dans l’exemple MKAnnotation ci-dessous) et avec les délégués (comme présenté plus loin dans ce document, dans la section Délégués).

Protocoles avec Xamarin.ios

Examinons un exemple utilisant un Objective-C protocole de Xamarin.iOS. Pour cet exemple, nous allons utiliser le MKAnnotation protocole, qui fait partie de l’infrastructure MapKit . MKAnnotation est un protocole qui permet à tout objet qui l’adopte de fournir des informations sur une annotation qui peut être ajoutée à une carte. Par exemple, une implémentation MKAnnotation d’objet fournit l’emplacement de l’annotation et le titre qui lui est associé.

De cette façon, le MKAnnotation protocole est utilisé pour fournir des données pertinentes qui accompagnent une annotation. La vue réelle de l’annotation elle-même est générée à partir des données de l’objet qui adopte le MKAnnotation protocole. Par exemple, le texte de la légende qui s’affiche lorsque l’utilisateur appuie sur l’annotation (comme illustré dans la capture d’écran ci-dessous) provient de la Title propriété de la classe qui implémente le protocole :

Exemple de texte pour la légende lorsque l’utilisateur appuie sur l’annotation

Comme décrit dans la section suivante, Protocoles en profondeur, Xamarin.iOS lie les protocoles à des classes abstraites. Pour le MKAnnotation protocole, la classe C# liée est nommée MKAnnotation pour imiter le nom du protocole, et il s’agit d’une sous-classe de NSObject, la classe de base racine pour CocoaTouch. Le protocole nécessite l’implémentation d’un getter et d’un setter pour la coordonnée ; toutefois, un titre et un sous-titre sont facultatifs. Par conséquent, dans la MKAnnotation classe, la Coordinate propriété est abstraite, ce qui nécessite son implémentation et les Title propriétés et Subtitle sont marquées virtuelles, ce qui les rend facultatives, comme indiqué ci-dessous :

[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 ();
        }
    }
...
}

N’importe quelle classe peut fournir des données d’annotation en dérivant simplement de MKAnnotation, tant qu’au moins la Coordinate propriété est implémentée. Par exemple, voici un exemple de classe qui prend la coordonnée dans le constructeur et retourne une chaîne pour le titre :

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

Par le biais du protocole auquel elle est liée, toute classe que les MKAnnotation sous-classes peuvent fournir des données pertinentes qui seront utilisées par la carte lorsqu’elle créera la vue de l’annotation. Pour ajouter une annotation à une carte, appelez simplement la AddAnnotation méthode d’un MKMapView instance, comme indiqué dans le code suivant :

//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 mappage est ici une instance d’un MKMapView, qui est la classe qui représente la carte elle-même. MKMapView utilise les Coordinate données dérivées du SampleMapAnnotation instance pour positionner la vue d’annotation sur la carte.

Le MKAnnotation protocole fournit un ensemble connu de fonctionnalités sur tous les objets qui l’implémentent, sans que le consommateur (la carte dans ce cas) ait besoin de connaître les détails de l’implémentation. Cela simplifie l’ajout d’une variété d’annotations possibles à une carte.

Présentation approfondie des protocoles

Étant donné que les interfaces C# ne prennent pas en charge les méthodes facultatives, Xamarin.iOS mappe les protocoles aux classes abstraites. Par conséquent, l’adoption d’un protocole dans Objective-C s’effectue dans Xamarin.iOS en dérivant de la classe abstraite liée au protocole et en implémentant les méthodes requises. Ces méthodes seront exposées en tant que méthodes abstraites dans la classe . Les méthodes facultatives du protocole seront liées aux méthodes virtuelles de la classe C#.

Par exemple, voici une partie du UITableViewDataSource protocole comme lié dans 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){...}
...
}

Notez que la classe est abstraite. Xamarin.iOS rend la classe abstraite pour prendre en charge les méthodes facultatives/obligatoires dans les protocoles. Toutefois, contrairement aux Objective-C protocoles (ou interfaces C#), les classes C# ne prennent pas en charge l’héritage multiple. Cela affecte la conception du code C# qui utilise des protocoles et conduit généralement à des classes imbriquées. Plus d’informations sur ce problème sont abordées plus loin dans ce document, dans la section Délégués.

GetCell(…) est une méthode abstraite, liée au Objective-Csélecteur , tableView:cellForRowAtIndexPath:qui est une méthode obligatoire du UITableViewDataSource protocole. Le sélecteur est le terme pour le Objective-C nom de la méthode. Pour appliquer la méthode en fonction des besoins, Xamarin.iOS la déclare comme abstraite. L’autre méthode, NumberOfSections(…), est liée à numberOfSectionsInTableview:. Cette méthode étant facultative dans le protocole, Xamarin.iOS la déclare comme virtuelle, ce qui la rend facultative à remplacer en C#.

Xamarin.iOS prend en charge toutes les liaisons iOS pour vous. Toutefois, si vous avez besoin de Objective-C lier manuellement un protocole, vous pouvez le faire en décorant une classe avec .ExportAttribute Il s’agit de la même méthode utilisée par Xamarin.iOS lui-même.

Pour plus d’informations sur la liaison de Objective-C types dans Xamarin.iOS, consultez l’article Types de liaisonObjective-C.

Mais nous n’en sommes pas encore là. Ils sont également utilisés dans iOS comme base pour Objective-C les délégués, qui est le sujet de la section suivante.

Délégués

iOS utilise Objective-C des délégués pour implémenter le modèle de délégation, dans lequel un objet transmet le travail à un autre. L’objet effectuant le travail est le délégué du premier objet. Un objet indique à son délégué d’effectuer le travail en lui envoyant des messages après certaines choses. L’envoi d’un message comme celui-ci dans Objective-C équivaut fonctionnellement à appeler une méthode en C#. Un délégué implémente des méthodes en réponse à ces appels et fournit donc des fonctionnalités à l’application.

Les délégués vous permettent d’étendre le comportement des classes sans avoir à créer de sous-classes. Les applications dans iOS utilisent souvent des délégués lorsqu’une classe appelle une autre après qu’une action importante se produit. Par exemple, la MKMapView classe rappelle son délégué lorsque l’utilisateur appuie sur une annotation sur une carte, ce qui donne à l’auteur de la classe déléguée la possibilité de répondre dans l’application. Vous pouvez utiliser un exemple de ce type d’utilisation des délégués plus loin dans cet article, dans Exemple d’utilisation d’un délégué avec Xamarin.iOS.

À ce stade, vous vous demandez peut-être comment une classe détermine les méthodes à appeler sur son délégué. Il s’agit d’un autre endroit où vous utilisez des protocoles. En règle générale, les méthodes disponibles pour un délégué proviennent des protocoles qu’il adopte.

Utilisation des protocoles avec les délégués

Nous avons vu plus tôt comment les protocoles sont utilisés pour prendre en charge l’ajout d’annotations à une carte. Les protocoles sont également utilisés pour fournir un ensemble connu de méthodes que les classes appellent après que certains événements se produisent, par exemple après que l’utilisateur appuie sur une annotation sur une carte ou sélectionne une cellule dans un tableau. Les classes qui implémentent ces méthodes sont appelées délégués des classes qui les appellent.

Les classes qui prennent en charge la délégation le font en exposant une propriété Delegate, à laquelle une classe implémentant le délégué est affectée. Les méthodes que vous implémentez pour le délégué dépendent du protocole que le délégué particulier adopte. Pour la UITableView méthode, vous implémentez le UITableViewDelegate protocole, pour la UIAccelerometer méthode , vous implémentez UIAccelerometerDelegate, et ainsi de suite pour toutes les autres classes dans iOS pour lesquelles vous souhaitez exposer un délégué.

La MKMapView classe que nous avons vue dans notre exemple précédent a également une propriété appelée Delegate, qu’elle appellera après différents événements. Le délégué pour MKMapView est de type MKMapViewDelegate. Vous l’utiliserez sous peu dans un exemple pour répondre à l’annotation une fois qu’elle a été sélectionnée, mais nous allons d’abord aborder la différence entre les délégués forts et faibles.

Délégués forts et délégués faibles

Les délégués que nous avons examinés jusqu’à présent sont des délégués forts, ce qui signifie qu’ils sont fortement typés. Les liaisons Xamarin.iOS sont fournies avec une classe fortement typée pour chaque protocole délégué dans iOS. Toutefois, iOS a également le concept d’un délégué faible. Au lieu de sous-classer une classe liée au Objective-C protocole pour un délégué particulier, iOS vous permet également de choisir de lier les méthodes de protocole vous-même dans toute classe que vous souhaitez dériver de NSObject, en décorant vos méthodes avec l’attribut ExportAttribute, puis en fournissant les sélecteurs appropriés. Lorsque vous utilisez cette approche, vous affectez un instance de votre classe à la propriété WeakDelegate au lieu de la propriété Delegate. Un délégué faible vous offre la possibilité de descendre votre classe de délégué dans une hiérarchie d’héritage différente. Examinons un exemple Xamarin.iOS qui utilise des délégués forts et faibles.

Exemple d’utilisation d’un délégué avec Xamarin.iOS

Pour exécuter du code en réponse à l’utilisateur appuyant sur l’annotation dans notre exemple, nous pouvons sous-classer MKMapViewDelegate et affecter une instance à la MKMapViewpropriété de Delegate . Le MKMapViewDelegate protocole contient uniquement des méthodes facultatives. Par conséquent, toutes les méthodes sont virtuelles liées à ce protocole dans la classe Xamarin.iOS MKMapViewDelegate . Lorsque l’utilisateur sélectionne une annotation, le MKMapView instance envoie le mapView:didSelectAnnotationView: message à son délégué. Pour gérer cela dans Xamarin.iOS, nous devons remplacer la DidSelectAnnotationView (MKMapView mapView, MKAnnotationView annotationView) méthode dans la sous-classe MKMapViewDelegate comme suit :

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 classe SampleMapDelegate illustrée ci-dessus est implémentée en tant que classe imbriquée dans le contrôleur qui contient les MKMapView instance. Dans Objective-C, vous verrez souvent le contrôleur adopter plusieurs protocoles directement dans la classe . Toutefois, étant donné que les protocoles sont liés à des classes dans Xamarin.iOS, les classes qui implémentent des délégués fortement typés sont généralement incluses en tant que classes imbriquées.

Une fois l’implémentation de la classe déléguée en place, il vous suffit d’instancier un instance du délégué dans le contrôleur et de l’affecter à la MKMapViewpropriété de Delegate , comme indiqué ici :

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

Pour utiliser un délégué faible pour accomplir la même chose, vous devez lier la méthode vous-même dans n’importe quelle classe qui dérive de NSObject et l’affecter à la WeakDelegate propriété du MKMapView. Étant donné que la UIViewController classe dérive finalement de NSObject (comme toutes les Objective-C classes de CocoaTouch), nous pouvons simplement implémenter une méthode liée mapView:didSelectAnnotationView: à directement dans le contrôleur et affecter le contrôleur à WeakDelegateMKMapView, en évitant d’avoir besoin de la classe imbriquée supplémentaire. Le code ci-dessous illustre cette approche :

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)
    {
        ...
    }
}

Lors de l’exécution de ce code, l’application se comporte exactement comme elle l’a fait lors de l’exécution de la version de délégué fortement typée. L’avantage de ce code est que le délégué faible ne nécessite pas la création de la classe supplémentaire qui a été créée lorsque nous avons utilisé le délégué fortement typé. Toutefois, cela se fait au détriment de la sécurité des types. Si vous faites une erreur dans le sélecteur qui a été passé à , vous ne le trouverez pas avant l’exécution ExportAttribute.

Événements et délégués

Les délégués sont utilisés pour les rappels dans iOS de la même façon que .NET utilise les événements. Pour que les API iOS et la façon dont elles utilisent Objective-C les délégués ressemblent davantage à .NET, Xamarin.iOS expose les événements .NET à de nombreux endroits où les délégués sont utilisés dans iOS.

Par exemple, l’implémentation antérieure où répondait MKMapViewDelegate à une annotation sélectionnée pouvait également être implémentée dans Xamarin.iOS à l’aide d’un événement .NET. Dans ce cas, l’événement est défini dans MKMapView et appelé DidSelectAnnotationView. Il aurait une EventArgs sous-classe de type MKMapViewAnnotationEventsArgs. La View propriété de MKMapViewAnnotationEventsArgs vous donne une référence à la vue d’annotation, à partir de laquelle vous pouvez procéder à la même implémentation que celle que vous avez eue précédemment, comme illustré ici :

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

Résumé

Cet article explique comment utiliser des événements, des protocoles et des délégués dans Xamarin.iOS. Nous avons vu comment Xamarin.iOS expose des événements de style .NET normaux pour les contrôles. Ensuite, nous avons découvert Objective-C les protocoles, notamment en quoi ils sont différents des interfaces C# et comment Xamarin.iOS les utilise. Enfin, nous avons examiné Objective-C les délégués du point de vue de Xamarin.iOS. Nous avons vu comment Xamarin.iOS prend en charge les délégués fortement et faiblement typés, et comment lier des événements .NET aux méthodes de délégué.