Xamarin.iOS의 이벤트, 프로토콜 및 대리자

Xamarin.iOS는 컨트롤을 사용하여 대부분의 사용자 상호 작용에 대한 이벤트를 노출합니다. Xamarin.iOS 애플리케이션은 기존 .NET 애플리케이션과 거의 동일한 방식으로 이러한 이벤트를 사용합니다. 예를 들어 Xamarin.iOS UIButton 클래스에는 TouchUpInside라는 이벤트가 있으며 이 클래스와 이벤트가 .NET 앱에 있는 것처럼 이 이벤트를 사용합니다.

이 .NET 방법 외에도 Xamarin.iOS는 더 복잡한 상호 작용 및 데이터 바인딩에 사용할 수 있는 다른 모델을 노출합니다. 이 방법론은 Apple에서 대리자 및 프로토콜을 호출하는 것을 사용합니다. 대리자는 C#의 대리자와 개념상 유사하지만 단일 메서드를 정의하고 호출하는 대신 대리 Objective-C 자는 프로토콜을 준수하는 전체 클래스입니다. 프로토콜은 C#의 인터페이스와 비슷하지만 해당 메서드는 선택 사항일 수 있습니다. 예를 들어 UITableView를 데이터로 채우려면 UITableView가 자체 채우기 위해 호출하는 UITableViewDataSource 프로토콜에 정의된 메서드를 구현하는 대리자 클래스를 만듭니다.

이 문서에서는 다음을 포함하여 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에는 C# 람다 식을 사용하는 다음 코드와 같이 일반적으로 .NET에서 처리하는 TouchUpInside 이벤트가 있습니다.

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 용 핵심 애플리케이션 역량의 Target-Action 섹션을 참조하세요.

자세한 내용은 Xcode를 사용하여 사용자 인터페이스 디자인을 참조하세요.

이벤트

UIControl에서 이벤트를 가로채려면 C# 람다 및 대리자 함수 사용부터 하위 수준 Objective-C API 사용까지 다양한 옵션을 사용할 수 있습니다.

다음 섹션에서는 필요한 컨트롤 양에 따라 단추에서 TouchDown 이벤트를 캡처하는 방법을 보여 줍니다.

C# 스타일

대리자 구문 사용:

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

대신 람다를 좋아하는 경우:

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

람다 구문 사용:

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#의 인터페이스와 비슷한 용도로 사용되며, 기본 차이점은 프로토콜에 선택적 메서드가 있을 수 있다는 점입니다. 프로토콜을 채택하는 클래스가 구현하지 않으면 선택적 메서드가 호출되지 않습니다. 또한 C# 클래스가 Objective-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 되며 CocoaTouch의 NSObject루트 기본 클래스인 서브클래스입니다. 프로토콜을 사용하려면 좌표에 대해 getter 및 setter를 구현해야 합니다. 그러나 제목과 부제목은 선택 사항입니다. 따라서 클래스에서 MKAnnotation 속성은 추상이므로 구현해야 하며 속성과 SubtitleTitle 속성은 가상으로 표시되어 아래와 같이 선택 사항 Coordinate 입니다.

[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 만들 때 맵에서 사용할 관련 데이터를 제공할 수 있습니다. 맵에 주석을 추가하려면 다음 코드와 같이 인스턴스의 MKMapView 메서드를 호출 AddAnnotation 하기만 하면됩니다.

//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 에서 SampleMapAnnotation 파생된 데이터를 사용하여 Coordinate 주석 뷰를 지도에 배치합니다.

프로토콜은 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는 프로토콜에서 선택적/필수 메서드를 지원하기 위해 클래스를 추상화합니다. 그러나 프로토콜(또는 C# 인터페이스)와 달리 Objective-C C# 클래스는 여러 상속을 지원하지 않습니다. 이는 프로토콜을 사용하는 C# 코드의 디자인에 영향을 미치며 일반적으로 중첩된 클래스로 이어집니다. 이 문제에 대한 자세한 내용은 이 문서의 뒷부분에 있는 대리자 섹션에서 설명합니다.

GetCell(…)는 프로토콜의 필수 메서드인 선택기로 tableView:cellForRowAtIndexPath:바인딩Objective-C된 추상 메서드 UITableViewDataSource 입니다. 선택기는 메서드 이름의 용어입니다 Objective-C . 필요에 따라 메서드를 적용하기 위해 Xamarin.iOS는 추상 메서드를 선언합니다. 다른 메서드는 NumberOfSections(…).에 바인딩됩니다 numberOfSectionsInTableview:. 이 메서드는 프로토콜에서 선택 사항이므로 Xamarin.iOS는 이를 가상으로 선언하여 C#에서 재정의할 선택 사항입니다.

Xamarin.iOS는 모든 iOS 바인딩을 처리합니다. 그러나 프로토콜 Objective-C 을 수동으로 바인딩해야 하는 경우 클래스 ExportAttribute를 데코레이트하여 이 작업을 수행할 수 있습니다. 이는 Xamarin.iOS 자체에서 사용하는 것과 동일한 방법입니다.

Xamarin.iOS에서 형식을 바인딩 Objective-C 하는 방법에 대한 자세한 내용은 바인딩 Objective-C 형식 문서를 참조하세요.

하지만 프로토콜은 아직 통과하지 못했습니다. 또한 다음 섹션의 항목인 대리자의 기준으로 Objective-C iOS에서 사용됩니다.

대리자

iOS는 대리자를 사용하여 Objective-C 한 개체가 작업을 다른 개체로 전달하는 위임 패턴을 구현합니다. 작업을 수행하는 개체는 첫 번째 개체의 대리자입니다. 개체는 특정 작업이 발생한 후 메시지를 전송하여 대리자에게 작업을 수행하도록 지시합니다. 이와 같은 메시지를 보내는 것은 C#에서 Objective-C 메서드를 호출하는 것과 기능적으로 동일합니다. 대리자는 이러한 호출에 대한 응답으로 메서드를 구현하므로 애플리케이션에 기능을 제공합니다.

대리자를 사용하면 서브클래스를 만들지 않고도 클래스의 동작을 확장할 수 있습니다. iOS의 애플리케이션은 중요한 작업이 발생한 후 한 클래스가 다른 클래스로 다시 호출할 때 대리자를 사용하는 경우가 많습니다. 예를 들어 사용자가 지도에서 MKMapView 주석을 탭하면 클래스가 해당 대리자를 다시 호출하여 대리자 클래스의 작성자에게 애플리케이션 내에서 응답할 수 있는 기회를 제공합니다. Xamarin.iOS에서 대리자 사용 예제에서 이 문서의 뒷부분에 있는 이러한 유형의 대리자 사용 예제를 통해 작업할 수 있습니다.

이 시점에서 클래스가 해당 대리자에서 호출할 메서드를 결정하는 방법을 궁금할 수 있습니다. 프로토콜을 사용하는 또 다른 위치입니다. 일반적으로 대리자에서 사용할 수 있는 메서드는 채택하는 프로토콜에서 가져옵니다.

대리자를 사용하여 프로토콜을 사용하는 방법

앞에서 프로토콜을 사용하여 맵에 주석 추가를 지원하는 방법을 알아보았습니다. 또한 프로토콜은 사용자가 지도에서 주석을 탭하거나 표에서 셀을 선택한 후와 같이 특정 이벤트가 발생한 후 호출할 클래스에 대해 알려진 메서드 집합을 제공하는 데 사용됩니다. 이러한 메서드를 구현하는 클래스를 호출하는 클래스의 대리자로 알려져 있습니다.

위임을 지원하는 클래스는 대리자를 구현하는 클래스가 할당된 Delegate 속성을 노출하여 이 작업을 수행합니다. 대리자를 위해 구현하는 메서드는 특정 대리자가 채택하는 프로토콜에 따라 달라집니다. 이 메서드의 UITableView 경우 프로토콜을 구현 UITableViewDelegate 하고, 메서드에 UIAccelerometer 대해 대리자를 노출하려는 iOS 전체의 다른 클래스에 대해 구현 UIAccelerometerDelegate합니다.

MKMapView 이전 예제에서 본 클래스에는 대리자라는 속성도 있습니다. 이 속성은 다양한 이벤트가 발생한 후에 호출됩니다. 대리 MKMapView 자는 형식 MKMapViewDelegate입니다. 선택한 후 주석에 응답하는 예제에서 곧 이를 사용하지만, 먼저 강력한 대리자와 약한 대리자의 차이점을 살펴보겠습니다.

강력한 대리자 대 약한 대리자

지금까지 살펴본 대리자는 강력한 대리자이며, 이는 강력한 형식의 대리자입니다. Xamarin.iOS 바인딩은 iOS의 모든 대리자 프로토콜에 대해 강력한 형식의 클래스와 함께 제공됩니다. 그러나 iOS에는 약한 대리자의 개념도 있습니다. iOS에서는 특정 대리자에 대한 프로토콜에 Objective-C 바인딩된 클래스를 서브클래싱하는 대신 NSObject에서 파생된 모든 클래스에서 직접 프로토콜 메서드를 바인딩하고 ExportAttribute를 사용하여 메서드를 데코레이팅한 다음 적절한 선택기를 제공할 수 있습니다. 이 방법을 사용하면 Delegate 속성 대신 WeakDelegate 속성에 클래스의 인스턴스를 할당합니다. 약한 대리자는 대리자 클래스를 다른 상속 계층 구조로 전환할 수 있는 유연성을 제공합니다. 강력한 대리자와 약한 대리자를 모두 사용하는 Xamarin.iOS 예제를 살펴보겠습니다.

Xamarin.iOS에서 대리자를 사용하는 예제

예제에서 주석을 탭하는 사용자에 대한 응답으로 코드를 실행하려면 서브클래싱 MKMapViewDelegate 하고 인스턴스를 's Delegate 속성에 MKMapView할당할 수 있습니다. 프로토콜에는 MKMapViewDelegate 선택적 메서드만 포함됩니다. 따라서 모든 메서드는 Xamarin.iOS MKMapViewDelegate 클래스에서 이 프로토콜에 바인딩된 가상 메서드입니다. 사용자가 주석을 선택하면 인스턴스가 MKMapView 해당 대리자 mapView:didSelectAnnotationView: 에게 메시지를 보냅니다. Xamarin.iOS에서 이를 처리하려면 다음과 같이 MKMapViewDelegate 하위 클래스의 메서드를 재정 DidSelectAnnotationView (MKMapView mapView, MKAnnotationView annotationView) 의해야 합니다.

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의 클래스에 바인딩되므로 강력한 형식의 대리자를 구현하는 클래스는 일반적으로 중첩 클래스로 포함됩니다.

대리자 클래스 구현이 적용된 상태에서는 다음과 같이 컨트롤러에서 대리자의 인스턴스를 인스턴스화하고 '의 Delegate 속성에 할당하기MKMapView만 하면 됩니다.

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 모든 클래스에서 메서드를 직접 바인딩하고 해당 속성MKMapView에 할당해야 WeakDelegate 합니다. 클래스는 UIViewController 궁극적으로 NSObject (CocoaTouch의 모든 Objective-C 클래스와 같이) 파생되므로 컨트롤러에서 직접 바인딩된 메서드를 mapView:didSelectAnnotationView: 구현하고 컨트롤러를 MKMapView's WeakDelegate에 할당하여 추가 중첩 클래스가 필요하지 않도록 할 수 있습니다. 아래 코드는 이 방법을 보여 줍니다.

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실수를 했다면 런타임까지는 알 수 없습니다.

이벤트 및 대리자

대리자는 .NET에서 이벤트를 사용하는 방식과 유사하게 iOS의 콜백에 사용됩니다. iOS API와 대리자를 사용하는 Objective-C 방식이 .NET처럼 보이도록 하기 위해 Xamarin.iOS는 대리자가 iOS에서 사용되는 여러 위치에서 .NET 이벤트를 노출합니다.

예를 들어 선택한 주석에 MKMapViewDelegate 응답한 이전 구현은 .NET 이벤트를 사용하여 Xamarin.iOS에서도 구현할 수 있습니다. 이 경우 이벤트가 정의 MKMapView 되고 호출 DidSelectAnnotationView됩니다. 형식MKMapViewAnnotationEventsArgs의 하위 클래스가 EventArgs 있습니다. MKMapViewAnnotationEventsArgs 속성은 View 주석 보기에 대한 참조를 제공하며, 여기에서 설명한 대로 이전에 했던 것과 동일한 구현을 진행할 수 있습니다.

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 스타일 이벤트를 노출하는 방법을 알아보았습니다. 다음으로 C# 인터페이스와 다른 방법 및 Xamarin.iOS에서 프로토콜을 사용하는 방법을 포함하여 프로토콜에 대해 Objective-C 알아보았습니다. 마지막으로 Xamarin.iOS 관점에서 대리자를 검사 Objective-C 했습니다. Xamarin.iOS에서 강력한 대리자와 약한 형식의 대리자를 모두 지원하는 방법과 .NET 이벤트를 대리자 메서드에 바인딩하는 방법을 알아보았습니다.