Abril de 2018

Volume 33 – Número 4

Visual Studio para Mac - Programação para watchOS com Xamarin e Visual Studio para Mac

Por Dawid Borycki

Pequenos dispositivos portáteis, como rastreadores de atividade física e relógios inteligentes (como o Microsoft Band, Android Wear ou Apple Watch) estão se tornando cada vez mais populares. Esses dispositivos portáteis são equipados com vários sensores, que monitoram, em tempo real, os parâmetros de saúde de quem os usa. Muitos dispositivos portáteis também têm interfaces de comunicação para que os dados do sensor sejam facilmente transmitidos para serviços de nuvem personalizados ou dedicados (como o Microsoft Health) para armazenamento ou processamento avançado. Assim, o dispositivo portátil pode funcionar como um ponto de extremidade adicional em um ecossistema da Internet das Coisas (IoT). Isso, por sua vez, ajuda a aprimorar a qualidade dos cuidados pessoais de saúde, pois os algoritmos preditivos da IoT podem informar antecipadamente o usuário sobre problemas de saúde iminentes.

Os dispositivos portáteis também podem executar aplicativos personalizados. Os desenvolvedores recebem SDKs dedicados. No entanto, como acontece com muitos dispositivos móveis, cada plataforma tem sua própria API específica, que pode ser acessada pelas linguagens de programação e ferramentas específicas da plataforma. Para facilitar as coisas, o Xamarin dá suporte ao Android Wear e ao watchOS nas bibliotecas Xamarin.Android e Xamarin.iOS, respectivamente. Você pode desenvolver aplicativos portáteis de forma semelhante à usada para criar aplicativos móveis, utilizando a base de código comum do .NET, que é referenciada nos projetos específicos da plataforma.

Neste artigo, mostrarei como você pode utilizar essa abordagem para criar o aplicativo watchOS representado na Figura 1. Quando você executa esse aplicativo, ele começa recuperando a coleção de objetos do serviço Web REST. Essa coleção é composta por fotos falsas que possuem, cada uma, um título e um bitmap (imagem em apenas uma cor). Nessa etapa, o aplicativo mostra somente um botão com a legenda Get list (Obter lista). Esse botão fica desabilitado até os dados serem baixados, como mostrado na primeira linha da Figura 1.

Visualização do aplicativo watchOS
Figura 1 - Visualização do aplicativo watchOS

Toque no botão, e uma folha de ações (segunda linha da Figura 1) aparecerá exibindo um alerta composto por diversos botões, definidos como ações ou botões de ação (bit.ly/2EEUZpL). Neste exemplo, a folha de ações fornece os botões de ação, cujas legendas contêm uma série de fotos a serem exibidas. Quando você toca em uma ação, as fotos selecionadas são exibidas no controle da tabela (bit.ly/2Caq0nM), logo abaixo do botão Get list (Obter lista), conforme mostrado na última linha da Figura 1. Essa tabela pode ser rolada para baixo para que você possa ver todas as fotos no grupo.

Vou implementar a comunicação com o serviço Web em uma biblioteca de classes .NET Standard (bit.ly/2HArfMq) separada. De acordo com a documentação referenciada, o .NET Standard é uma especificação formal das APIs .NET desenvolvida para conceder acesso uniforme a interfaces de programação disponíveis em todas as implementações do .NET. Uma das principais vantagens dessa abordagem é que a biblioteca compartilhada pode ser facilmente referenciada em projetos .NET para reduzir ou eliminar a compilação condicional do código compartilhado. Consequentemente, a biblioteca de classes .NET Standard pode ser implementada uma vez e, em seguida, referenciada em vários projetos .NET, visando à Plataforma Universal do Windows (UWP), ao .NET Core, ASP.NET Core, Xamarin.iOS, Xamarin.Android, Xamarin.Forms e assim sucessivamente, sem que seja preciso fazer a recompilação para cada plataforma.

Aqui, mostrarei como reutilizar o código comum no aplicativo watchOS. Para o serviço Web, usarei o JSONPlaceholder (jsonplaceholder.typicode.com) do servidor de API REST falso, que fornece vários recursos, inclusive o de fotos utilizado no aplicativo de exemplo. Esse recurso armazena uma coleção de fotos falsas, cada uma representada como o seguinte objeto JSON:

{
  "albumId": 1,
  "id": 1,
  "title": "accusamus beatae ad facilis cum similique qui sunt",
  "url": "http://placehold.it/600/92c952",
  "thumbnailUrl": "http://placehold.it/150/92c952"
}

Cada foto tem um ID, um álbum de fotos, um título e duas URLs associados, indicando um bitmap e sua miniatura. Neste exercício, o bitmap é uma imagem de uma cor com uma legenda que mostra as dimensões do bitmap.

Tudo o que você vê aqui é criado usando-se o Visual Studio para Mac. Você pode encontrar o código de exemplo deste projeto no GitHub, em github.com/dawidborycki/Photos.

Aplicativo pai e código compartilhado

A estrutura de uma típica solução watchOS contém três projetos (consulte apple.co/2GwXhrn and bit.ly/2EI2dNO). O primeiro é o aplicativo iOS pai. Os outros dois projetos são dedicados ao aplicativo watch: Pacote de aplicativos Watch e pacote de extensão WatchKit. O aplicativo iOS pai é usado como um proxy para fornecer pacotes Watch ao dispositivo portátil. O pacote de aplicativos Watch contém storyboards de interface. Como acontece com o iOS, os desenvolvedores usam storyboards de interface para definir cenas e transições entre elas. Por fim, o pacote de extensões WatchKit contém recursos e o código do aplicativo.

Vamos começar criando o aplicativo iOS pai, usando o novo projeto Single View iOS, que pode ser encontrado no criador Novo Projeto do Visual Studio para Mac. Defino os nomes do projeto e da solução como Photos.iOS e Photos (Fotos), respectivamente. Depois de criar o projeto, complemento a solução Photos adicionando outro projeto Photos.Common, que crio usando o modelo de projeto Biblioteca do .NET Standard. Esse modelo está localizado no grupo Multiplataforma | Biblioteca do criador Novo Projeto.

Quando você cria a Biblioteca do .NET Standard, pode escolher a versão do .NET Standard. Essa versão determina a API disponível (quanto mais alta for a versão, maior será a funcionalidade acessível) e as plataformas suportadas (quanto mais alta for a versão, menor será o número de plataformas suportadas). Aqui, defino a versão do .NET Standard como 2.0, que já inclui a classe HttpClient para realizar a comunicação com o serviço Web pelo HTTP. Você pode recuperar a lista de APIs para cada versão do .NET Standard usando o navegador de API .NET em bit.ly/2Fl44Fa.

Depois de configurar o projeto comum, instalo um pacote NuGet, Newtonsoft.JSON, que será usado para desserializar as repostas do HTTP. A instalação do pacote NuGet no Visual Studio para Mac é idêntica à realizada no Visual Studio para Windows. No Gerenciador de Soluções, clique com o botão direito do mouse no nó Dependencies (Dependências) | NuGet e, no menu de contexto, escolha Add Packages (Adicionar Pacotes). O Visual Studio exibe uma janela na qual você pode pesquisar pacotes. Você também pode usar o Console de Pacotes e instalar o pacote NuGet pela linha de comandos.

Cliente REST

Agora estou pronto para implementar a classe de cliente para o serviço Web Photos (Fotos). Para simplificar a desserialização, mapeio o objeto JSON, mostrado anteriormente, para uma classe C#. Isso pode ser feito manualmente ou com uma ferramenta dedicada. Aqui, vou usar JSONUtils (jsonutils.com). O site da Web tem uma interface intuitiva que consiste nos seguintes elementos:

  • A caixa de texto Name (Nome), na qual você insere o nome da sua classe
  • A caixa de texto JSON Text ou URL, na qual deve-se inserir o código JSON ou sua URL
  • Vários botões de opção, que permitem escolher a linguagem (C#, VB.NET etc.)
  • Duas caixas de seleção: Add Namespace (Adicionar Namespace) e Pascal Case (Caso Pascal)

Para gerar a classe C#, defino a opção Class Name (Nome da Classe) como Photo (Foto) e colo a seguinte URL na caixa de texto JSON Text ou URL: jsonplaceholder.typicode.com/photos/1. Por fim, habilito a caixa de seleção Pascal Case e clico no botão Submit (Enviar). A classe Photo (Foto) gerada agora aparece na parte inferior da página. A Classe Photo (Foto) (veja o código complementar: Photos.Common/Model/Photo.cs) não requer outros comentários, é composta somente por propriedades implementadas automaticamente, representando o objeto JSON mostrado anteriormente.

Para implementar o cliente REST, crio a classe estática PhotoClient (projeto Photos.Common). Essa classe tem um campo do tipo HttpClient. Esse campo é instanciado no construtor estático para definir a propriedade BaseAddress de forma que indique a URL JSONPlaceholder, como mostrado a seguir:

private static HttpClient httpClient;
static PhotosClient()
{
  httpClient = new HttpClient()
  {
    BaseAddress = new Uri("https://jsonplaceholder.typicode.com/")
  };
}

Como mostra a Figura 2, o PhotosClient tem dois métodos públicos:

  • GetByAlbumId: Obtém a coleção de fotos do álbum em questão usando o método GetAsync da classe HttpClient. Após a verificação do código de status de resposta do HTPP (CheckStatusCode), a resposta resultante é desserializada para uma coleção de objetos de Photo (Foto) em C# usando um método auxiliar genérico DeserializeResponse (os métodos auxiliares serão discutidos posteriormente).
  • GetImageData: Recupera a matriz de bytes, representando a foto da URL fornecida. Para obter os dados da imagem, uso o GetByteArrayAsync da instância de classe HttpClient.

Figura 2 - Métodos públicos da classe PhotoClient

public static async Task<IEnumerable<Photo>> GetByAlbumId(int albumId)
{
  var response = await httpClient.GetAsync($"photos?albumId={albumId}");
  var photoCollection = new List<Photo>();
  try
  {
    CheckStatusCode(response);
    photoCollection = await DeserializeResponse<List<Photo>>(response);
  }
  catch(Exception ex)
  {
    Console.WriteLine(ex.Message);
  }
  return photoCollection;
}
public static async Task<byte[]> GetImageData(Photo photo)
{
  var imageData = new byte[0];
  try
  {
    Check.IsNull(photo);
    imageData = await httpClient.GetByteArrayAsync(photo.ThumbnailUrl);
  }
  catch(Exception ex)
  {
    Console.WriteLine(ex.Message);
  }
  return imageData;
}

A PhotosClient também implementa dois métodos particulares: Check­StatusCode e DeserializeResponse. O primeiro método aceita uma instância da classe HttpResponseMessage e verifica o valor de sua propriedade IsSuccessStatusCode, causando uma exceção se o código do status for diferente de 200 (código de sucesso).

private static void CheckStatusCode(HttpResponseMessage response)
{
  if (!response.IsSuccessStatusCode)
  {
    throw new Exception($"Unexpected status code: {response.StatusCode}");
  }
}

O segundo método, DeserializeReponse, também aceita uma instância da classe HttpResponseMessage, mas lê o corpo da mensagem como uma cadeia de caracteres usando o método HttpResponseMessage.Content.ReadAsStringAsync. O valor resultante é passado ao método estático DeserializeObject da classe Newtonsoft.Json.JsonConvert. Esta última retorna o objeto C# do tipo fornecido, como mostrado aqui:

private static async Task<T> DeserializeResponse<T>(HttpResponseMessage response)
{
  var jsonString = await response.Content.ReadAsStringAsync();
  return JsonConvert.DeserializeObject<T>(jsonString);
}

Na Figura 2, também uso o método IsNull da classe personalizada Check. O método IsNull faz uma simples validação de argumentos para verificar se o argumento é nulo. Caso seja, ocorrerá uma exceção do tipo ArgumentNullException (veja o código complementar: Photos.Common/Helpers/Check.cs).

A funcionalidade compartilhada agora está pronta. Por isso, posso continuar a implementar o aplicativo watchOS.

O aplicativo watchOS e sua estrutura

Para criar o aplicativo watchOS, primeiro clico com o botão direito do mouse no nome da solução (neste caso, Fotos) no Solution Explorer (Gerenciador de Soluções) e escolho Add (Adicionar) | Add New Project (Adicionar Novo Projeto) no menu de contexto. A caixa de diálogo New Project (Novo Projeto) é exibida. Nela, clico na seguinte guia: watchOS | App (não use a guia Extension ou Library). Uma lista de modelos de projeto é exibida. Nela, escolho o modelo de projeto WatchKit App C#. Depois, a lista de opções configuráveis será apresentada, como mostrado na Figura 3.

Configuração do aplicativo watchOS
Figura 3 - Configuração do aplicativo watchOS

Como mencionado anteriormente, cada aplicativo watchOS tem um aplicativo iOS pai associado. Nesta instância, é o aplicativo Photos.iOS. Em seguida, na caixa de texto App Name (Nome do Aplicativo), digito WatchKit, defino Target (Destino) como watchOS 4.2 (observe que os itens específicos desta lista dependerão da versão do SDK instalada) e desmarco todas as caixas de seleção do grupo Scenes (Cenas).

Depois de clicar no botão Next (Avançar), o Visual Studio criará mais dois projetos: Photos.iOS.WatchKit e Photos.iOS.WatchKitExtension.

O primeiro projeto (WatchKit) contém o storyboard, que você usa para definir cenas e transições entre elas. O segundo projeto (WatchKitExtension) contém a lógica associada, incluindo controladores de visualização, chamados de controladores de interface no watchOS. Então, normalmente, você modifica a interface do usuário do aplicativo watch usando o designer de storyboard (mostrado na Figura 4), ativado clicando-se duas vezes no arquivo Interface.storyboard do aplicativo WatchKit.

Projetando a interface do usuário do aplicativo watchOS
Figura 4 - Projetando a interface do usuário do aplicativo watchOS

O designer de storyboard permite que você arraste e solte os controles da caixa de ferramentas nas cenas. Na Figura 4, tenho apenas uma cena, que é associada à classe InterfaceController, definida no projeto Photos.iOS.WatchKit­Extension. Esta classe é como a UIViewController de um aplicativo iOS, ou seja, ela apresenta e gerencia conteúdo na tela e implementa métodos que lidam com as interações dos usuários. Observe que quando os controles são adicionados à cena, você pode modificar suas propriedades usando o painel de propriedades e acessando-os no código por meio da classe InterfaceController.

Antes de editar a interface do usuário, vamos investigar rapidamente a estrutura dessa classe, derivada de WatchKit.WKInterfaceController — a classe base de todos os controladores de interface. Uma implementação padrão do InterfaceController substitui três métodos relacionados ao ciclo de vida da visualização (consulte apple.co/2GwXhrn):

  • Awake: Chamado pelo sistema logo após a inicialização do InterfaceController. Você normalmente usa esse método para carregar dados e preparar a interface do usuário.
  • WillActivate: Chamado quando a visualização associada está prestes a se tornar ativa. Você usa esse método para preparar as atualizações finais imediatamente antes da exibição da interface.
  • DidDeactivate: Chamado quando o modo de exibição fica inativo. Você normalmente usa esse método para lançar recursos dinâmicos, que não são mais necessários.

Esses métodos são usados para configurar seu modo de exibição, dependendo da sua visibilidade. Para exemplificar, vou analisar o seguinte método Awake:

public override void Awake(
  NSObject context)
{
  base.Awake(context);
  SetTitle("Hello, watch!");
}

Esse código chama o método Awake da classe base (WKInterfaceController) e, em seguida, chama o método SetTitle, que muda a cadeia de caracteres exibida no canto superior esquerdo da visualização. Esse título é um elemento padrão de cada controlador de interface.

Para testar essa modificação, você pode executar o aplicativo Photos.iOS.WatchKit no simulador. Primeiro, você precisa definir esse aplicativo (e não o pacote de extensão) como o projeto de inicialização usando uma lista suspensa na barra de ferramentas do Visual Studio para Mac. Ao lado dessa lista, você tem mais duas listas suspensas: uma para selecionar a configuração (Debug ou Release) e outra para selecionar em uma lista de simuladores. Aqui, estou usando a configuração de depuração e o emulador Apple Watch Series 3 – 42 mm – watchOS 4.2. Depois de escolher o simulador e clicar no ícone Play (Reproduzir), o aplicativo é compilado e implantado. Observe que os dois simuladores são iniciados: o simulador iOS e seu simulador watchOS (confira a tela esquerda na Figura 1).

Folha de ações

Agora posso continuar implementando a interface de usuário real do aplicativo Photos.iOS.WatchKit. Como mostrado na Figura 1, a interface do usuário do aplicativo contém três elementos: um botão, uma folha de ações e uma visualização de tabela. Quando o usuário toca no botão, uma folha de ações é ativada. Ela apresenta várias opções, que permitem que o usuário escolha o grupo de fotos a ser exibido no modo de exibição da tabela. Implementei esse grupo de fotos para atender à orientação da Apple para limitar o número de linhas no modo de exibição da tabela como uma forma de aprimorar o desempenho do aplicativo (apple.co/2Cecrnt).

Vou começar criando a lista de botões, que será exibida na folha de ações. A lista desses botões é gerada com base na coleção de fotos (campo de fotos) recuperada do serviço Web, como mostrado na Figura 5. Cada botão de ação é representado como uma instância da classe WatchKit.WKAction. Essa classe não tem construtores públicos, mas implementa o método Create estático, que você usa para criar ações. Como mostrado na Figura 5, o método Create aceita três argumentos:

  • Title (Título) define a legenda do botão de ação
  • Style (Estilo) indica o estilo do botão de ação
  • Handler (Manipulador) especifica um método a ser executado quando o usuário toca no botão de ação

Figura 5 - Particionamento de títulos de fotos

private const int rowsPerGroup = 10;
private IEnumerable<Photo> photos;
private WKAlertAction[] alertActions;
private void CreateAlertActions()
{
  var actionsCount = photos.Count() / rowsPerGroup;
  alertActions = new WKAlertAction[actionsCount];
  for (var i = 0; i < actionsCount; i++)
  {
    var rowSelection = new RowSelection(
      i, rowsPerGroup, photos.Count());
    var alertAction = WKAlertAction.Create(
      rowSelection.Title,
      WKAlertActionStyle.Default,
      async () => { await DisplaySelectedPhotos(rowSelection); });
    alertActions[i] = alertAction;
  }
}

Na Figura 5, todas as ações têm um estilo padrão, representado como o valor Padrão da enumeração WatchKit.WKAlertStyle. Essa enumeração define dois outros valores (apple.co/2EHCAZr): Cancel e Destructive. Você pode usar o primeiro para criar uma ação que cancela uma operação sem fazer nenhuma alteração. O estilo Destructive deve ser aplicado a ações que produzam modificações irreversíveis.

O método CreateAlertActions da Figura 5 organiza as fotos em partes, e cada uma delas contém dez elementos (constante rowsPerGroup). Para obter um grupo selecionado de fotos da coleção, preciso de dois índices: um onde o grupo começa (variável beginIndex) e outro onde termina (endIndex). Para calcular esses índices, uso a classe RowSelection, que também é usada para criar títulos para itens exibidos na folha de ações. A classe RowSelection é implementada no projeto Photos.iOS.WatchKitExtension (RowSelection.cs), e suas partes mais importantes são mostradas na Figura 6.

Figura 6 - Cálculo de índices com a classe RowSelection

public int BeginIndex { get; private set; }
public int EndIndex { get; private set; }
public int RowCount { get; private set; }
public string Title { get; private set; }
private static string titlePrefix = "Elements to show:";
public RowSelection(int groupIndex, int rowsPerGroup, int elementCount)
{
  BeginIndex = groupIndex * rowsPerGroup;
  EndIndex = Math.Min((groupIndex + 1) * rowsPerGroup, elementCount) - 1;
  RowCount = EndIndex - BeginIndex + 1;
  Title = $"{titlePrefix} {BeginIndex}-{EndIndex}";
}

Por fim, cada botão da folha de ações tem um manipulador associado, que chama o método DisplaySelectedPhotos. Esse método é responsável por apresentar a tabela das fotos selecionadas e será descrito mais adiante.

Para ativar a folha de ações, primeiro referencio o projeto Photos.Common. Para fazer isso, no Gerenciador de Soluções, clico com o botão direito do mouse em References (Referências) de Photos.iOS.WatchKitExtension, seleciono Edit References (Editar Referências), escolho a guia Project (Projeto) e seleciono Photos.Common. No Gerenciador de Referências também preciso referenciar a biblioteca Newtonsoft.Json.dll para garantir que ela será copiada para o diretório de saída. Para fazer isso, uso a guia Assembly .NET, clico no botão Procurar e escolho Newtonsoft.Json.dll na pasta packages/Newtonsoft.Json/lib/netstandard20. Essa pasta é criada após a instalação do pacote Newtonsoft.Json NuGet.

Essas etapas são necessárias para acessar uma base de código compartilhada (incluindo o PhotosClient implementado anteriormente) do aplicativo watchOS. Em seguida, modifico a interface do usuário usando o storyboard. Uma descrição detalhada de como o layout funciona no watchOS pode ser encontrada na documentação da Apple (apple.co/2FlzADj) e do Xamarin (bit.ly/2EKjCRM).

Depois de abrir o designer de storyboard, arrasto o Controle de botão da caixa de ferramentas para a cena. Usando o painel Properties (Propriedades), defino o nome do botão e as propriedades do título como ButtonDisplayPhotoList e Get list (Obter lista), respectivamente. Em seguida, crio um manipulador de eventos, que é executado sempre que o usuário toca no botão. Para criar um manipulador de eventos, uso o painel Properties (Propriedades), clico na guia Events (Eventos) e digito ButtonDisplayPhotoList_Activated na caixa de pesquisa Action (Ação). Depois que pressiono a tecla Enter, o Visual Studio declara o novo método na classe InterfaceController. Por fim, Button­DisplayPhotoList_Activated é definido da seguinte forma:

partial void ButtonDisplayPhotoList_Activated()
{
  PresentAlertController(string.Empty,
    string.Empty,
    WKAlertControllerStyle.ActionSheet,
    alertActions);
}

Para criar e apresentar uma folha de ações, uso o PresentAlertController. Esse método aceita quatro argumentos:

  • Title (Título) indica o título do alerta.
  • Message (Mensagem) especifica o texto a ser exibido no corpo do alerta.
  • PreferredStyle (Estilo Preferido) especifica o estilo do controlador de alerta. Style (Estilo) é representado por um dos valores, definido na enumeração Watch­Kit.WKAlertControllerStyle: Alert, SideBy­SideButtonsAlert ou ActionSheet. As diferenças entre eles estão resumidas em apple.co/2GA57Rp.
  • Ações são uma coleção de botões de ação a serem incluídos no alerta. Observe que o número de ações depende do estilo do alerta, conforme descrito na documentação referenciada.

Aqui, o título e a mensagem são definidos como string.Empty, enquanto o estilo do alerta é definido como ActionSheet. Consequentemente, somente os botões de ação serão exibidos (consulte a Figura 1). Para garantir que os alertActions estejam prontos antes de o usuário tocar no botão Get list (Obter lista), recupero as fotos e os títulos no método Awake (como mostrado na Figura 7):

Figura 7 - Recuperar fotos e títulos

public async override void Awake(NSObject context)
{
  base.Awake(context);
  SetTitle("Hello, watch!");
  // Disable button until the photos are downloaded
  ButtonDisplayPhotoList.SetEnabled(false);
  // Get photos from the web service (first album only)
  photos = await PhotosClient.GetByAlbumId(1);
  // Create actions for the alert
  CreateAlertActions();
  ButtonDisplayPhotoList.SetEnabled(true);
}

Agora você pode executar o aplicativo. Quando o álbum de fotos for recuperado do servidor remoto, o botão será habilitado. Clique nele para ativar a folha de ações (mostrada na parte do meio da Figura 1).

Modo de exibição de tabela

Para concluir esta implementação, preciso criar o modo de exibição de tabela mostrando o grupo de fotos selecionado. Para isso, abro o designer de storyboard e, na caixa de ferramentas, arrasto o controle da Tabela até a cena (vou colocá-lo embaixo do botão Get list).

Na próxima etapa, preciso definir o layout da célula. Por padrão, esse layout tem um único controle: Grupo. Posso usá-lo como o pai de outros controles, mas primeiro preciso ter certeza de que o controle do grupo está ativo. Então, clico no painel Document Outline (Estrutura de Tópicos do Documento), visível na parte inferior da Figura 4, e, em seguida, clico em Group Control (Controle de Grupos), em Table/Table Row (Tabela/Linha da Tabela). Em seguida, arrasto os controles de Image (Imagem) e Label (Rótulo) para a tabela. Image e Label vão aparecer no modo de exibição de tabela e também em Document Outline. Vou configurar todas as propriedades dos controles da seguinte forma: Image (Nome: ImagePhotoPreview; Tamanho: Largura e Altura Fixas de 50 px), Rótulo (Nome: LabelPhotoTitle), Table (Nome: TablePhotos), Group (Tamanho: Altura fixa de 50 px).

É importante definir explicitamente os nomes dos controles para que você possa referenciá-los facilmente no código. Quando você especifica o nome do controle, o Visual Studio faz uma declaração apropriada no arquivo InterfaceController.designer.cs.

No watchOS, cada linha tem um controlador de linha dedicado, que você pode usar para controlar a aparência da linha. Aqui, usarei esse controlador para especificar o conteúdo da imagem e do rótulo de cada linha. Para criar o controlador de linha, selecione a Linha da Tabela no painel Estrutura de Tópicos do Documento e abra a guia Propriedades, onde você pode digitar PhotoRowController na caixa de texto Classe. Um novo arquivo, PhotoRowController.cs, será adicionado. Ele contém a classe do mesmo nome. Em seguida, complemento a definição dessa classe com outro método, da seguinte forma:

public async Task SetElement(Photo photo)
{
  Check.IsNull(photo);
  // Retrieve image data and use it to create UIImage
  var imageData = await PhotosClient.GetImageData(photo);
  var image = UIImage.LoadFromData(NSData.FromArray(imageData));
  // Set image and title
  ImagePhotoPreview.SetImage(image);
  LabelPhotoTitle.SetText(photo.Title);
}

A função SetElement aceita um argumento do tipo Photo (Foto) para exibir uma miniatura de foto com o título da foto na linha apropriada do modo de exibição da tabela. Em seguida, para realmente carregar e configurar linhas de tabela, estendo a definição de InterfaceController usando o seguinte método:

private async Task DisplaySelectedPhotos(RowSelection rowSelection)
{
  TablePhotos.SetNumberOfRows(rowSelection.RowCount, "default");
  for (int i = rowSelection.BeginIndex, j = 0;
    i <= rowSelection.EndIndex; i++, j++)
  {
    var elementRow = (PhotoRowController)TablePhotos.GetRowController(j);
    await elementRow.SetElement(photos.ElementAt(i));
  }
}

RowSelection é passado ao método DisplaySelectedPhotos para fornecer as informações necessárias sobre as linhas a serem exibidas. Mais especificamente, a propriedade RowCount é usada para definir o número de linhas a serem adicionadas à tabela (TablePhotos.SetNumberOfRows). Em seguida, DisplaySelectedPhotos itera por meio das linhas da tabela para definir o conteúdo de cada linha. Em cada iteração, primeiro obtenho uma referência ao PhotoRowController associado à linha atual. Fornecida essa referência, chamo o método PhotoRowController.SetElement para obter os dados e o título da imagem, que são exibidos na célula da tabela.

Por fim, após executar o aplicativo, você obterá os resultados mostrados anteriormente na Figura 1.

Conclusão

Neste artigo, mostrei como desenvolver aplicativos watchOS com o Xamarin, o Visual Studio para Mac e uma base de código C# .NET compartilhada implementada na biblioteca de classes .NET Standard. Explorei alguns dos elementos mais importantes dos aplicativos watchOS, incluindo a estrutura dos aplicativos, os controladores de interface e alguns controladores da interface do usuário (botão, folha de ações, modo de exibição de tabela). Como a base de códigos compartilhada é implementada usando-se a mesma abordagem empregada com aplicativos móveis, você pode estender facilmente sua solução móvel abranger dispositivos portáteis inteligentes. Você pode obter mais informações sobre o watchOS na Apple, em apple.co/2EFLeaL, bem como na documentação detalhada do Xamarin, em bit.ly/2ohSwLU.


Dawid Borycki é engenheiro de software, pesquisador em biomédica, autor e conferencista. Ele gosta de aprender novas tecnologias para experimentação de software e protótipos.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Brad Umbaugh
Brad Umbaugh escreve sobre iOS e a documentação relacionada para a equipe do Xamarin, na Microsoft. Entre em contato com ele no Twitter @bradumbaugh, onde ele basicamente retuíta piadas infames.


Discuta esse artigo no fórum do MSDN Magazine