Xamarin.iOS 中的事件、通訊協定和委派

Xamarin.iOS 會使用控件來公開大部分用戶互動的事件。 Xamarin.iOS 應用程式會以與傳統 .NET 應用程式相同的方式取用這些事件。 例如,Xamarin.iOS UIButton 類別具有名為 TouchUpInside 的事件,並取用此事件,就像這個類別和事件位於 .NET 應用程式中一樣。

除了這個 .NET 方法之外,Xamarin.iOS 也會公開另一個模型,可用於更複雜的互動和數據系結。 此方法會使用Apple所呼叫的委派和通訊協定。 委派在概念上與 C# 中的委派類似,但與其定義和呼叫單一方法,中的 Objective-C 委派是符合通訊協定的整個類別。 通訊協議類似於 C# 中的介面,不同之處在於其方法可以是選擇性的。 例如,若要使用數據填入UITableView,您會建立委派類別,以實作UITableViewDataSource通訊協定中定義的方法,UITableView會呼叫以填入本身。

在本文中,您將瞭解所有這些主題,為處理 Xamarin.iOS 中的回呼案例奠定堅實的基礎,包括:

  • 事件 – 搭配 UIKit 控制項使用 .NET 事件。
  • 通訊協定 – 瞭解哪些通訊協定 及其使用方式,以及建立提供地圖批注數據的範例。
  • 委派 – 藉Objective-C由擴充對應範例來處理包含批注的用戶互動,然後學習強式和弱式委派之間的差異,以及何時使用這些委派來瞭解委派。

為了說明通訊協定和委派,我們將建置簡單的地圖應用程式,將註釋新增至地圖,如下所示:

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

處理此應用程式之前,讓我們先查看UIKit下的 .NET 事件。

使用UIKit的 .NET 事件

Xamarin.iOS 會在 UIKit 控制件上公開 .NET 事件。 例如,UIButton 有 TouchUpInside 事件,您可以像在 .NET 中一樣處理,如下列使用 C# Lambda 表達式的程式代碼所示:

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

您也可以使用 C# 2.0 樣式的匿名方法來實作,如下所示:

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

上述程式代碼會以UIViewController的方法連接 ViewDidLoad 起來。 aButton變數會參考按鈕,您可以在 Xcode 介面產生器或程式代碼中加入該按鈕。

Xamarin.iOS 也支援將程式代碼連線到與控件互動的目標動作樣式。

如需 iOS 目標動作模式的詳細資訊,請參閱 Apple iOS 開發人員連結庫中 iOS 核心應用程式專長認證的目標動作一節

如需詳細資訊,請參閱 使用 Xcode 設計使用者介面。

事件

如果您想要攔截來自 UIControl 的事件,您有一系列選項:從使用 C# Lambda 和委派函式到使用低階 Objective-C API。

下一節說明如何根據您需要多少控件,擷取按鈕上的 TouchDown 事件。

C# 樣式

使用委派語法:

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

如果您改為喜歡 Lambda:

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

如果您要有多個按鈕使用相同的處理程式來共用相同的程式代碼:

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

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

監視一種以上的事件

UIControlEvent 旗標的 C# 事件具有與個別旗標的一對一對應。 當您想要讓相同的程式代碼片段處理兩個或多個事件時,請使用 UIControl.AddTarget 方法:

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

使用 Lambda 語法:

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

如果您需要使用的低階功能 Objective-C,例如連接到特定物件實例並叫用特定選取器:

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

// In some other place:

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

請注意,如果您在繼承的基類中實作實例方法,它必須是公用方法。

通訊協定

通訊協定是 Objective-C 提供方法宣告清單的語言功能。 它與 C# 中的介面類似,主要差異在於通訊協定可以有選擇性方法。 如果採用通訊協議的類別未實作這些方法,則不會呼叫選擇性方法。 此外,中的 Objective-C 單一類別可以實作多個通訊協定,就像 C# 類別可以實作多個介面一樣。

Apple 會在整個 iOS 中使用通訊協議來定義要採用的類別合約,同時從呼叫端擷取實作類別,因此就像 C# 介面一樣運作。 通訊協議同時用於非委派案例中(例如 MKAnnotation 下一個所示的範例),以及委派(如本檔稍後的一節所示)。

使用 Xamarin.ios 的通訊協定

讓我們看看使用來自 Xamarin.iOS 的通訊協定的 Objective-C 範例。 在此範例中,我們將使用 MKAnnotation 屬於架構一部分的 MapKit 通訊協定。 MKAnnotation 是一種通訊協議,允許採用它的任何物件提供可新增至地圖之批註的相關信息。 例如,實作 MKAnnotation 的物件會提供註釋的位置和與其相關聯的標題。

如此一來,通訊 MKAnnotation 協定會用來提供附注的相關數據。 註釋本身的實際檢視是從採用 MKAnnotation 通訊協定之 對象中的數據所建置。 例如,當使用者點選批注時出現的圖說文字(如以下螢幕快照所示)來自 Title 實作通訊協定之類別中的 屬性:

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

如下一節所述, 通訊協定深入探討,Xamarin.iOS 會將通訊協議系結至抽象類。 針對通訊 MKAnnotation 協定,系結的 C# 類別會命名 MKAnnotation 為模擬通訊協議的名稱,而它是 的子類別 NSObject,是 CocoaTouch 的根基類。 通訊協定需要實作座標的 getter 和 setter;不過,標題和副標題是選擇性的。 因此,在類別中MKAnnotationCoordinate屬性是抽象的,需要實作屬性,且 和 SubtitleTitle 屬性會標示為虛擬,使其成為選擇性專案,如下所示:

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

只要至少Coordinate實作 屬性,任何類別都可以藉由衍生自 MKAnnotation來提供批注數據。 例如,以下是採用建構函式中座標的範例類別,並傳回標題的字串:

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

透過系結至的通訊協定,子類別 MKAnnotation 的任何類別都可以提供對應在建立註釋檢視時所使用的相關數據。 若要將註釋新增至對應,只要呼叫 AddAnnotation 實例的 MKMapView 方法,如下列程式代碼所示:

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

這裏的對應變數是 的 MKMapView實例,這是代表對應本身的類別。 MKMapView會使用Coordinate衍生自 SampleMapAnnotation 實例的數據,在地圖上放置註釋檢視。

通訊 MKAnnotation 協議會針對任何實作它的物件提供一組已知的功能,而不需要取用者(在此案例中為對應)需要知道實作詳細數據。 這樣可簡化將各種可能註釋新增至地圖。

通訊協定深入探討

由於 C# 介面不支援選擇性方法,因此 Xamarin.iOS 會將通訊協議對應至抽象類。 因此,採用中的 Objective-C 通訊協定是在 Xamarin.iOS 中完成,方法是衍生自系結至通訊協定的抽象類,並實作必要的方法。 這些方法將會公開為 類別中的抽象方法。 來自通訊協議的選擇性方法將會系結至 C# 類別的虛擬方法。

例如,以下是 Xamarin.iOS 中系結的通訊協定的 UITableViewDataSource 一部分:

public abstract class UITableViewDataSource : NSObject
{
    [Export ("tableView:cellForRowAtIndexPath:")]
    public abstract UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath);
    [Export ("numberOfSectionsInTableView:")]
    public virtual int NumberOfSections (UITableView tableView){...}
...
}

請注意,類別是抽象的。 Xamarin.iOS 讓類別抽象化,以支援通訊協定中的選擇性/必要方法。 不過,不同於 Objective-C 通訊協定(或 C# 介面),C# 類別不支援多重繼承。 這會影響使用通訊協定的 C# 程式代碼設計,通常會導致巢狀類別。 本檔稍後的一節將討論此問題。

GetCell(…) 是系結至 Objective-C選取器的抽象方法, tableView:cellForRowAtIndexPath:這是通訊協定的必要方法 UITableViewDataSource 。 選取器是 Objective-C 方法名稱的詞彙。 若要視需要強制執行 方法,Xamarin.iOS 會將它宣告為抽象。 另一個 方法 NumberOfSections(…)會繫結至 numberOfSectionsInTableview:。 這個方法在通訊協定中是選擇性的,因此 Xamarin.iOS 會將它宣告為虛擬,使其成為在 C# 中覆寫的選擇性方法。

Xamarin.iOS 會為您處理所有 iOS 系結。 不過,如果您曾經需要手動系結通訊協定 Objective-C ,您可以使用 裝飾類別 ExportAttribute來執行此動作。 這是 Xamarin.iOS 本身所使用的相同方法。

如需如何在 Xamarin.iOS 中系結類型的詳細資訊,請參閱系Objective-C結Objective-C類型一文

不過,我們還沒有通過通訊協定。 它們也會用於 iOS 作為委派的基礎 Objective-C ,這是下一節的主題。

委派

iOS 會使用 Objective-C 委派來實作委派模式,其中一個對象會傳遞至另一個物件。 執行工作的物件是第一個物件的委派。 物件會藉由在發生某些事情之後傳送訊息,告訴其委派執行工作。 在中 Objective-C 傳送這類訊息的功能相當於在 C# 中呼叫方法。 委派會實作方法以回應這些呼叫,因此會提供應用程式的功能。

委派可讓您擴充類別的行為,而不需要建立子類別。 iOS 中的應用程式通常會在發生重要動作之後,於某個類別回呼至另一個類別時,使用委派。 例如,當用戶 MKMapView 點選地圖上的註釋時,類別會回呼其委派,讓委派類別的作者有機會在應用程式內回應。 您可以在本文稍後的<使用委派與 Xamarin.iOS 的範例>中,逐步解說這種類型的委派使用方式。

此時,您可能想知道類別如何決定在其委派上呼叫的方法。 這是您使用通訊協定的另一個位置。 通常,委派可用的方法來自其採用的通訊協定。

如何搭配委派使用通訊協定

我們先前已瞭解如何使用通訊協議來支援將註釋新增至地圖。 通訊協定也可用來提供一組已知的方法,讓類別在特定事件發生後呼叫,例如用戶點選地圖上的註釋或選取數據表中的數據格之後。 實作這些方法的類別稱為呼叫這些方法的類別委派。

支援委派的類別會藉由公開委派屬性,將實作委派的類別指派給該屬性。 您為委派實作的方法將取決於特定委派採用的通訊協定。 UITableView針對 方法,您可以針對 UIAccelerometer 方法實UITableViewDelegate作通訊協議,UIAccelerometerDelegate針對要公開委派的 iOS 中任何其他類別實作 ,依此實作 。

MKMapView我們在先前範例中看到的 類別也有名為 Delegate 的屬性,它會在發生各種事件之後呼叫。 的 [委派 MKMapView ] 類型為 MKMapViewDelegate。 在選取註釋之後,您將會在範例中使用此項目來回應批註,但首先讓我們討論強式和弱式委派之間的差異。

強委派與弱式委派

到目前為止,我們探討的委派是強委派,這表示它們具有強型別。 Xamarin.iOS 系結會隨附 iOS 中每個委派通訊協議的強型別類別。 不過,iOS 也有弱委派的概念。 iOS 也可讓您選擇將衍生自 NSObject 的任何類別中,自行系結至 Objective-C 特定委派之通訊協議的類別,而是使用 ExportAttribute 裝飾您的方法,然後提供適當的選取器。 當您採用此方法時,會將類別的實例指派給 WeakDelegate 屬性,而不是指派給 Delegate 屬性。 弱式委派可讓您彈性地將委派類別帶下不同的繼承階層。 讓我們看看使用強式和弱式委派的 Xamarin.iOS 範例。

搭配 Xamarin.iOS 使用委派的範例

若要執行程式代碼以回應用戶點選範例中的註釋,我們可以子類別 MKMapViewDelegate 並將實例指派給 MKMapViewDelegate 屬性。 通訊 MKMapViewDelegate 協定只包含選擇性方法。 因此,所有方法都是系結至 Xamarin.iOS MKMapViewDelegate 類別中這個通訊協定的虛擬方法。 當用戶選取註釋時, MKMapView 實例會將訊息傳送 mapView:didSelectAnnotationView: 給其委派。 若要在 Xamarin.iOS 中處理這個問題,我們需要覆寫 DidSelectAnnotationView (MKMapView mapView, MKAnnotationView annotationView) MKMapViewDelegate 子類別中的 方法,如下所示:

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 類別會實作為包含 MKMapView 實例的控制器中的巢狀類別。 在 中 Objective-C,您通常會看到控制器直接在 類別內採用多個通訊協定。 不過,由於通訊協議系結至 Xamarin.iOS 中的類別,實作強型別委派的類別通常會包含為巢狀類別。

有了委派類別實作,您只需要具現化控制器中的委派實例,並將它指派給 MKMapViewDelegate 屬性,如下所示:

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

若要使用弱式委派來完成相同的工作,您必須在衍生自 NSObject 的任何類別中自行系結 方法,並將其指派給 WeakDelegateMKMapView屬性。 由於類別 UIViewController 最終衍生自 NSObject (就像 CocoaTouch 中的每個 Objective-C 類別一樣),因此我們可以直接實作系結至 mapView:didSelectAnnotationView: 控制器的方法,並將控制器指派給 MKMapViewWeakDelegate,以避免需要額外的巢狀類別。 下列程式代碼示範此方法:

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

執行此程式代碼時,應用程式的行為與執行強型別委派版本時的行為完全相同。 此程式代碼的優點是弱式委派不需要建立當我們使用強型別委派時所建立的額外類別。 不過,這以犧牲型別安全為代價。 如果您要在傳遞至 ExportAttribute的選取器中犯錯誤,則直到運行時間才會發現。

事件和委派

委派用於 iOS 中的回呼,類似於 .NET 使用事件的方式。 若要讓 iOS API 及其使用 Objective-C 委派的方式看起來更像 .NET,Xamarin.iOS 會在 iOS 中使用委派的許多位置公開 .NET 事件。

例如,使用 .NET 事件,在 Xamarin.iOS 中也可以實作回應所選註釋的先前 MKMapViewDelegate 實作。 在這裡情況下,事件會在 中 MKMapView 定義,並呼叫 DidSelectAnnotationView。 其子類別的類型為 EventArgsMKMapViewAnnotationEventsArgs。 的 ViewMKMapViewAnnotationEventsArgs 屬性會提供批注檢視的參考,您可以從該檢視繼續進行先前的相同實作,如下所示:

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

摘要

本文涵蓋如何在 Xamarin.iOS 中使用事件、通訊協定和委派。 我們已瞭解 Xamarin.iOS 如何公開控件的一般 .NET 樣式事件。 接下來,我們已了解通訊 Objective-C 協定,包括它們與 C# 介面的不同,以及 Xamarin.iOS 如何使用它們。 最後,我們從 Xamarin.iOS 的觀點檢查 Objective-C 委派。 我們已瞭解 Xamarin.iOS 如何同時支持強型別和弱型別委派,以及如何將 .NET 事件系結至委派方法。