Události, protokoly a Delegáti v Xamarin. iOS

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

Kromě tohoto přístupu k .NET vystavuje Xamarin. iOS jiný model, který se dá použít pro složitější interakce a datovou vazbu. Tato metodologie používá volání delegátů a protokolů Apple. Delegáti jsou v jazyce C# podobným způsobem, ale místo definování a volání jediné metody delegát v Objective-C je celá třída, která odpovídá protokolu. Protokol je podobný rozhraní v jazyce C# s tím rozdílem, že jeho metody mohou být nepovinné. Například pro naplnění UITableView s daty byste vytvořili třídu delegáta, která implementuje metody definované v protokolu UITableViewDataSource, který by UITableView volal k naplnění.

V tomto článku se seznámíte se všemi těmito tématy, které vám podávají plný základ pro zpracování scénářů zpětného volání v Xamarin. iOS, včetně těchto:

  • Události – pomocí událostí .NET s ovládacími prvky UIKit
  • protokoly – Učení, jaké protokoly jsou a jak se používají, a vytvoření příkladu, který poskytuje data pro anotaci mapy.
  • delegáti – Učení o delegátech rozšířením příkladu mapy pro zpracování interakce uživatele, která obsahuje anotaci, a následným učením rozdílu mezi silnými a slabými delegáty a kdy použít každou z nich.

Pro ilustraci protokolů a delegátů vytvoříme jednoduchou aplikaci mapy, která přidá anotaci na mapu, jak je znázorněno zde:

Příklad jednoduché aplikace mapy, která přidá poznámku na mapuPříklad poznámky přidané k mapě

Než se pustíte do této aplikace, začněte tím, že si probereme události .NET pod UIKit.

Události .NET s UIKit

Xamarin. iOS zpřístupňuje události .NET v ovládacích prvcích UIKit. Například UIButton má událost TouchUpInside, která se zpracovává jako obvykle 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 ji také implementovat pomocí anonymní metody C# 2,0, jako je tato:

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

Předchozí kód je zapojený v ViewDidLoad metodě UIViewController. aButtonProměnná odkazuje na tlačítko, které můžete přidat do Interface Builder Xcode nebo s kódem.

Xamarin. iOS také podporuje styl cíle a akce propojení kódu s interakcí, ke které dochází s ovládacím prvkem.

Další podrobnosti o vzorcích cílů a akcí pro iOS najdete v části Target-Action v tématu kompetence aplikací pro iOS v knihovně pro vývojáře v iOS společnosti Apple.

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

Události

Chcete-li zachytit události z UIControl, máte rozsah možností: z použití výrazů lambda jazyka C# a delegování funkcí k použití Objective-C rozhraní API nízké úrovně.

V následující části se dozvíte, jak byste zachytit událost TouchDown na tlačítko v závislosti na tom, kolik potřebujete.

Styl C#

Pomocí syntaxe delegáta:

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

Pokud místo toho chcete použít výrazy lambda:

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

Pokud chcete, aby více tlačítek používalo 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 druhu události

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

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

Pomocí syntaxe lambda:

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

Pokud potřebujete používat funkce nízké úrovně Objective-C , jako je například zapojení do konkrétní instance 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 v zděděné základní třídě, musí se jednat o veřejnou metodu.

Protokoly

Protokol je Objective-C funkce jazyka, která poskytuje seznam deklarací metod. Obsluhuje podobný účel rozhraní v jazyce C#, hlavní rozdíl spočívá v tom, ž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 v v Objective-C může implementovat více protokolů, stejně jako třída jazyka C#, může implementovat více rozhraní.

Společnost Apple používá protokoly v rámci iOS k definování smluv pro třídy, které se mají přijmout. při abstrakci se však z volajícího vyvolala implementace třídy, takže funguje stejně jako rozhraní C#. Protokoly se používají ve scénářích, které nejsou delegáty (například s MKAnnotation příkladem zobrazeným dále) a s delegáty (jak je uvedeno dále v tomto dokumentu v části Delegáti).

Protokoly s Xamarin. iOS

Pojďme se podívat na příklad pomocí Objective-C protokolu z Xamarin. iOS. V tomto příkladu použijeme MKAnnotation protokol, který je součástí MapKit rozhraní .NET Framework. MKAnnotation je protokol, který umožňuje jakémukoli objektu, který ho přijme, aby poskytoval informace o anotaci, kterou lze přidat k mapě. Například implementace objektu MKAnnotation poskytuje umístění anotace a nadpis, který je k němu přidružen.

Tímto způsobem se MKAnnotation protokol používá k poskytnutí 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 pro popisek, který se zobrazí, když uživatel klepne na anotaci (jak je znázorněno na snímku obrazovky níže), přichází z Title vlastnosti ve třídě, která implementuje protokol:

Příklad textu pro popisek při klepnutí na poznámku uživatelem

Jak je popsáno v další části, protokoly s hloubkou podrobně, Xamarin. iOS váže protokoly na abstraktní třídy. Pro MKAnnotation protokol je vázaná třída jazyka C# pojmenována MKAnnotation tak, aby napodobuje název protokolu, a je podtřídou NSObject kořenové základní třídy pro CocoaTouch. Protokol vyžaduje, aby byly pro souřadnici implementovány metody getter a setter. název a podnadpis jsou však volitelné. Proto ve MKAnnotation třídě Coordinate je vlastnost MKAnnotation, vyžaduje, aby byla implementována a TitleSubtitle vlastnosti a jsou označeny jako Coordinate, takže jsou nepovinné, 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 poskytnout data poznámek pouhým odvozením z MKAnnotation , pokud je alespoň tato Coordinate vlastnost implementována. Zde 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ána, jakákoliv třída, kterou podtřídy MKAnnotation mohou poskytnout relevantní data, která budou použita při vytváření zobrazení poznámky. Chcete-li přidat poznámku na mapu, 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));

Mapová proměnná je zde instancí MKMapView třídy, která představuje samotnou mapu. Použije MKMapViewCoordinate data odvozená z SampleMapAnnotation instance k umístění zobrazení poznámky na mapě.

MKAnnotationProtokol poskytuje známou sadu funkcí napříč všemi objekty, které ji implementují, bez příjemce (mapa v tomto případě), které potřebují vědět o podrobnostech implementace. Tím se zjednoduší přidání nejrůznějších možných poznámek na mapu.

Protokoly s hloubkovým podrobně

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

Tady je například část protokolu, která je UITableViewDataSource 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 umožňuje, aby abstraktní třída podporovala volitelné/požadované metody v protokolech. Nicméně na rozdíl od Objective-C protokolů (rozhraní c#) třídy C# nepodporují vícenásobnou dědičnost. To má vliv na návrh kódu jazyka C#, který používá protokoly, a obvykle vede na vnořené třídy. Další informace o tomto problému najdete dále v tomto dokumentu v části Delegáti.

GetCell(…)je abstraktní metoda, která je svázána s Objective-CGetCell(…), tableView:cellForRowAtIndexPath: , což je požadovaná metoda UITableViewDataSource protokolu. Selektor je Objective-C termínem pro název metody. Aby bylo možné metodu vyhovět jako povinnou, Xamarin. iOS ji deklaruje jako abstraktní. Druhá metoda, NumberOfSections(…) , je svázána s numberOfSectionsInTableview: . Tato metoda je v protokolu nepovinná, takže Xamarin. iOS ji deklaruje jako virtuální, takže je volitelná pro přepsání v jazyce C#.

Xamarin. iOS se stará o veškerou vazbu iOS za vás. Nicméně, pokud někdy potřebujete vytvořit vazby protokolu z Objective-C ručního, můžete tak učinit upravení třídy pomocí ExportAttribute . Jedná se o stejnou metodu, kterou používá sám Xamarin. iOS.

Další informace o tom, jak vytvořit vazby Objective-C typů v Xamarin. iOS, najdete v článku Binding Objective-C Types .

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

Delegáti

systém iOS používá Objective-C delegáty k implementaci vzoru delegování, ve kterém jeden objekt přechází do jiné pracovní. Objekt, který provádí práci, je delegátem prvního objektu. Objekt oznamuje jeho delegátovi, aby fungoval, odesláním zpráv IT po určité věci. Odeslání zprávy jako v nástroji Objective-C je funkčně ekvivalentní volání metody v jazyce C#. Delegát implementuje metody v reakci na tato volání, takže poskytuje funkce pro aplikaci.

Delegáti umožňují zvětšit chování tříd bez nutnosti vytvářet podtřídy. Aplikace v systému iOS často používají delegáty, když jedna třída volá zpět na jinou po výskytu důležité akce. Například MKMapView třída volá zpět na delegáta, když uživatel klepne na mapu na mapě, takže autorovi třídy delegáta umožní odpovědět v rámci aplikace. Příklad tohoto typu použití delegáta si můžete projít dále v tomto článku, v příkladu použití delegáta s Xamarin.iOS.

V tomto okamžiku se možná ptáte, jak třída určuje, jaké metody se mají volat na svém delegátu. Toto je další místo, kde používáte protokoly. Metody dostupné pro delegáta obvykle pocházejí z protokolů, které přijímají.

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

Už dříve jsme viděli, 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, které třídy volají po určitých událostech, například když uživatel klepne na anotaci na mapě nebo vybere buňku v tabulce. Třídy, které implementují tyto metody, jsou označovány jako delegáty tříd, které je volají.

Třídy, které podporují delegování tak, že vystaví 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. Pro metodu implementujete protokol pro metodu , implementujete , a tak dále pro všechny ostatní třídy v celém UITableViewUITableViewDelegateUIAccelerometer iOSu, pro které chcete vystavit UIAccelerometerDelegate delegáta.

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

Silné delegáty vs. slabé delegáty

Delegáty, na které jsme se dosud dívali, jsou silné delegáty, což znamená, že jsou silného typu. Vazby Xamarin.iOS se dodá se třídou silného typu pro každý protokol delegáta v iOSu. IOS má ale také koncept slabého delegáta. Místo vytvoření podtřídy třídy vázané na protokol pro konkrétního delegáta umožňuje iOS také zvolit vazbu metod protokolu sami v libovolné třídě, která je odvozená z objektu NSObject, přenést metody pomocí ExportAttribute a pak zadat příslušné Objective-C selektory. Když použijete tento přístup, přiřadíte instanci vaší třídy k vlastnosti WeakDelegate místo vlastnosti Delegate. Slabý delegát vám nabízí flexibilitu při vynění třídy delegáta z jiné hierarchie 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

Abychom mohli spustit kód v reakci na to, že uživatel klepne na poznámku v našem příkladu, můžeme vytvořit podtřídu a přiřadit MKMapViewDelegate instanci k vlastnosti objektu MKMapViewDelegate . Protokol MKMapViewDelegate obsahuje pouze volitelné metody. Proto jsou všechny metody virtuální, které jsou vázané na tento protokol ve třídě MKMapViewDelegate Xamarin.iOS. Když uživatel vybere poznámku, instance odešle zprávu svému MKMapViewmapView:didSelectAnnotationView: delegátovi. Aby to bylo možné zvládnout v Xamarin.iOS, musíme přepsat metodu v podtřídě DidSelectAnnotationView (MKMapView mapView, MKAnnotationView annotationView) MKMapViewDelegate, jako je tato:

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

Výše uvedená třída SampleMapDelegate je implementovaná jako vnořená třída v kontroleru, který obsahuje MKMapView instanci . V Objective-C systému často uvidíte, že kontroler přijímá několik protokolů přímo v rámci třídy . Vzhledem k tomu, že jsou protokoly vázané na třídy v Xamarin.iOS, jsou třídy, které implementují delegáty silného typu, obvykle zahrnuty jako vnořené třídy.

Když je implementace třídy delegáta nastavená, potřebujete pouze vytvořit instanci delegáta v kontroleru a přiřadit ji k vlastnosti třídy , jak je MKMapViewDelegate 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
    {
        ...
    }
}

Pokud chcete použít slabého delegáta k provedení stejného návazu, musíte metodu svázat sami v libovolné třídě, která je odvozena z , a přiřadit ji k vlastnosti NSObjectWeakDelegate objektu MKMapView . Vzhledem k tomu, že třída je nakonec odvozena z (stejně jako každá třída v CocoaTouch), můžeme jednoduše implementovat metodu svázané s přímo v kontroleru a přiřadit kontroler k , takže není potřeba UIViewControllerNSObject další Objective-CmapView:didSelectAnnotationView:MKMapViewWeakDelegate vnořená třída. 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á stejně jako při spuštění verze delegáta silného typu. Výhodou tohoto kódu je, že slabý delegát nevyžaduje vytvoření dodatečné třídy, která byla vytvořena při použití delegáta silného typu. To je ale na úkor bezpečnosti typů. Pokud byste ve selektoru udělali chybu, která byla předána do , nezísděli byste to až ExportAttribute za běhu.

Události a delegáti

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

Například dřívější implementaci, ve které objekt reagoval na vybranou poznámku, je možné implementovat také v MKMapViewDelegate Xamarin.iOS pomocí události .NET. V takovém případě by událost byla definována v a MKMapView s názvem DidSelectAnnotationView . Bude mít EventArgs podtřídu typu MKMapViewAnnotationEventsArgs . Vlastnost objektu vám poskytne odkaz na zobrazení poznámek, ze kterého můžete pokračovat se stejnou implementací, jakou jste měli dříve, jak je ViewMKMapViewAnnotationEventsArgs 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);
    }
};

Souhrn

Tento článek popisuje, jak používat události, protokoly a delegáty v Xamarin.iOS. Viděli jsme, jak Xamarin.iOS zpřístupňuje běžné události stylu .NET pro ovládací prvky. Dále jsme se dozvěděli o protokolech, včetně toho, jak se liší od rozhraní jazyka C# a jak je Objective-C Xamarin.iOS používá. Nakonec jsme Objective-C prozkoumali delegáty z pohledu Xamarin.iOS. Viděli jsme, jak Xamarin.iOS podporuje delegáty silného i slabého typu a jak vytvořit vazbu událostí .NET k delegování metod.