É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 façon que les applications .NET traditionnelles. Par exemple, la classe Xamarin.iOS UIButton 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 les délégués et les protocoles. Les délégués sont similaires au concept des délégués en C#, mais au lieu de définir et d’appeler une seule méthode, un délégué est Objective-C 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 créez une classe de délégué qui implémente les méthodes définies dans le protocole UITableViewDataSource que l’UITableView appelle pour remplir elle-même.

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

  • Événements : utilisation d’événements .NET avec des contrôles UIKit.
  • Protocoles : Apprentissage quels protocoles sont utilisés et comment ils sont utilisés, et la création d’un exemple qui fournit des données pour une annotation de carte.
  • Délégués : Apprentissage 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 de ces délégués.

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 illustré dans le code suivant qui utilise une expression lambda C# :

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

Vous pouvez également implémenter cela 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 de 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 Action cible des compétences d’application principales pour iOS dans la bibliothèque de développement 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 : à l’aide des lambdaS C# et des fonctions déléguées à l’aide 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

Utilisation 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 que plusieurs boutons utilisent 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 avoir le même élément de code gérer deux événements ou plus, utilisez la UIControl.AddTarget méthode :

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

Utilisation 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, Objective-Ctelles que le raccordement à une instance d’objet particulière 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 d’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 principale 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 classe unique peut Objective-C 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 s’éloignant de la classe d’implémentation de l’appelant, fonctionnant ainsi comme une interface C#. Les protocoles sont utilisés dans des scénarios non délégués (par exemple, avec l’exemple MKAnnotation illustré ci-dessous) et avec des délégués (comme présentés plus loin dans ce document, dans la section Délégués).

Protocoles avec Xamarin.ios

Examinons un exemple utilisant un Objective-C protocole à partir 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 Deep Dive, Xamarin.iOS lie des 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 , la classe de NSObjectbase racine pour CocoaTouch. Le protocole nécessite qu’un getter et un setter soient implémentés 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, nécessitant qu’elle soit implémentée et que les Title Subtitle propriétés soient 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 ();
        }
    }
...
}

Toute classe peut fournir des données d’annotation en dérivant MKAnnotationsimplement de , 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 il est lié, toute classe à laquelle les MKAnnotation sous-classes peuvent fournir des données pertinentes qui seront utilisées par la carte lorsqu’elle crée la vue de l’annotation. Pour ajouter une annotation à une carte, appelez simplement la AddAnnotation méthode d’une 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 carte est ici une instance d’un MKMapView, qui est la classe qui représente la carte elle-même. Les MKMapView données dérivées de l’instance SampleMapAnnotation seront utilisées Coordinate 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 est Objective-C effectuée 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 sous forme de méthodes abstraites dans la classe. Les méthodes facultatives du protocole sont 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/requises dans les protocoles. Toutefois, contrairement aux Objective-C protocoles (ou interfaces C#), les classes C# ne prennent pas en charge plusieurs héritages. 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 est abordé 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 requise du UITableViewDataSource protocole. Le sélecteur est le Objective-C terme du nom de méthode. Pour appliquer la méthode en fonction des besoins, Xamarin.iOS le déclare comme abstrait. L’autre méthode, est NumberOfSections(…)liée à numberOfSectionsInTableview:. Cette méthode est facultative dans le protocole. Par conséquent, Xamarin.iOS le déclare comme virtuel, ce qui le rend facultatif pour remplacer en C#.

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

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

Nous n’avons pas encore de protocoles. Ils sont également utilisés dans iOS comme base pour Objective-C les délégués, qui est la rubrique 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 passe le travail à un autre. L’objet effectuant le travail est le délégué du premier objet. Un objet indique à son délégué de faire du travail en l’envoyant des messages une fois que certaines choses se produisent. L’envoi d’un message comme celui-ci 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 revient à une autre une fois qu’une action importante se produit. Par exemple, la MKMapView classe revient à 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é l’opportunité de répondre dans l’application. Vous pouvez utiliser un exemple de ce type d’utilisation de délégué 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 quelles 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’ils adoptent.

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

Nous avons vu précédemment 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 permettant aux classes d’appeler une fois 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 adopté par le délégué particulier. 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 lequel il est de type MKMapViewDelegate. Vous allez l’utiliser sous peu dans un exemple pour répondre à l’annotation après sa sélection, mais commençons par discuter de 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 lier les méthodes de protocole vous-même dans n’importe quelle classe que vous aimez qui dérive de NSObject, décorant vos méthodes avec ExportAttribute, puis en fournissant les sélecteurs appropriés. Lorsque vous adoptez cette approche, vous affectez une 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 réduire la hiérarchie d’héritage de votre classe de délégué. Examinons un exemple Xamarin.iOS qui utilise à la fois 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é 's 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, l’instance MKMapView 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 indiquée ci-dessus est implémentée en tant que classe imbriquée dans le contrôleur qui contient l’instance MKMapView . Dans Objective-C, vous verrez souvent que le contrôleur adopte 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.

Avec l’implémentation de classe de délégué en place, vous devez instancier une instance du délégué dans le contrôleur et l’affecter à la MKMapViewpropriété ' 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 NSObject et l’assigne à la WeakDelegate propriété du MKMapView. Étant donné que la UIViewController classe dérive finalement de NSObject (comme chaque Objective-C classe dans CocoaTouch), nous pouvons simplement implémenter une méthode liée directement mapView:didSelectAnnotationView: au contrôleur et affecter le contrôleur à MKMapView's WeakDelegate, évitant ainsi la nécessité 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 lors de l’exécution de la version déléguée 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 créée lorsque nous avons utilisé le délégué fortement typé. Toutefois, cela se produit au détriment de la sécurité de type. Si vous deviez faire une erreur dans le sélecteur qui a été passé au , vous ne trouverez pas jusqu’à ExportAttributel’exécution.

É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 des événements. Pour rendre les API iOS et la façon dont elles utilisent Objective-C des délégués semblent plus comme .NET, Xamarin.iOS expose les événements .NET dans de nombreux endroits où les délégués sont utilisés dans iOS.

Par exemple, l’implémentation précédente où la MKMapViewDelegate réponse à une annotation sélectionnée peut é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 cette propriété vous donnerait une référence à la vue d’annotation, à partir de laquelle vous pouviez poursuivre la même implémentation que celle que vous aviez 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 a abordé 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 les Objective-C protocoles, y compris la façon dont 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 à des méthodes déléguées.