Partilhar via


Eventos, protocolos e delegados no Xamarin.iOS

O Xamarin.iOS usa controles para expor eventos para a maioria das interações do usuário. Os aplicativos Xamarin.iOS consomem esses eventos da mesma maneira que os aplicativos .NET tradicionais. Por exemplo, a classe UIButton do Xamarin.iOS tem um evento chamado TouchUpInside e consome esse evento como se essa classe e esse evento estivessem em um aplicativo .NET.

Além dessa abordagem .NET, o Xamarin.iOS expõe outro modelo que pode ser usado para interação mais complexa e associação de dados. Essa metodologia usa o que a Apple chama de delegados e protocolos. Os delegados são semelhantes em conceito aos delegados em C#, mas em vez de definir e chamar um único método, um delegado em é uma classe inteira que está em Objective-C conformidade com um protocolo. Um protocolo é semelhante a uma interface em C#, exceto que seus métodos podem ser opcionais. Assim, por exemplo, para preencher um UITableView com dados, você criaria uma classe delegada que implementa os métodos definidos no protocolo UITableViewDataSource que o UITableView chamaria para preencher a si mesmo.

Neste artigo, você aprenderá sobre todos esses tópicos, fornecendo uma base sólida para lidar com cenários de retorno de chamada no Xamarin.iOS, incluindo:

  • Eventos – Usando eventos .NET com controles UIKit.
  • Protocolos – Aprender o que são protocolos e como eles são usados e criar um exemplo que forneça dados para uma anotação de mapa.
  • Delegados – Aprender sobre Objective-C delegados estendendo o exemplo de mapa para lidar com a interação do usuário que inclui uma anotação e, em seguida, aprender a diferença entre delegados fortes e fracos e quando usar cada um deles.

Para ilustrar protocolos e delegados, criaremos um aplicativo de mapa simples que adiciona uma anotação a um mapa, conforme mostrado aqui:

Um exemplo de um aplicativo de mapa simples que adiciona uma anotação a um mapaUm exemplo de anotação adicionada a um mapa

Antes de abordar este aplicativo, vamos começar examinando os eventos do .NET no UIKit.

Eventos do .NET com UIKit

Xamarin.iOS expõe eventos .NET em controles UIKit. Por exemplo, UIButton tem um evento TouchUpInside, que você manipula como faria normalmente no .NET, conforme mostrado no código a seguir que usa uma expressão lambda C#:

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

Você também pode implementar isso com um método anônimo no estilo C# 2.0 como este:

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

O código anterior é conectado no ViewDidLoad método do UIViewController. A aButton variável faz referência a um botão, que você pode adicionar no Xcode Interface Builder ou com código.

O Xamarin.iOS também oferece suporte ao estilo de ação de destino de conectar seu código a uma interação que ocorre com um controle.

Para obter mais detalhes sobre o padrão de ação de destino do iOS, consulte a seção Ação de destino das principais competências de aplicativo para iOS na Biblioteca de desenvolvedores do iOS da Apple.

Para obter mais informações, consulte Criando interfaces de usuário com o Xcode.

Eventos

Se você quiser interceptar eventos do UIControl, você tem uma variedade de opções: desde usar as funções lambdas e delegar C# até usar as APIs de baixo nível Objective-C .

A seção a seguir mostra como você capturaria o evento TouchDown em um botão, dependendo de quanto controle você precisa.

Estilo C#

Usando a sintaxe delegada:

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

Se você gosta de lambdas:

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

Se você quiser ter vários botões, use o mesmo manipulador para compartilhar o mesmo código:

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

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

Monitoramento de mais de um tipo de Evento

Os eventos C# para sinalizadores UIControlEvent têm um mapeamento um-para-um para sinalizadores individuais. Quando você quiser que a mesma parte de código manipule dois ou mais eventos, use o UIControl.AddTarget método:

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

Usando a sintaxe lambda:

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

Se você precisar usar recursos de baixo nível do , como conectar-se a uma instância de Objective-Cobjeto específica e invocar um seletor específico:

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

// In some other place:

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

Observe que, se você implementar o método de instância em uma classe base herdada, ele deverá ser um método público.

Protocolos

Um protocolo é um Objective-C recurso de idioma que fornece uma lista de declarações de método. Ele serve a um propósito semelhante a uma interface em C#, a principal diferença é que um protocolo pode ter métodos opcionais. Os métodos opcionais não serão chamados se a classe que adota um protocolo não os implementar. Além disso, uma única classe em Objective-C pode implementar vários protocolos, assim como uma classe C# pode implementar várias interfaces.

A Apple usa protocolos em todo o iOS para definir contratos para as classes a serem adotadas, enquanto abstrai a classe de implementação do chamador, operando assim como uma interface C#. Os protocolos são usados em cenários não delegados (como no exemplo mostrado a MKAnnotation seguir) e com delegados (conforme apresentado posteriormente neste documento, na seção Representantes).

Protocolos com Xamarin.ios

Vamos dar uma olhada em um exemplo usando um Objective-C protocolo do Xamarin.iOS. Para este exemplo, usaremos o MKAnnotation protocolo, que faz parte da MapKit estrutura. MKAnnotation é um protocolo que permite que qualquer objeto que o adote forneça informações sobre uma anotação que pode ser adicionada a um mapa. Por exemplo, uma implementação MKAnnotation de objeto fornece o local da anotação e o título associado a ela.

Dessa forma, o MKAnnotation protocolo é utilizado para fornecer dados pertinentes que acompanham uma anotação. A exibição real da anotação em si é criada a partir dos dados no objeto que adota o MKAnnotation protocolo. Por exemplo, o texto do texto explicativo que aparece quando o usuário toca na anotação (como mostrado na captura de tela abaixo) vem da Title propriedade na classe que implementa o protocolo:

Texto de exemplo para o texto explicativo quando o usuário toca na anotação

Conforme descrito na próxima seção, Protocols Deep Dive, Xamarin.iOS vincula protocolos a classes abstratas. Para o MKAnnotation protocolo, a classe C# acoplada é nomeada MKAnnotation para imitar o nome do protocolo e é uma subclasse de , a classe base raiz NSObjectpara CocoaTouch. O protocolo requer que um getter e setter sejam implementados para a coordenada; no entanto, um título e um subtítulo são opcionais. Portanto, na MKAnnotation classe, a Coordinate propriedade é abstrata, exigindo que ela seja implementada e as propriedades e Subtitle sejam marcadas Titlecomo virtuais, tornando-as opcionais, como mostrado abaixo:

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

Qualquer classe pode fornecer dados de anotação simplesmente derivando de MKAnnotation, desde que pelo menos a Coordinate propriedade seja implementada. Por exemplo, aqui está uma classe de exemplo que usa a coordenada no construtor e retorna uma cadeia de caracteres para o título:

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

Através do protocolo ao qual ele está vinculado, qualquer classe que subclasses MKAnnotation pode fornecer dados relevantes que serão usados pelo mapa quando ele cria a visualização da anotação. Para adicionar uma anotação a um mapa, basta chamar o AddAnnotation método de uma MKMapView instância, conforme mostrado no código a seguir:

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

A variável map aqui é uma instância de um MKMapView, que é a classe que representa o próprio mapa. O MKMapView usará os Coordinate dados derivados da instância para posicionar a exibição de SampleMapAnnotation anotação no mapa.

O MKAnnotation protocolo fornece um conjunto conhecido de recursos em todos os objetos que o implementam, sem que o consumidor (o mapa, neste caso) precise saber sobre os detalhes da implementação. Isso simplifica a adição de uma variedade de anotações possíveis a um mapa.

Protocolos Deep Dive

Como as interfaces C# não oferecem suporte a métodos opcionais, o Xamarin.iOS mapeia protocolos para classes abstratas. Portanto, a adoção de um protocolo em Objective-C é realizada no Xamarin.iOS derivando da classe abstrata que está vinculada ao protocolo e implementando os métodos necessários. Esses métodos serão expostos como métodos abstratos na classe. Os métodos opcionais do protocolo serão vinculados a métodos virtuais da classe C#.

Por exemplo, aqui está uma parte do UITableViewDataSource protocolo como vinculado no 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){...}
...
}

Observe que a classe é abstrata. O Xamarin.iOS torna a classe abstrata para oferecer suporte a métodos opcionais/obrigatórios em protocolos. No entanto, ao contrário Objective-C dos protocolos (ou interfaces C#), as classes C# não oferecem suporte a herança múltipla. Isso afeta o design do código C# que usa protocolos e normalmente leva a classes aninhadas. Mais informações sobre esse problema serão abordadas posteriormente neste documento, na seção Delegados.

GetCell(…) é um método abstrato, vinculado ao Objective-Cseletor, tableView:cellForRowAtIndexPath:que é um método obrigatório do UITableViewDataSource protocolo. Seletor é o termo para o nome do Objective-C método. Para impor o método conforme necessário, o Xamarin.iOS o declara como abstrato. O outro método, NumberOfSections(…), está vinculado a numberOfSectionsInTableview:. Esse método é opcional no protocolo, portanto, o Xamarin.iOS o declara como virtual, tornando-o opcional para substituir em C#.

O Xamarin.iOS cuida de todas as ligações do iOS para você. No entanto, se você precisar vincular um protocolo manualmente Objective-C , poderá fazê-lo decorando uma classe com o ExportAttribute. Este é o mesmo método usado pelo próprio Xamarin.iOS.

Para obter mais informações sobre como vincular Objective-C tipos no Xamarin.iOS, consulte o artigo Tipos de vinculaçãoObjective-C.

Mas ainda não terminamos com os protocolos. Eles também são usados no iOS como base para Objective-C delegados, que é o tópico da próxima seção.

Representantes

O iOS usa Objective-C delegados para implementar o padrão de delegação, no qual um objeto passa o trabalho para outro. O objeto que faz o trabalho é o delegado do primeiro objeto. Um objeto diz ao seu delegado para fazer o trabalho enviando-lhe mensagens depois que certas coisas acontecem. Enviar uma mensagem como essa é Objective-C funcionalmente equivalente a chamar um método em C#. Um delegado implementa métodos em resposta a essas chamadas e, portanto, fornece funcionalidade ao aplicativo.

Os delegados permitem que você estenda o comportamento das classes sem a necessidade de criar subclasses. Os aplicativos no iOS geralmente usam delegados quando uma classe chama de volta para outra depois que uma ação importante ocorre. Por exemplo, a MKMapView classe chama de volta para seu delegado quando o usuário toca em uma anotação em um mapa, dando ao autor da classe delegada a oportunidade de responder dentro do aplicativo. Você pode trabalhar com um exemplo desse tipo de uso de representante mais adiante neste artigo, em Exemplo usando um representante com Xamarin.iOS.

Neste ponto, você pode estar se perguntando como uma classe determina quais métodos chamar seu delegado. Este é outro lugar onde você usa protocolos. Normalmente, os métodos disponíveis para um delegado vêm dos protocolos que eles adotam.

Como os protocolos são usados com os delegados

Vimos anteriormente como os protocolos são usados para dar suporte à adição de anotações a um mapa. Os protocolos também são usados para fornecer um conjunto conhecido de métodos para classes a serem chamadas depois que determinados eventos ocorrem, como depois que o usuário toca em uma anotação em um mapa ou seleciona uma célula em uma tabela. As classes que implementam esses métodos são conhecidas como os delegados das classes que os chamam.

As classes que dão suporte à delegação fazem isso expondo uma propriedade Delegate, à qual uma classe que implementa o delegado é atribuída. Os métodos implementados para o delegado dependerão do protocolo adotado pelo delegado específico. Para o método, você implementa o protocolo, para o UIAccelerometer método, você implementaria UIAccelerometerDelegateo , e assim por diante para quaisquer outras classes em todo o UITableViewUITableViewDelegate iOS para as quais você gostaria de expor um delegado.

A MKMapView classe que vimos em nosso exemplo anterior também tem uma propriedade chamada Delegate, que será chamada depois que vários eventos ocorrerem. O Delegado para MKMapView é do tipo MKMapViewDelegate. Você usará isso brevemente em um exemplo para responder à anotação depois que ela for selecionada, mas primeiro vamos discutir a diferença entre delegados fortes e fracos.

Delegados fortes x delegados fracos

Os delegados que analisamos até agora são delegados fortes, o que significa que são fortemente tipados. As ligações Xamarin.iOS são fornecidas com uma classe fortemente tipada para cada protocolo delegado no iOS. No entanto, o iOS também tem o conceito de um delegado fraco. Em vez de subclassificar uma classe vinculada ao protocolo de um delegado específico, o Objective-C iOS também permite que você escolha vincular os métodos de protocolo em qualquer classe que você goste que derive do NSObject, decorando seus métodos com o ExportAttribute e, em seguida, fornecendo os seletores apropriados. Ao adotar essa abordagem, você atribui uma instância de sua classe à propriedade WeakDelegate em vez de à propriedade Delegate. Um delegado fraco oferece a flexibilidade de levar sua classe de delegado para uma hierarquia de herança diferente. Vejamos um exemplo do Xamarin.iOS que usa delegados fortes e fracos.

Exemplo usando um delegado com Xamarin.iOS

Para executar código em resposta ao usuário tocando na anotação em nosso exemplo, podemos subclassificar MKMapViewDelegate e atribuir uma instância à MKMapViewpropriedade 's Delegate . O MKMapViewDelegate protocolo contém apenas métodos opcionais. Portanto, todos os métodos são virtuais que estão vinculados a esse protocolo na classe Xamarin.iOS MKMapViewDelegate . Quando o usuário seleciona uma anotação, a MKMapView instância enviará a mapView:didSelectAnnotationView: mensagem para seu representante. Para lidar com isso no Xamarin.iOS, precisamos substituir o DidSelectAnnotationView (MKMapView mapView, MKAnnotationView annotationView) método na subclasse MKMapViewDelegate da seguinte maneira:

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

A classe SampleMapDelegate mostrada acima é implementada como uma classe aninhada no controlador que contém a MKMapView instância. No Objective-C, você verá frequentemente o controlador adotar vários protocolos diretamente dentro da classe. No entanto, como os protocolos são vinculados a classes no Xamarin.iOS, as classes que implementam delegados fortemente tipados geralmente são incluídas como classes aninhadas.

Com a implementação da classe delegate em vigor, você só precisa instanciar uma instância do delegado no controlador e atribuí-la à MKMapViewpropriedade 's Delegate , conforme mostrado aqui:

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

Para usar um delegado fraco para realizar a mesma coisa, você mesmo precisa vincular o método em qualquer classe derivada de NSObject e atribuí-lo à WeakDelegate propriedade do MKMapView. Uma vez que a UIViewController classe deriva de NSObject (como toda Objective-C classe em CocoaTouch), podemos simplesmente implementar um método ligado diretamente mapView:didSelectAnnotationView: no controlador e atribuir o controlador a MKMapView's, evitando a necessidade da WeakDelegateclasse aninhada extra. O código abaixo demonstra essa abordagem:

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

Ao executar esse código, o aplicativo se comporta exatamente como ao executar a versão delegado fortemente tipada. O benefício desse código é que o delegado fraco não requer a criação da classe extra que foi criada quando usamos o delegado fortemente tipado. No entanto, isso vem às custas da segurança do tipo. Se você cometesse um erro no seletor que foi passado para o , você não descobriria até o ExportAttributetempo de execução.

Eventos e Congressistas

Os delegados são usados para retornos de chamada no iOS de forma semelhante à maneira como o .NET usa eventos. Para fazer com que as APIs do iOS e a maneira como eles usam Objective-C delegados pareçam mais com o .NET, o Xamarin.iOS expõe eventos do .NET em muitos lugares onde os delegados são usados no iOS.

Por exemplo, a implementação anterior em que a resposta a uma anotação selecionada também poderia ser implementada MKMapViewDelegate no Xamarin.iOS usando um evento .NET. Nesse caso, o evento seria definido e MKMapView chamado DidSelectAnnotationViewde . Teria uma EventArgs subclasse do tipo MKMapViewAnnotationEventsArgs. A View propriedade de lhe daria uma referência à exibição de MKMapViewAnnotationEventsArgs anotação, a partir da qual você poderia prosseguir com a mesma implementação que tinha anteriormente, conforme ilustrado aqui:

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

Resumo

Este artigo abordou como usar eventos, protocolos e delegados no Xamarin.iOS. Vimos como o Xamarin.iOS expõe eventos de estilo .NET normais para controles. Em seguida, aprendemos sobre Objective-C protocolos, incluindo como eles são diferentes das interfaces C# e como o Xamarin.iOS os usa. Finalmente, examinamos Objective-C os delegados de uma perspectiva Xamarin.iOS. Vimos como o Xamarin.iOS oferece suporte a delegados de tipo forte e fraco e como vincular eventos .NET a métodos delegados.