Zdarzenia, protokoły i delegaty w środowisku Xamarin.iOS
Platforma Xamarin.iOS używa kontrolek do uwidaczniania zdarzeń dla większości interakcji użytkownika. Aplikacje platformy Xamarin.iOS używają tych zdarzeń w taki sam sposób, jak w przypadku tradycyjnych aplikacji platformy .NET. Na przykład klasa Xamarin.iOS UIButton ma zdarzenie o nazwie TouchUpInside i korzysta z tego zdarzenia tak, jakby ta klasa i zdarzenie znajdowało się w aplikacji platformy .NET.
Oprócz tego podejścia platformy .NET platforma Xamarin.iOS uwidacznia inny model, który może służyć do bardziej złożonej interakcji i powiązania danych. Ta metodologia używa funkcji wywoływanych przez firmę Apple delegatów i protokołów. Delegaty są podobne do delegatów w języku C#, ale zamiast definiować i wywoływać jedną metodę, delegat w Objective-C systemie jest całą klasą zgodną z protokołem. Protokół jest podobny do interfejsu w języku C#, z tą różnicą, że jego metody mogą być opcjonalne. Aby na przykład wypełnić element UITableView danymi, należy utworzyć klasę delegata, która implementuje metody zdefiniowane w protokole UITableViewDataSource, który wywoła obiekt UITableView, aby wypełnić się.
W tym artykule znajdziesz informacje na temat wszystkich tych tematów, zapewniając solidną podstawę do obsługi scenariuszy wywołania zwrotnego na platformie Xamarin.iOS, w tym:
- Zdarzenia — używanie zdarzeń platformy .NET z kontrolkami UIKit.
- Protokoły — Edukacja, jakie protokoły są i jak są używane, oraz tworzenie przykładu zawierającego dane adnotacji mapy.
- Delegaci — Edukacja na temat Objective-C delegatów, rozszerzając przykład mapy w celu obsługi interakcji użytkownika, która zawiera adnotację, a następnie poznając różnicę między silnymi i słabymi delegatami i kiedy należy ich używać.
Aby zilustrować protokoły i delegaty, utworzymy prostą aplikację mapy, która dodaje adnotację do mapy, jak pokazano poniżej:
Przed rozpoczęciem pracy z tą aplikacją przyjrzyjmy się zdarzeń platformy .NET w zestawie UIKit.
Zdarzenia platformy .NET z zestawem UIKit
Środowisko Xamarin.iOS uwidacznia zdarzenia platformy .NET w kontrolkach UIKit. Na przykład funkcja UIButton ma zdarzenie TouchUpInside, które jest obsługiwane w zwykły sposób na platformie .NET, jak pokazano w poniższym kodzie używającym wyrażenia lambda języka C#:
aButton.TouchUpInside += (o,s) => {
Console.WriteLine("button touched");
};
Można to również zaimplementować za pomocą metody anonimowej w stylu C# 2.0, takiej jak ta:
aButton.TouchUpInside += delegate {
Console.WriteLine ("button touched");
};
Powyższy kod jest podłączony do ViewDidLoad
metody UIViewController. Zmienna aButton
odwołuje się do przycisku, który można dodać w narzędziu Xcode Interface Builder lub z kodem.
Platforma Xamarin.iOS obsługuje również styl akcji docelowej łączący kod z interakcją, która występuje z kontrolką.
Aby uzyskać więcej informacji na temat wzorca akcji docelowej systemu iOS, zobacz sekcję Target-Action ( Akcja docelowa) w sekcji Core Application Competencies for iOS for iOS (Podstawowe kompetencje aplikacji dla systemu iOS) w bibliotece deweloperów systemu iOS firmy Apple.
Aby uzyskać więcej informacji, zobacz Projektowanie interfejsów użytkownika za pomocą programu Xcode.
Zdarzenia
Jeśli chcesz przechwycić zdarzenia z interfejsu UIControl, masz szereg opcji: od używania lambdów języka C# i delegowania funkcji do korzystania z interfejsów API niskiego poziomu Objective-C .
W poniższej sekcji pokazano, jak przechwycić zdarzenie TouchDown na przycisku, w zależności od tego, ile kontrolki potrzebujesz.
Styl języka C#
Przy użyciu składni delegata:
UIButton button = MakeTheButton ();
button.TouchDown += delegate {
Console.WriteLine ("Touched");
};
Jeśli zamiast tego lubisz lambdy:
button.TouchDown += () => {
Console.WriteLine ("Touched");
};
Jeśli chcesz mieć wiele przycisków, użyj tej samej procedury obsługi, aby udostępnić ten sam kod:
void handler (object sender, EventArgs args)
{
if (sender == button1)
Console.WriteLine ("button1");
else
Console.WriteLine ("some other button");
}
button1.TouchDown += handler;
button2.TouchDown += handler;
Monitorowanie więcej niż jednego rodzaju zdarzenia
Zdarzenia języka C# dla flag UIControlEvent mają mapowanie "jeden do jednego" do poszczególnych flag. Jeśli chcesz, aby ten sam fragment kodu obsługiwał co najmniej dwa zdarzenia, użyj UIControl.AddTarget
metody :
button.AddTarget (handler, UIControlEvent.TouchDown | UIControlEvent.TouchCancel);
Przy użyciu składni lambda:
button.AddTarget ((sender, event)=> Console.WriteLine ("An event happened"), UIControlEvent.TouchDown | UIControlEvent.TouchCancel);
Jeśli musisz użyć funkcji Objective-Cniskiego poziomu , takich jak podłączanie do określonego wystąpienia obiektu i wywoływanie określonego selektora:
[Export ("MySelector")]
void MyObjectiveCHandler ()
{
Console.WriteLine ("Hello!");
}
// In some other place:
button.AddTarget (this, new Selector ("MySelector"), UIControlEvent.TouchDown);
Należy pamiętać, że jeśli implementujesz metodę wystąpienia w dziedziczonej klasie bazowej, musi to być metoda publiczna.
Protokoły
Protokół to Objective-C funkcja języka, która udostępnia listę deklaracji metod. Służy on do podobnego celu do interfejsu w języku C#, główną różnicą jest to, że protokół może mieć opcjonalne metody. Metody opcjonalne nie są wywoływane, jeśli klasa, która przyjmuje protokół, nie implementuje ich. Ponadto pojedyncza klasa w Objective-C programie może implementować wiele protokołów, podobnie jak klasa języka C# może implementować wiele interfejsów.
Firma Apple używa protokołów w całym systemie iOS do definiowania kontraktów dla klas do wdrożenia, jednocześnie abstrakcjąc klasę implementającą z obiektu wywołującego, tak aby działały tak samo jak interfejs języka C#. Protokoły są używane zarówno w scenariuszach niedelegacyjnych (na przykład w przykładzie MKAnnotation
pokazanym obok), jak i delegatów (jak przedstawiono w dalszej części tego dokumentu, w sekcji Delegaty).
Protokoły z platformą Xamarin.ios
Przyjrzyjmy się przykładowi przy użyciu Objective-C protokołu z platformy Xamarin.iOS. W tym przykładzie MKAnnotation
użyjemy protokołu, który jest częścią MapKit
struktury. MKAnnotation
to protokół, który umożliwia każdemu obiektowi, który go przyjmuje w celu udostępnienia informacji na temat adnotacji, które można dodać do mapy. Na przykład implementacja MKAnnotation
obiektu udostępnia lokalizację adnotacji i skojarzony z nim tytuł.
W ten sposób MKAnnotation
protokół jest używany do dostarczania istotnych danych, które towarzyszą adnotacji. Rzeczywisty widok samej adnotacji jest tworzony na podstawie danych w obiekcie, który przyjmuje MKAnnotation
protokół. Na przykład tekst objaśnienia, który pojawia się, gdy użytkownik naciśnie adnotację (jak pokazano na poniższym zrzucie ekranu) pochodzi z Title
właściwości w klasie implementujące protokół:
Zgodnie z opisem w następnej sekcji protokoły Szczegółowe omówienie, Xamarin.iOS wiąże protokoły z klasami abstrakcyjnymi. MKAnnotation
Dla protokołu powiązana klasa języka C# nosi nazwę MKAnnotation
, aby naśladować nazwę protokołu i jest podklasą NSObject
, główną klasą bazową dla cocoaTouch. Protokół wymaga implementacji elementu getter i ustawiania dla współrzędnych; jednak tytuł i podtytuł są opcjonalne. W związku z tym MKAnnotation
w klasie Coordinate
właściwość jest abstrakcyjna, wymagając jej zaimplementowania, a Title
właściwości i Subtitle
są oznaczone jako wirtualne, co czyni je opcjonalnymi, jak pokazano poniżej:
[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 ();
}
}
...
}
Każda klasa może dostarczać dane adnotacji, po prostu wyprowadzając z MKAnnotation
klasy , o ile co najmniej Coordinate
właściwość jest implementowana. Oto przykładowa klasa, która przyjmuje współrzędną w konstruktorze i zwraca ciąg tytułu:
/// <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;
}
}
}
Za pośrednictwem protokołu, z jakim jest powiązany, każda klasa, która podklasy MKAnnotation
może dostarczyć odpowiednie dane, które będą używane przez mapę podczas tworzenia widoku adnotacji. Aby dodać adnotację do mapy, po prostu wywołaj AddAnnotation
metodę MKMapView
wystąpienia, jak pokazano w poniższym kodzie:
//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));
Zmienna mapy jest tutaj wystąpieniem MKMapView
klasy , która reprezentuje samą mapę. Użyje MKMapView
Coordinate
danych pochodzących z SampleMapAnnotation
wystąpienia, aby ustawić widok adnotacji na mapie.
Protokół MKAnnotation
udostępnia znany zestaw możliwości we wszystkich obiektach, które go implementują, bez konsumenta (w tym przypadku mapy) wymagającego znajomości szczegółów implementacji. Usprawnia to dodawanie różnych możliwych adnotacji do mapy.
Szczegółowe omówienie protokołów
Ponieważ interfejsy języka C# nie obsługują opcjonalnych metod, platforma Xamarin.iOS mapuje protokoły na klasy abstrakcyjne. W związku z tym wdrożenie protokołu w Objective-C programie jest realizowane w środowisku Xamarin.iOS przez wyprowadzenie z klasy abstrakcyjnej powiązanej z protokołem i implementowanie wymaganych metod. Te metody zostaną uwidocznione jako metody abstrakcyjne w klasie. Opcjonalne metody z protokołu będą powiązane z metodami wirtualnymi klasy C#.
Na przykład poniżej znajduje się część UITableViewDataSource
protokołu powiązana z platformą 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){...}
...
}
Należy pamiętać, że klasa jest abstrakcyjna. Platforma Xamarin.iOS sprawia, że klasa jest abstrakcyjna do obsługi opcjonalnych/wymaganych metod w protokołach. Jednak w przeciwieństwie do Objective-C protokołów (lub interfejsów języka C#) klasy języka C# nie obsługują wielu dziedziczenia. Ma to wpływ na projekt kodu w języku C#, który używa protokołów, i zwykle prowadzi do zagnieżdżonych klas. Więcej informacji na temat tego problemu opisano w dalszej części tego dokumentu w sekcji Delegaci.
GetCell(…)
to metoda abstrakcyjna powiązana z Objective-Cselektorem , tableView:cellForRowAtIndexPath:
która jest wymaganą metodą UITableViewDataSource
protokołu. Selektor to Objective-C termin nazwy metody. Aby wymusić metodę zgodnie z wymaganiami, platforma Xamarin.iOS deklaruje ją jako abstrakcyjną. Druga metoda, NumberOfSections(…)
, jest powiązana z numberOfSectionsInTableview:
. Ta metoda jest opcjonalna w protokole, więc platforma Xamarin.iOS deklaruje ją jako wirtualną, co czyni ją opcjonalną do zastąpienia w języku C#.
Środowisko Xamarin.iOS zajmuje się wszystkimi powiązaniami systemu iOS. Jeśli jednak kiedykolwiek musisz powiązać protokół z Objective-C ręcznie, możesz to zrobić, dekorując klasę za pomocą klasy ExportAttribute
. Jest to ta sama metoda używana przez samą platformę Xamarin.iOS.
Aby uzyskać więcej informacji na temat tworzenia powiązań Objective-C typów na platformie Xamarin.iOS, zobacz artykuł Typy powiązańObjective-C.
Nie korzystamy jeszcze z protokołów. Są one również używane w systemie iOS jako podstawy dla Objective-C delegatów, który jest tematem następnej sekcji.
Delegaci
System iOS używa Objective-C delegatów do implementowania wzorca delegowania, w którym jeden obiekt przechodzi pracę do innego. Obiekt wykonujący pracę jest delegatem pierwszego obiektu. Obiekt nakazuje delegatowi pracę, wysyłając komunikaty po wystąpieniu pewnych rzeczy. Wysyłanie komunikatu takiego w pliku Objective-C jest funkcjonalnie równoważne wywołaniu metody w języku C#. Delegat implementuje metody w odpowiedzi na te wywołania, a więc zapewnia funkcjonalność aplikacji.
Delegaci umożliwiają rozszerzanie zachowania klas bez konieczności tworzenia podklas. Aplikacje w systemie iOS często używają delegatów, gdy jedna klasa wywołuje z powrotem do innej po wystąpieniu ważnej akcji. Na przykład MKMapView
klasa wywołuje delegata, gdy użytkownik naciśnie adnotację na mapie, dając autorowi klasy delegata możliwość odpowiedzi w aplikacji. W dalszej części tego artykułu możesz zapoznać się z przykładem użycia delegata tego typu, w temacie Example Using a Delegate with Xamarin.iOS (Przykład używanie delegata z platformą Xamarin.iOS).
W tym momencie możesz się zastanawiać, w jaki sposób klasa określa, jakie metody wywołać na jego delegatu. Jest to inne miejsce, w którym są używane protokoły. Zazwyczaj metody dostępne dla delegata pochodzą z wdrażanych protokołów.
Jak protokoły są używane z delegatami
Widzieliśmy wcześniej, jak protokoły są używane do obsługi dodawania adnotacji do mapy. Protokoły są również używane do udostępniania znanego zestawu metod wywoływania klas po wystąpieniu niektórych zdarzeń, takich jak po naciśnięciu adnotacji przez użytkownika na mapie lub wybraniu komórki w tabeli. Klasy implementujące te metody są nazywane delegatami klas, które je nazywają.
Klasy, które obsługują delegowanie, wykonują to przez uwidacznianie właściwości Delegat, do której jest przypisywana klasa implementująca delegata. Metody zaimplementowane dla delegata będą zależeć od protokołu, który dany delegat przyjmuje. W przypadku UITableView
metody należy zaimplementować UITableViewDelegate
UIAccelerometerDelegate
protokół dla UIAccelerometer
metody , a tak dalej dla innych klas w systemie iOS, dla których chcesz uwidocznić delegata.
Klasa MKMapView
, którą widzieliśmy we wcześniejszym przykładzie, ma również właściwość o nazwie Delegate, która będzie wywoływana po wystąpieniu różnych zdarzeń. Delegat dla MKMapView
jest typu MKMapViewDelegate
.
Użyjesz tego wkrótce w przykładzie, aby odpowiedzieć na adnotację po jej wybraniu, ale najpierw omówimy różnicę między silnymi i słabymi delegatami.
Silni delegaci a słabi delegaci
Delegaci, na których przyjrzeliśmy się do tej pory, są silnymi delegatami, co oznacza, że są silnie typizowane. Powiązania platformy Xamarin.iOS są dostarczane z silnie typizowanym typem klasy dla każdego protokołu delegata w systemie iOS. Jednak system iOS ma również koncepcję słabego delegata. Zamiast podklasować klasę powiązaną z Objective-C protokołem dla określonego delegata, system iOS umożliwia również samodzielne powiązanie metod protokołu w dowolnej klasie, która pochodzi z klasy NSObject, dekorując metody za pomocą atrybutu ExportAttribute, a następnie podając odpowiednie selektory. W przypadku tego podejścia przypiszesz wystąpienie klasy do właściwości WeakDelegate zamiast do właściwości Delegate. Słaby delegat zapewnia elastyczność przechodzenia do klasy delegata w dół innej hierarchii dziedziczenia. Przyjrzyjmy się przykładowi platformy Xamarin.iOS, który używa zarówno silnych, jak i słabych delegatów.
Przykład użycia delegata z platformą Xamarin.iOS
Aby wykonać kod w odpowiedzi na naciśnięcie adnotacji przez użytkownika w naszym przykładzie, możemy podklasy MKMapViewDelegate
i przypisać wystąpienie do MKMapView
właściwości .s Delegate
. Protokół MKMapViewDelegate
zawiera tylko metody opcjonalne.
W związku z tym wszystkie metody są wirtualne, które są powiązane z tym protokołem w klasie Xamarin.iOS MKMapViewDelegate
. Gdy użytkownik wybierze adnotację, MKMapView
wystąpienie wyśle komunikat do swojego delegata mapView:didSelectAnnotationView:
. Aby to obsłużyć w środowisku Xamarin.iOS, musimy zastąpić metodę DidSelectAnnotationView (MKMapView mapView, MKAnnotationView annotationView)
w podklasie MKMapViewDelegate w następujący sposób:
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);
}
}
}
Klasa SampleMapDelegate pokazana powyżej jest implementowana jako zagnieżdżona klasa w kontrolerze zawierającym MKMapView
wystąpienie. W Objective-Csystemie często kontroler przyjmuje wiele protokołów bezpośrednio w klasie . Jednak ponieważ protokoły są powiązane z klasami w środowisku Xamarin.iOS, klasy implementujące silnie typizowane delegaty są zwykle uwzględniane jako klasy zagnieżdżone.
Po wdrożeniu implementacji klasy delegata wystarczy utworzyć wystąpienie delegata w kontrolerze i przypisać je do MKMapView
właściwości "s Delegate
", jak pokazano poniżej:
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
{
...
}
}
Aby użyć słabego delegata, aby wykonać to samo, musisz powiązać metodę samodzielnie w dowolnej klasie, która pochodzi z NSObject
klasy i przypisać ją do WeakDelegate
właściwości MKMapView
. UIViewController
Ponieważ klasa ostatecznie pochodzi z NSObject
(podobnie jak każda Objective-C klasa w cocoaTouch), możemy po prostu zaimplementować metodę powiązaną mapView:didSelectAnnotationView:
bezpośrednio z kontrolerem i przypisać kontroler do MKMapView
WeakDelegate
klasy , unikając potrzeby dodatkowej klasy zagnieżdżonej. Poniższy kod przedstawia następujące podejście:
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)
{
...
}
}
Podczas uruchamiania tego kodu aplikacja zachowuje się dokładnie tak, jak w przypadku uruchamiania silnie typizowanej wersji delegata. Zaletą tego kodu jest to, że słaby delegat nie wymaga utworzenia dodatkowej klasy, która została utworzona podczas korzystania z silnie typizowanego delegata. Jednak wiąże się to z kosztem bezpieczeństwa typu. Jeśli popełnisz błąd w selektorze ExportAttribute
, który został przekazany do elementu , nie dowiesz się, aż do czasu wykonania.
Zdarzenia i delegaty
Delegaty są używane do wywołania zwrotnego w systemie iOS podobnie jak w przypadku korzystania ze zdarzeń platformy .NET. Aby interfejsy API systemu iOS i sposób korzystania z Objective-C delegatów wyglądały bardziej jak .NET, platforma Xamarin.iOS uwidacznia zdarzenia platformy .NET w wielu miejscach, w których delegaty są używane w systemie iOS.
Na przykład wcześniejsza implementacja, w której MKMapViewDelegate
odpowiedź na wybraną adnotację może być również zaimplementowana w środowisku Xamarin.iOS przy użyciu zdarzenia platformy .NET. W takim przypadku zdarzenie zostanie zdefiniowane w MKMapView
pliku i o nazwie DidSelectAnnotationView
. Ma ona podklasę EventArgs
typu MKMapViewAnnotationEventsArgs
. Właściwość View
polecenia MKMapViewAnnotationEventsArgs
daje odwołanie do widoku adnotacji, z którego można kontynuować tę samą implementację, którą wcześniej wykonano, jak pokazano poniżej:
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);
}
};
Podsumowanie
W tym artykule opisano sposób używania zdarzeń, protokołów i delegatów na platformie Xamarin.iOS. Zobaczyliśmy, jak platforma Xamarin.iOS uwidacznia normalne zdarzenia stylu platformy .NET dla kontrolek. Następnie dowiedzieliśmy się o Objective-C protokołach, w tym o tym, jak różnią się one od interfejsów języka C# i jak są używane przez platformę Xamarin.iOS. Na koniec zbadaliśmy Objective-C delegatów z perspektywy platformy Xamarin.iOS. Zobaczyliśmy, jak platforma Xamarin.iOS obsługuje delegatów silnie i słabo wpisanych oraz jak powiązać zdarzenia platformy .NET z metodami delegowanymi.