Události, protokoly a delegáty v Xamarin.iOS

Xamarin.iOS používá ovládací prvky k zveřejnění událostí pro většinu interakcí uživatelů. Aplikace Xamarin.iOS tyto události využívají podobným způsobem jako tradiční aplikace .NET. Například Xamarin.iOS UIButton třída má událost s názvem TouchUpInside a spotřebovává tuto událost stejně, jako kdyby byla tato třída a událost v aplikaci .NET.

Kromě tohoto přístupu .NET Xamarin.iOS zveřejňuje jiný model, který lze použít pro složitější interakci a datová vazba. Tato metodologie používá, co Apple volá delegáty a protokoly. Delegáti se podobají delegátům v jazyce C#, ale místo definování a volání jedné metody je delegát v Objective-C celé třídě, která odpovídá protokolu. Protokol je podobný rozhraní v jazyce C#, s tím rozdílem, že jeho metody mohou být volitelné. Například pro naplnění UITableView daty byste vytvořili delegovat třídu, která implementuje metody definované v protokolu UITableViewDataSource, který BY UITableView volal k naplnění sebe sama.

V tomto článku se dozvíte o všech těchto tématech a získáte solidní základ pro zpracování scénářů zpětného volání v Xamarin.iOS, včetně těchto:

  • Události – Použití událostí .NET s ovládacími prvky UIKit
  • Protokoly – Učení jaké protokoly a jak se používají, a vytvoření příkladu, který poskytuje data pro poznámku mapy.
  • Delegáti – Učení o Objective-C delegátech rozšířením příkladu mapy pro zpracování interakce uživatelů, která obsahuje poznámku, pak se naučíte rozdíl mezi silnými a slabými delegáty a kdy je použít.

Abychom ilustroval protokoly a delegáty, vytvoříme jednoduchou mapovou aplikaci, která přidá poznámku k mapě, jak je znázorněno zde:

An example of a simple map application that adds an annotation to a mapAn example annotation added to a map

Než začneme tuto aplikaci řešit, podívejme se na události .NET v uiKitu.

Události .NET s využitím UIKitu

Xamarin.iOS zveřejňuje události .NET v ovládacích prvcích UIKit. Například UIButton má událost TouchUpInside, kterou zpracováváte obvyklým způsobem v .NET, jak je znázorněno v následujícím kódu, který používá výraz lambda jazyka C#:

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

Můžete to také implementovat s anonymní metodou ve stylu C# 2.0, jako je tato:

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

Předchozí kód je připojen v ViewDidLoad metodě UIViewController. Proměnná aButton odkazuje na tlačítko, které můžete přidat do Tvůrce rozhraní Xcode nebo pomocí kódu.

Xamarin.iOS také podporuje styl cílové akce připojení kódu k interakci, ke které dochází s ovládacím prvku.

Další podrobnosti o modelu cílové akce pro iOS najdete v části Cílová akce základních kompetencí aplikací pro iOS v knihovně pro vývojáře pro iOS společnosti Apple.

Další informace naleznete v tématu Navrhování uživatelských rozhraní pomocí Xcode.

Událost

Pokud chcete zachytit události z UIControl, máte řadu možností: od použití lambda jazyka C# a delegování funkcí na použití rozhraní API nízké úrovně Objective-C .

Následující část ukazuje, jak byste zachytili událost TouchDownu na tlačítku v závislosti na tom, kolik ovládacího prvku potřebujete.

Styl jazyka C#

Použití syntaxe delegáta:

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

Pokud se vám místo toho líbí lambda:

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

Pokud chcete mít více tlačítek, použijte stejnou obslužnou rutinu ke sdílení stejného kódu:

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

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

Monitorování více než jednoho typu události

Události jazyka C# pro příznaky UIControlEvent mají mapování 1:1 na jednotlivé příznaky. Pokud chcete mít stejnou část kódu, která zpracovává dvě nebo více událostí, použijte metodu UIControl.AddTarget :

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

Použití syntaxe lambda:

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

Pokud potřebujete použít funkce Objective-Cnízké úrovně , jako je připojení ke konkrétní instanci objektu a vyvolání konkrétního selektoru:

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

// In some other place:

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

Upozorňujeme, že pokud implementujete metodu instance do zděděné základní třídy, musí se jednat o veřejnou metodu.

Protokoly

Protokol je Objective-C jazyková funkce, která poskytuje seznam deklarací metod. Slouží k podobnému účelu jako rozhraní v jazyce C#, hlavním rozdílem je to, že protokol může mít volitelné metody. Volitelné metody nejsou volány, pokud třída, která přijímá protokol, je neimplementuje. Jedna třída může Objective-C také implementovat více protokolů, stejně jako třída jazyka C#může implementovat více rozhraní.

Apple používá protokoly v iOSu k definování kontraktů pro třídy, které se mají přijmout, a zároveň abstrakci implementace třídy od volajícího, takže funguje stejně jako rozhraní jazyka C#. Protokoly se používají ve scénářích, které nejsou delegáty (například v MKAnnotation následujícím příkladu), i u delegátů (jak je uvedeno dále v tomto dokumentu v části Delegáti).

Protokoly s Xamarin.ios

Podívejme se na příklad použití Objective-C protokolu z Xamarin.iOS. V tomto příkladu MKAnnotation použijeme protokol, který je součástí MapKit architektury. MKAnnotation je protokol, který umožňuje každému objektu, který ho přijme, poskytnout informace o poznámce, kterou lze přidat do mapy. Například implementace MKAnnotation objektu poskytuje umístění poznámky a název přidružený k němu.

Tímto způsobem MKAnnotation se protokol používá k poskytování relevantních dat, která doprovází poznámku. Skutečné zobrazení samotné poznámky je vytvořeno z dat v objektu, který přijímá MKAnnotation protokol. Například text popisku, který se zobrazí, když uživatel klepne na poznámku (jak je znázorněno na následujícím snímku obrazovky), pochází z Title vlastnosti ve třídě, která implementuje protokol:

Example text for the callout when the user taps on the annotation

Jak je popsáno v další části, Protokoly podrobně, Xamarin.iOS vytvoří vazbu protokolů na abstraktní třídy. MKAnnotation Pro protokol je vázaná třída jazyka C# pojmenována MKAnnotation pro napodobování názvu protokolu a jedná se o podtřídu NSObject, kořenovou základní třídu pro CocoaTouch. Protokol vyžaduje implementaci getter a setter pro souřadnici; název a podnadpis jsou však volitelné. Proto je vlastnost ve MKAnnotation třídě abstraktní a Coordinatevyžaduje, aby byla implementována a TitleSubtitle vlastnosti jsou označené jako virtuální, takže jsou volitelné, jak je znázorněno níže:

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

Libovolná třída může poskytovat anotační data jednoduše odvozením z MKAnnotation, pokud je implementována alespoň Coordinate vlastnost. Tady je například ukázková třída, která přebírá souřadnici v konstruktoru a vrací řetězec pro název:

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

Prostřednictvím protokolu, ke kterému je vázán, může každá třída, která podtřídy MKAnnotation poskytnout relevantní data, která bude mapa používat při vytváření zobrazení poznámek. Pokud chcete přidat poznámku k mapě, jednoduše zavolejte AddAnnotation metodu MKMapView instance, jak je znázorněno v následujícím kódu:

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

Proměnná mapy zde je instance MKMapView, což je třída, která představuje samotnou mapu. Coordinate Použije MKMapView data odvozená z SampleMapAnnotation instance k umístění zobrazení poznámek na mapě.

Protokol MKAnnotation poskytuje známou sadu funkcí napříč všemi objekty, které ho implementují, bez toho, aby uživatel (v tomto případě mapu) potřeboval vědět o podrobnostech implementace. To zjednodušuje přidávání různých možných poznámek do mapy.

Podrobné informace o protokolech

Vzhledem k tomu, že rozhraní jazyka C# nepodporují volitelné metody, Xamarin.iOS mapuje protokoly na abstraktní třídy. Proto přijetí protokolu v Objective-C Xamarin.iOS je dosaženo odvozením abstraktní třídy, která je svázána s protokolem a implementací požadovaných metod. Tyto metody budou vystaveny jako abstraktní metody ve třídě. Volitelné metody z protokolu budou vázány na virtuální metody třídy C#.

Tady je například část UITableViewDataSource protokolu vázaná v 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){...}
...
}

Všimněte si, že třída je abstraktní. Xamarin.iOS ztěžuje třídu tak, aby podporovala volitelné/požadované metody v protokolech. Na rozdíl od Objective-C protokolů (nebo rozhraní jazyka C#) však třídy jazyka C# nepodporují více dědičnosti. To má vliv na návrh kódu jazyka C#, který používá protokoly, a obvykle vede k vnořeným třídám. Další informace o tomto problému najdete dále v tomto dokumentu v části Delegáti.

GetCell(…) je abstraktní metoda vázaná na Objective-Cselektor, tableView:cellForRowAtIndexPath:což je požadovaná metoda UITableViewDataSource protokolu. Selektor je Objective-C termín pro název metody. Pokud chcete vynutit metodu podle potřeby, Xamarin.iOS ji deklaruje jako abstraktní. Druhá metoda, NumberOfSections(…)je vázána na numberOfSectionsInTableview:. Tato metoda je v protokolu nepovinná, takže Xamarin.iOS ji deklaruje jako virtuální, takže je volitelná k přepsání v jazyce C#.

Xamarin.iOS se postará o všechny vazby iOS za vás. Nicméně, pokud někdy potřebujete vytvořit vazbu protokolu z Objective-C ručně, můžete to udělat dekorací třídy s ExportAttribute. Jedná se o stejnou metodu, kterou používá samotný Xamarin.iOS.

Další informace o vytvoření vazby Objective-C typů v Xamarin.iOS naleznete v článku Typy vazebObjective-C.

Zatím ale nepoužíváme protokoly. Používají se také v iOSu jako základ pro Objective-C delegáty, což je téma další části.

Delegáti

iOS používá Objective-C delegáty k implementaci vzoru delegování, ve kterém jeden objekt předává práci do jiného. Objekt, který provádí práci, je delegátem prvního objektu. Objekt řekne svému delegátovi, aby fungoval odesláním zpráv, jakmile dojde k určitým věcem. Odeslání podobné zprávy Objective-C je funkčně ekvivalentní volání metody v jazyce C#. Delegát implementuje metody v reakci na tato volání a poskytuje tak funkci pro aplikaci.

Delegáti umožňují rozšířit chování tříd bez nutnosti vytvářet podtřídy. Aplikace v iOSu často používají delegáty, když jedna třída volá zpět do jiného poté, co dojde k důležité akci. Třída například volá zpět svému delegátovi, MKMapView když uživatel klepne na poznámku na mapě a autor třídy delegáta dává možnost odpovědět v aplikaci. Můžete si projít příklad tohoto typu použití delegáta dále v tomto článku v části Příklad Použití delegáta s Xamarin.iOS.

V tomto okamžiku vás může zajímat, jak třída určuje, jaké metody se mají volat na jeho delegáta. Toto je další místo, kde používáte protokoly. Metody, které jsou pro delegáta k dispozici, obvykle pocházejí z protokolů, které přijmou.

Jak se protokoly používají s delegáty

Viděli jsme, jak se protokoly používají k podpoře přidávání poznámek do mapy. Protokoly se také používají k poskytování známé sady metod pro třídy, které se mají volat po určitých událostech, například po klepnutí uživatele na poznámku na mapě nebo výběru buňky v tabulce. Třídy, které implementují tyto metody, jsou známé jako delegáty tříd, které je volají.

Třídy, které podporují delegování, tak, že zveřejňují vlastnost Delegate, ke které je přiřazena třída implementující delegáta. Metody, které implementujete pro delegáta, budou záviset na protokolu, který konkrétní delegát přijme. UITableView Pro metodu UITableViewDelegate implementujete protokol, pro metodu UIAccelerometer byste implementovali UIAccelerometerDelegatea tak dále pro všechny ostatní třídy v iOSu, pro které byste chtěli vystavit delegáta.

Třída MKMapView , kterou jsme viděli v našem předchozím příkladu, má také vlastnost s názvem Delegate, kterou bude volat po různých událostech. Delegát pro MKMapView je typu MKMapViewDelegate. Použijete to krátce v příkladu k odpovědi na poznámku po jejím výběru, ale nejprve se podíváme na rozdíl mezi silnými a slabými delegáty.

Silní delegáti versus slabí delegáti

Delegáti, na které jsme se zatím podívali, jsou silní delegáti, což znamená, že jsou silně napsané. Vazby Xamarin.iOS se dodávají se třídou silného typu pro každý delegovaný protokol v iOSu. IOS má ale také koncept slabého delegáta. Místo podtřídy třídy vázané na Objective-C protokol pro konkrétní delegáta vám iOS také umožňuje svázat metody protokolu sami v jakékoli třídě, která je odvozena z NSObject, dekódovat metody exportAttribute a pak poskytnout příslušné selektory. Když použijete tento přístup, přiřadíte instanci třídy WeakDelegate vlastnost namísto Delegate vlastnost. Slabý delegát vám nabízí flexibilitu pro snížení úrovně třídy delegáta v jiné hierarchii dědičnosti. Podívejme se na příklad Xamarin.iOS, který používá silné i slabé delegáty.

Příklad použití delegáta s Xamarin.iOS

Pokud chceme spustit kód v reakci na uživatele, který klepne na poznámku v našem příkladu, můžeme podtřídu MKMapViewDelegate a přiřadit instanci vlastnostiMKMapViewDelegate. Protokol MKMapViewDelegate obsahuje pouze volitelné metody. Proto všechny metody jsou virtuální, které jsou vázány na tento protokol ve třídě Xamarin.iOS MKMapViewDelegate . Když uživatel vybere poznámku, MKMapView instance odešle mapView:didSelectAnnotationView: zprávu svému delegátu. Abychom to mohli zpracovat v Xamarin.iOS, musíme přepsat DidSelectAnnotationView (MKMapView mapView, MKAnnotationView annotationView) metodu v podtřídě MKMapViewDelegate takto:

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

SampleMapDelegate třída zobrazená výše je implementována jako vnořená třída v kontroleru, který obsahuje MKMapView instanci. V Objective-Ctomto případě často uvidíte, že kontroler přijímá více protokolů přímo v rámci třídy. Vzhledem k tomu, že protokoly jsou vázány na třídy v Xamarin.iOS, třídy, které implementují delegáty silného typu, jsou obvykle zahrnuty jako vnořené třídy.

Při implementaci třídy delegáta stačí vytvořit instanci delegáta v kontroleru a přiřadit ji k MKMapViewvlastnosti 's Delegate , jak je znázorněno zde:

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

Chcete-li použít slabého delegáta k dosažení stejné věci, musíte vytvořit vazbu metody v libovolné třídě, která je odvozena od NSObject a přiřadit ji k WeakDelegate vlastnosti MKMapView. Vzhledem k tomu, že UIViewController třída je nakonec odvozena od NSObject (jako každá Objective-C třída v CocoaTouch), můžeme jednoduše implementovat metodu vázanou přímo mapView:didSelectAnnotationView: do kontroleru a přiřadit kontroleru k MKMapView's WeakDelegate, vyhnout se nutnosti extra vnořené třídy. Následující kód ukazuje tento přístup:

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

Při spuštění tohoto kódu se aplikace chová přesně stejně jako při spuštění verze delegáta se silnými typy. Výhodou tohoto kódu je, že slabý delegát nevyžaduje vytvoření extra třídy, která byla vytvořena při použití delegáta silného typu. Jedná se však o náklady na bezpečnost typů. Kdybyste v selektoru udělali chybu, která byla předána do ExportAttribute, nezjistit, dokud nespustíte modul runtime.

Události a delegáti

Delegáti se používají pro zpětná volání v iOSu podobně jako rozhraní .NET používá události. Aby rozhraní API pro iOS a způsob, jakým používají Objective-C delegáty, vypadaly podobně jako .NET, Xamarin.iOS zveřejňuje události .NET na mnoha místech, kde se delegáti používají v iOSu.

Například předchozí implementace, kde MKMapViewDelegate odpověď na vybranou poznámku může být implementována také v Xamarin.iOS pomocí události .NET. V takovém případě by byla událost definována a MKMapView volána DidSelectAnnotationView. Měl by podtřídu EventArgs typu MKMapViewAnnotationEventsArgs. Vlastnost ViewMKMapViewAnnotationEventsArgs by vám poskytla odkaz na zobrazení poznámek, ze kterého byste mohli pokračovat ve stejné implementaci, kterou jste měli dříve, jak je znázorněno zde:

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

Shrnutí

Tento článek se zabývá používáním událostí, protokolů a delegátů v Xamarin.iOS. Viděli jsme, jak Xamarin.iOS zveřejňuje běžné události stylu .NET pro ovládací prvky. Dále jsme se dozvěděli o Objective-C protokolech, včetně toho, jak se liší od rozhraní jazyka C# a jak je Xamarin.iOS používá. Nakonec jsme prozkoumali Objective-C delegáty z pohledu Xamarin.iOS. Viděli jsme, jak Xamarin.iOS podporuje delegáty silného i slabě zadaného typu a jak vytvořit vazbu událostí .NET na metody delegování.