Bagikan melalui


Peristiwa, Protokol, dan Delegasi di Xamarin.iOS

Xamarin.iOS menggunakan kontrol untuk mengekspos peristiwa untuk sebagian besar interaksi pengguna. Aplikasi Xamarin.iOS mengonsumsi peristiwa ini dengan cara yang sama seperti aplikasi .NET tradisional. Misalnya, kelas UIButton Xamarin.iOS memiliki peristiwa yang disebut TouchUpInside dan menggunakan peristiwa ini seolah-olah kelas dan peristiwa ini berada di aplikasi .NET.

Selain pendekatan .NET ini, Xamarin.iOS mengekspos model lain yang dapat digunakan untuk interaksi dan pengikatan data yang lebih kompleks. Metodologi ini menggunakan panggilan Apple yang mendelegasikan dan protokol. Delegasi mirip dalam konsep untuk mendelegasikan dalam C#, tetapi alih-alih menentukan dan memanggil satu metode, delegasi masuk Objective-C adalah seluruh kelas yang sesuai dengan protokol. Protokol mirip dengan antarmuka di C#, kecuali bahwa metodenya dapat bersifat opsional. Jadi misalnya, untuk mengisi UITableView dengan data, Anda akan membuat kelas delegasi yang mengimplementasikan metode yang ditentukan dalam protokol UITableViewDataSource yang akan dipanggil UITableView untuk mengisi dirinya sendiri.

Dalam artikel ini Anda akan mempelajari semua topik ini, memberi Anda fondasi yang kuat untuk menangani skenario panggilan balik di Xamarin.iOS, termasuk:

  • Peristiwa – Menggunakan peristiwa .NET dengan kontrol UIKit.
  • Protokol – Pembelajaran protokol apa dan bagaimana protokol tersebut digunakan, dan membuat contoh yang menyediakan data untuk anotasi peta.
  • Delegasi – Pembelajaran tentang Objective-C delegasi dengan memperluas contoh peta untuk menangani interaksi pengguna yang mencakup anotasi, lalu mempelajari perbedaan antara delegasi yang kuat dan lemah dan kapan menggunakan masing-masing.

Untuk mengilustrasikan protokol dan delegasi, kita akan membuat aplikasi peta sederhana yang menambahkan anotasi ke peta seperti yang ditunjukkan di sini:

Contoh aplikasi peta sederhana yang menambahkan anotasi ke petaContoh anotasi yang ditambahkan ke peta

Sebelum menangani aplikasi ini, mari kita mulai dengan melihat peristiwa .NET di bawah UIKit.

Peristiwa .NET dengan UIKit

Xamarin.iOS mengekspos peristiwa .NET pada kontrol UIKit. Misalnya, UIButton memiliki peristiwa TouchUpInside, yang Anda tangani seperti biasa di .NET, seperti yang ditunjukkan dalam kode berikut yang menggunakan ekspresi lambda C#:

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

Anda juga dapat menerapkan ini dengan metode anonim gaya C# 2.0 seperti ini:

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

Kode sebelumnya dikabeli dalam ViewDidLoad metode UIViewController. Variabel mereferensikan aButton tombol, yang dapat Anda tambahkan di Xcode Interface Builder atau dengan kode.

Xamarin.iOS juga mendukung gaya tindakan target untuk menghubungkan kode Anda ke interaksi yang terjadi dengan kontrol.

Untuk detail selengkapnya tentang pola tindakan target iOS, lihat bagian Tindakan Target dari Kompetensi Aplikasi Inti untuk iOS di Pustaka Pengembang iOS Apple.

Untuk informasi selengkapnya, lihat Mendesain antarmuka pengguna dengan Xcode.

Acara

Jika Anda ingin mencegat peristiwa dari UIControl, Anda memiliki berbagai opsi: dari menggunakan lambda C# dan mendelegasikan fungsi hingga menggunakan API tingkat Objective-C rendah.

Bagian berikut menunjukkan bagaimana Anda akan mengambil peristiwa TouchDown pada tombol, tergantung pada berapa banyak kontrol yang Anda butuhkan.

Gaya C#

Menggunakan sintaks delegasi:

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

Jika Anda menyukai lambda sebagai gantinya:

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

Jika Anda ingin memiliki beberapa tombol, gunakan handler yang sama untuk berbagi kode yang sama:

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

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

Memantau lebih dari satu jenis Peristiwa

Peristiwa C# untuk bendera UIControlEvent memiliki pemetaan satu-ke-satu ke bendera individual. Ketika Anda ingin memiliki bagian kode yang sama menangani dua peristiwa atau lebih, gunakan UIControl.AddTarget metode :

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

Menggunakan sintaks lambda:

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

Jika Anda perlu menggunakan fitur tingkat rendah , Objective-Cseperti menghubungkan ke instans objek tertentu dan memanggil pemilih tertentu:

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

// In some other place:

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

Harap dicatat, jika Anda menerapkan metode instans dalam kelas dasar yang diwariskan, metode tersebut harus berupa metode publik.

Protokol

Protokol adalah Objective-C fitur bahasa yang menyediakan daftar deklarasi metode. Ini melayani tujuan yang sama dengan antarmuka di C#, perbedaan utamanya adalah bahwa protokol dapat memiliki metode opsional. Metode opsional tidak dipanggil jika kelas yang mengadopsi protokol tidak menerapkannya. Selain itu, satu kelas di Objective-C dapat menerapkan beberapa protokol, sama seperti kelas C# dapat menerapkan beberapa antarmuka.

Apple menggunakan protokol di seluruh iOS untuk menentukan kontrak bagi kelas yang akan diadopsi, sambil mengabstraksi kelas penerapan dari pemanggil, sehingga beroperasi seperti antarmuka C#. Protokol digunakan baik dalam skenario non-delegasi (seperti dengan contoh yang ditunjukkan MKAnnotation berikutnya), dan dengan delegasi (seperti yang disajikan nanti dalam dokumen ini, di bagian Delegasi).

Protokol dengan Xamarin.ios

Mari kita lihat contoh menggunakan Objective-C protokol dari Xamarin.iOS. Untuk contoh ini, kita akan menggunakan MKAnnotation protokol, yang merupakan bagian MapKit dari kerangka kerja. MKAnnotation adalah protokol yang memungkinkan objek apa pun yang mengadopsinya untuk memberikan informasi tentang anotasi yang dapat ditambahkan ke peta. Misalnya, penerapan objek MKAnnotation menyediakan lokasi anotasi dan judul yang terkait dengannya.

Dengan cara ini, MKAnnotation protokol digunakan untuk memberikan data terkait yang menyertai anotasi. Tampilan aktual untuk anotasi itu sendiri dibangun dari data dalam objek yang mengadopsi MKAnnotation protokol. Misalnya, teks untuk callout yang muncul saat pengguna mengetuk anotasi (seperti yang ditunjukkan pada cuplikan layar di bawah) berasal dari Title properti di kelas yang menerapkan protokol:

Contoh teks untuk callout saat pengguna mengetuk anotasi

Seperti yang dijelaskan di bagian berikutnya, Protocols Deep Dive, Xamarin.iOS mengikat protokol ke kelas abstrak. MKAnnotation Untuk protokol, kelas C# terikat dinamai MKAnnotation untuk meniru nama protokol, dan itu adalah subkelas dari NSObject, kelas dasar akar untuk CocoaTouch. Protokol memerlukan getter dan setter untuk diimplementasikan untuk koordinat; namun, judul dan subtitel bersifat opsional. Oleh karena itu, di MKAnnotation kelas , Coordinate properti bersifat abstrak, mengharuskannya untuk diimplementasikan dan Title properti dan Subtitle ditandai virtual, menjadikannya opsional, seperti yang ditunjukkan di bawah ini:

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

Setiap kelas dapat memberikan data anotasi hanya dengan berasal dari MKAnnotation, selama setidaknya Coordinate properti diimplementasikan. Misalnya, berikut adalah kelas sampel yang mengambil koordinat di konstruktor dan mengembalikan string untuk judul:

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

Melalui protokol yang terikat, kelas apa pun yang subkelas MKAnnotation dapat menyediakan data relevan yang akan digunakan oleh peta saat membuat tampilan anotasi. Untuk menambahkan anotasi ke peta, cukup panggil AddAnnotation metode instans, seperti yang MKMapView ditunjukkan dalam kode berikut:

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

Variabel peta di sini adalah instans dari MKMapView, yang merupakan kelas yang mewakili peta itu sendiri. MKMapView akan menggunakan data yang Coordinate berasal dari SampleMapAnnotation instans untuk memosisikan tampilan anotasi di peta.

Protokol ini MKAnnotation menyediakan serangkaian kemampuan yang diketahui di semua objek yang mengimplementasikannya, tanpa konsumen (peta dalam hal ini) perlu mengetahui detail implementasi. Ini menyederhanakan penambahan berbagai anotasi yang mungkin ke peta.

Penyelaman Mendalam Protokol

Karena antarmuka C# tidak mendukung metode opsional, Xamarin.iOS memetakan protokol ke kelas abstrak. Oleh karena itu, mengadopsi protokol di Objective-C dicapai di Xamarin.iOS dengan berasal dari kelas abstrak yang terikat pada protokol dan menerapkan metode yang diperlukan. Metode ini akan diekspos sebagai metode abstrak di kelas . Metode opsional dari protokol akan terikat ke metode virtual kelas C#.

Misalnya, berikut adalah bagian dari protokol seperti yang UITableViewDataSource terikat di 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){...}
...
}

Perhatikan bahwa kelas ini abstrak. Xamarin.iOS membuat abstrak kelas untuk mendukung metode opsional/diperlukan dalam protokol. Namun, tidak seperti Objective-C protokol, (atau antarmuka C#), kelas C# tidak mendukung beberapa warisan. Ini memengaruhi desain kode C# yang menggunakan protokol, dan biasanya mengarah ke kelas berlapis. Selengkapnya tentang masalah ini dibahas nanti dalam dokumen ini, di bagian Delegasi.

GetCell(…)adalah metode abstrak, terikat ke pemilihObjective-C, tableView:cellForRowAtIndexPath:, yang merupakan metode protokol yang UITableViewDataSource diperlukan. Pemilih Objective-C adalah istilah untuk nama metode. Untuk menerapkan metode sesuai kebutuhan, Xamarin.iOS menyatakannya sebagai abstrak. Metode lain, NumberOfSections(…), terikat ke numberOfSectionsInTableview:. Metode ini bersifat opsional dalam protokol, sehingga Xamarin.iOS menyatakannya sebagai virtual, sehingga opsional untuk mengambil alih dalam C#.

Xamarin.iOS mengurus semua pengikatan iOS untuk Anda. Namun, jika Anda perlu mengikat protokol dari Objective-C secara manual, Anda dapat melakukannya dengan mendekorasi kelas dengan ExportAttribute. Ini adalah metode yang sama yang digunakan oleh Xamarin.iOS itu sendiri.

Untuk informasi selengkapnya tentang cara mengikat Objective-C jenis di Xamarin.iOS, lihat artikel Jenis PengikatanObjective-C.

Namun, kita belum melalui protokol. Mereka juga digunakan di iOS sebagai dasar untuk Objective-C delegasi, yang merupakan topik dari bagian berikutnya.

Delegasikan

iOS menggunakan Objective-C delegasi untuk menerapkan pola delegasi, di mana satu objek diteruskan bekerja ke objek lain. Objek yang melakukan pekerjaan adalah delegasi objek pertama. Objek memberi tahu delegasinya untuk melakukan pekerjaan dengan mengiriminya pesan setelah hal-hal tertentu terjadi. Mengirim pesan seperti ini secara Objective-C fungsional setara dengan memanggil metode di C#. Delegasi menerapkan metode sebagai respons terhadap panggilan ini, sehingga menyediakan fungsionalitas untuk aplikasi.

Delegasi memungkinkan Anda memperluas perilaku kelas tanpa perlu membuat subkelas. Aplikasi di iOS sering menggunakan delegasi ketika satu kelas memanggil kembali ke kelas lain setelah tindakan penting terjadi. Misalnya, MKMapView kelas memanggil kembali ke delegasinya ketika pengguna mengetuk anotasi di peta, memberi penulis kelas delegasi kesempatan untuk merespons dalam aplikasi. Anda dapat bekerja melalui contoh jenis penggunaan delegasi ini nanti dalam artikel ini, di Contoh Menggunakan Delegasi dengan Xamarin.iOS.

Pada titik ini, Anda mungkin bertanya-tanya bagaimana kelas menentukan metode apa yang akan dipanggil pada delegasinya. Ini adalah tempat lain di mana Anda menggunakan protokol. Biasanya, metode yang tersedia untuk delegasi berasal dari protokol yang mereka adopsi.

Bagaimana Protokol digunakan dengan Delegasi

Kami melihat sebelumnya bagaimana protokol digunakan untuk mendukung penambahan anotasi ke peta. Protokol juga digunakan untuk menyediakan sekumpulan metode yang diketahui untuk dipanggil kelas setelah peristiwa tertentu terjadi, seperti setelah pengguna mengetuk anotasi di peta atau memilih sel dalam tabel. Kelas yang menerapkan metode ini dikenal sebagai delegasi kelas yang memanggilnya.

Kelas yang mendukung delegasi melakukannya dengan mengekspos properti Delegasi, tempat kelas yang menerapkan delegasi ditetapkan. Metode yang Anda terapkan untuk delegasi akan bergantung pada protokol yang diadopsi delegasi tertentu. Untuk metode ini UITableView , Anda menerapkan UITableViewDelegate protokol, untuk metode , UIAccelerometer Anda akan menerapkan UIAccelerometerDelegate, dan sebagainya untuk kelas lain di seluruh iOS yang ingin Anda ekspos delegasikan.

Kelas MKMapView yang kami lihat dalam contoh kami sebelumnya juga memiliki properti yang disebut Delegasi, yang akan dipanggilnya setelah berbagai peristiwa terjadi. Delegasi untuk MKMapView berjenis MKMapViewDelegate. Anda akan menggunakan ini segera dalam contoh untuk menanggapi anotasi setelah dipilih, tetapi pertama-tama mari kita bahas perbedaan antara delegasi yang kuat dan lemah.

Delegasi Yang Kuat vs. Delegasi Lemah

Delegasi yang kita lihat sejauh ini adalah delegasi yang kuat, yang berarti mereka sangat di ketik. Pengikatan Xamarin.iOS dikirim dengan kelas yang sangat ditik untuk setiap protokol delegasi di iOS. Namun, iOS juga memiliki konsep delegasi yang lemah. Alih-alih mensubkelas kelas yang terikat ke Objective-C protokol untuk delegasi tertentu, iOS juga memungkinkan Anda memilih untuk mengikat metode protokol sendiri di kelas apa pun yang Anda sukai yang berasal dari NSObject, mendekorasi metode Anda dengan ExportAttribute, lalu menyediakan pemilih yang sesuai. Saat Anda mengambil pendekatan ini, Anda menetapkan instans kelas Anda ke properti WeakDelegate alih-alih ke properti Delegasi. Delegasi yang lemah menawarkan fleksibilitas untuk menurunkan kelas delegasi Anda ke hierarki warisan yang berbeda. Mari kita lihat contoh Xamarin.iOS yang menggunakan delegasi yang kuat dan lemah.

Contoh Menggunakan Delegasi dengan Xamarin.iOS

Untuk menjalankan kode sebagai respons terhadap pengguna yang mengetuk anotasi dalam contoh kami, kami dapat subkelas dan menetapkan instans MKMapViewDelegate ke MKMapViewproperti .Delegate Protokol MKMapViewDelegate hanya berisi metode opsional. Oleh karena itu, semua metode adalah virtual yang terikat dengan protokol ini di kelas Xamarin.iOS MKMapViewDelegate . Saat pengguna memilih anotasi, MKMapView instans akan mengirim pesan ke mapView:didSelectAnnotationView: delegasinya. Untuk menangani ini di Xamarin.iOS, kita perlu mengambil alih DidSelectAnnotationView (MKMapView mapView, MKAnnotationView annotationView) metode di subkelas MKMapViewDelegate seperti ini:

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

Kelas SampleMapDelegate yang ditunjukkan di atas diimplementasikan sebagai kelas berlapis di pengontrol yang berisi MKMapView instans. Dalam Objective-C, Anda akan sering melihat pengontrol mengadopsi beberapa protokol langsung dalam kelas. Namun, karena protokol terikat ke kelas di Xamarin.iOS, kelas yang menerapkan delegasi yang ditik dengan kuat biasanya disertakan sebagai kelas berlapis.

Dengan implementasi kelas delegasi di tempat, Anda hanya perlu membuat instans delegasi di pengontrol dan menetapkannya ke MKMapViewproperti seperti Delegate yang ditunjukkan di sini:

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

Untuk menggunakan delegasi yang lemah untuk mencapai hal yang sama, Anda perlu mengikat metode sendiri di kelas apa pun yang berasal dari NSObject dan menetapkannya ke WeakDelegate properti .MKMapView Karena kelas pada UIViewController akhirnya berasal dari NSObject (seperti setiap Objective-C kelas di CocoaTouch), kita hanya dapat menerapkan metode yang terikat langsung mapView:didSelectAnnotationView: di pengontrol dan menetapkan pengontrol ke MKMapView, WeakDelegatemenghindari kebutuhan akan kelas berlapis ekstra. Kode di bawah ini menunjukkan pendekatan ini:

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

Saat menjalankan kode ini, aplikasi berpura-pura persis seperti saat menjalankan versi delegasi yang sangat ditik. Manfaat dari kode ini adalah bahwa delegasi yang lemah tidak memerlukan pembuatan kelas tambahan yang dibuat ketika kami menggunakan delegasi yang di ketik dengan kuat. Namun, ini datang dengan mengorbankan keamanan jenis. Jika Anda membuat kesalahan di pemilih yang diteruskan ke ExportAttribute, Anda tidak akan mengetahuinya sampai runtime.

Peristiwa dan Delegasi

Delegasi digunakan untuk panggilan balik di iOS yang mirip dengan cara .NET menggunakan peristiwa. Untuk membuat API iOS dan cara mereka menggunakan Objective-C delegasi tampak lebih seperti .NET, Xamarin.iOS mengekspos peristiwa .NET di banyak tempat di mana delegasi digunakan di iOS.

Misalnya, implementasi sebelumnya di mana MKMapViewDelegate merespons anotasi yang dipilih juga dapat diimplementasikan di Xamarin.iOS dengan menggunakan peristiwa .NET. Dalam hal ini, peristiwa akan didefinisikan dalam MKMapView dan disebut DidSelectAnnotationView. Ini akan memiliki EventArgs subkelas jenis MKMapViewAnnotationEventsArgs. Properti ViewMKMapViewAnnotationEventsArgs akan memberi Anda referensi ke tampilan anotasi, dari mana Anda dapat melanjutkan dengan implementasi yang sama dengan yang Anda miliki sebelumnya, seperti yang diilustrasikan di sini:

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

Ringkasan

Artikel ini membahas cara menggunakan peristiwa, protokol, dan delegasi di Xamarin.iOS. Kami melihat bagaimana Xamarin.iOS mengekspos peristiwa gaya .NET normal untuk kontrol. Selanjutnya kita belajar tentang Objective-C protokol, termasuk bagaimana mereka berbeda dari antarmuka C# dan bagaimana Xamarin.iOS menggunakannya. Akhirnya, kami memeriksa Objective-C delegasi dari perspektif Xamarin.iOS. Kami melihat bagaimana Xamarin.iOS mendukung delegasi yang ditik dengan kuat dan lemah, dan cara mengikat peristiwa .NET untuk mendelegasikan metode.