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í stejným způsobem jako tradiční aplikace .NET. Například třída UIButton Xamarin.iOS má událost s názvem TouchUpInside a využívá tuto událost stejně jako 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é vazby. Tato metodologie používá to, 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 výjimkou toho, že jeho metody mohou být volitelné. Pokud tedy například chcete naplnit UITableView daty, vytvoříte delegovat třídu, která implementuje metody definované v protokolu UITableViewDataSource, který by UITableView volal k naplnění samotného.

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ě:

  • Události – Použití 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 poznámku mapy.
  • Delegáti – Učení o Objective-C delegátech rozšířením příkladu mapy pro zpracování interakce uživatele, která obsahuje poznámku, a pak se učí 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 tady:

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 na 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 pomocí anonymní metody 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 s kódem.

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

Další podrobnosti o vzoru cílové akce pro iOS najdete v části Target-Action základních kompetencí aplikací pro iOS v Knihovně pro vývojáře pro iOS společnosti Apple.

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

událost

Pokud chcete zachytit události z UIControl, máte řadu možností: od použití lambda c# a delegování funkcí na používání 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ích prvků 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í to být veřejná metoda.

Protokoly

Protokol je jazyková Objective-C funkce, která poskytuje seznam deklarací metod. Slouží k podobnému účelu rozhraní v jazyce C#, hlavním rozdílem je, že protokol může mít volitelné metody. Volitelné metody se nevolají, 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 implementující třídu od volajícího, takže funguje stejně jako rozhraní jazyka C#. Protokoly se používají ve scénářích, které nejsou delegáty (jako MKAnnotation je příklad uvedený dále), a s delegáty (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 objektu MKAnnotation poskytuje umístění poznámky a názvu přidruženého 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 sestaveno 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 snímku obrazovky níže), pochází z Title vlastnosti třídy, která implementuje protokol:

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

Jak je popsáno v další části, protokoly deep dive, Xamarin.iOS sváže protokoly s abstraktními třídami. MKAnnotation Pro protokol má svázaná třída jazyka C# název MKAnnotation napodobovat název protokolu a jedná se o podtřídu NSObject, kořenovou základní třídu pro CocoaTouch. Protokol vyžaduje, aby byl pro koordinaci implementován getter a setter; název a podnadpis jsou však volitelné. MKAnnotation Proto ve třídě je Coordinate vlastnost abstraktní, vyžaduje, aby byla implementována a TitleSubtitle vlastnosti jsou označeny jako virtuální, takže je 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 data poznámek 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, na který 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 je zde instance MKMapView, což je třída, která představuje samotnou mapu. Použije MKMapViewCoordinate 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í, aniž by uživatel (v tomto případě mapa) 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 z abstraktní třídy, která je vázána na protokol a implementace 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, která je 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 abstraktní pro podporu volitelných/požadovaných metod 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 ovlivňuje 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 pro iOS za vás. Pokud však někdy potřebujete vázat protokol z Objective-C ručně, můžete to udělat tak, že dekorujete třídu 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 najdete 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 říká delegátovi, aby fungoval, odesláním zpráv po určitých věcech. 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 aplikace.

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, jakmile 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. Příklad použití delegáta můžete provést později v tomto článku v části Příklad Použití delegáta s Xamarin.iOS.

V tuto chvíli vás může zajímat, jak třída určuje, jaké metody se mají volat na delegáta. To je další místo, kde používáte protokoly. Metody dostupné delegátu obvykle pocházejí z protokolů, které přijímají.

Způsob použití protokolů s delegáty

Viděli jsme dříve, jak se protokoly používají k podpoře přidávání poznámek do mapy. Protokoly se také používají k poskytnutí 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é tyto metody implementují, se označují jako delegáti 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 UITableView implementujete protokol, pro metodu UITableViewDelegateUIAccelerometer 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 Delegát, kterou bude volat po různých událostech. Delegát je MKMapView typu MKMapViewDelegate. Použijete to krátce v příkladu k odpovědi na poznámku po výběru, ale nejprve probereme rozdíl mezi silnými a slabými delegáty.

Silní delegáti a 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 silnou třídou pro každý delegovaný protokol v iOSu. IOS ale má také koncept slabého delegáta. Místo podtřídy třídy vázané na Objective-C protokol pro konkrétního delegáta vám iOS také umožňuje vytvořit vazbu metod protokolu sami v jakékoli třídě, která je odvozena z objektu NSObject, dekódovat metody exportAttribute a pak zadat příslušné selektory. Když použijete tento přístup, přiřadíte instanci třídy ke vlastnosti WeakDelegate místo vlastnosti Delegate. Slabý delegát nabízí flexibilitu, abyste mohli třídu delegáta snížit 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 chcete 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 vlastnosti MKMapView' Delegate . Protokol MKMapViewDelegate obsahuje pouze volitelné metody. Proto jsou všechny metody virtuální, které jsou vázané 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. Často Objective-Cuvidíte, že kontroler přijímá více protokolů přímo v rámci třídy. Vzhledem k tomu, že jsou však protokoly vázané 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 vlastnosti MKMapView's Delegate , jak je znázorněno tady:

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á pochází z NSObject a přiřadit ji vlastnosti WeakDelegate .MKMapView Vzhledem k tomu, že UIViewController třída je nakonec odvozena od NSObject (stejně jako každá Objective-C třída v CocoaTouch), můžeme jednoduše implementovat metodu vázané přímo mapView:didSelectAnnotationView: na kontroleru a přiřadit kontroleru k MKMapView's WeakDelegate, vyhnout se potřebě 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ým typem. 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. To však přichází na náklady na bezpečnost typů. Kdybyste udělali chybu v selektoru, který byl předán do ExportAttribute, nezjistit, dokud nechyběl modul runtime.

Události a delegáti

Delegáti se používají pro zpětné volání v iOSu podobně jako způsob, jakým .NET používá události. Pokud chcete, aby rozhraní API iOS a způsob jejich používání Objective-C delegátů 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 dřívější 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ěla by podtřídu EventArgs typu MKMapViewAnnotationEventsArgs. View Vlastnost MKMapViewAnnotationEventsArgs 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 tady:

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 zveřejňuje normální 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 silné i slabě napsané delegáty a jak svázat události .NET s metodami delegování.