Conhece o Meetup?
Meetup é uma rede social que tem o objetivo de facilitar reuniões em grupo offline. E por que estou falando disso? Apenas para dar uma introdução no assunto.
O que quero falar hoje é sobre como fazer o título clicável do app Meetup para iOS em um app desenvolvido com Xamarin.Forms.
Uma das coisas que me chamou atenção nesse app é a tela inicial, onde mostra o nome da cidade que você configurou, e também uma imagem de uma seta ao lado. Na verdade, esses dois elementos são um botão, que te permite clicar e ir para uma outra tela, onde você poderá redefinir a cidade selecionada.

Tela inicial do Meetup
Achei essa abordagem bem bacana e inteligente, pois no geral, não temos muito espaço na tela para encher-la de botões, nem fica muito agradável para o usuário. Então, quanto mais otimizarmos o espaço livre, melhor.
Por isso pensei, como fazer o título do app Meetup em um aplicativo utilizando Xamarin.Forms? Não foi difícil e consegui fazer a implementação dele para o iOS. Futuramente irei implementar também para Android e atualizo o post aqui pra vocês, beleza?
Então vamos prosseguir?
Para começar, criei uma ContentPage para a página inicial e especifiquei os dois atributos configuráveis para texto e ícone. Sem a customização do render, você não verá texto e imagem lado a lado, somente um deles aparecerá.
<?xml version="1.0" encoding="utf-8"?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Core.Views.HomeView" Title="Blumenau" Icon="arrow_down.png" > <ContentPage.Content> <Label Text="Home" VerticalOptions="Center" HorizontalOptions="Center" /> </ContentPage.Content> </ContentPage>
Agora, para iniciar as customiazações, iniciei a implementação do custom render.
Minha tentativa inicial foi criar um render para o NavigationRenderer, mas não obti sucesso. Depois de algumas pesquisas, achei uma implementação parecida com o que eu queria, que utilizava o PageRenderer.
Dessa forma, criei um custom render para o PageRenderer, fazendo override do método WillMoveToParentViewController.
using Core.iOS.Renderers; using UIKit; using Xamarin.Forms; using Xamarin.Forms.Platform.iOS; [assembly: ExportRenderer(typeof(ContentPage), typeof(CustomContentPageRenderer))] namespace Core.iOS.Renderers { public class CustomContentPageRenderer : PageRenderer { public override void WillMoveToParentViewController(UIViewController parent) { base.WillMoveToParentViewController(parent); } } }
Com isso, já é possível começar a modificar o render da ContentPage do nosso app feito utilizando Xamarin.Forms. Então, vamos programar neste método…
Nesse render, a lógica será: Carregar a instância da ContentPage que criamos lá com o XAML; Ler os seus atributos Title e Icon; Criar um novo componente visual que será um botão, contendo o título e o ícone configurados. No final, vamos atualizar o título da NavigationPage com o novo elemento criado. Simples assim!
Para carregar a instância da ContentPage, vamos utilizar o atributo Element, que está definido na classe PageRenderer. Através dele iremos conseguir acessar as propriedades Title e Icon que definimos no XAML.
if (Element == null) return; var page = Element as ContentPage;
Agora, vamos criar um botão utilizando o UIButton, adicionar o título e o ícone localizado na ContentPage, e ainda, definir a cor do texto que irá aparecer. Vamos também adicionar esse novo objeto para ser renderizado no lugar do render padrão, utilizando a referência do UIViewController para acessar o NavigationItem.
UIButton buttonTitle = new UIButton(UIButtonType.Custom); buttonTitle.SetTitle(page.Title, UIControlState.Normal); buttonTitle.SetTitleColor(UIColor.Black, UIControlState.Normal); buttonTitle.SetImage(new UIImage(page.Icon), UIControlState.Normal); parent.NavigationItem.TitleView = buttonTitle;
Pronto, vamos ver o que dá isso:
Opa, parece que algo está errado, pois não apareceu o ícone. Mas não está errado, apenas não chamamos o método responsável por organizar o conteúdo na tela. Então, vamos chamar:
UIButton buttonTitle = new UIButton(UIButtonType.Custom); buttonTitle.SetTitle(page.Title, UIControlState.Normal); buttonTitle.SetTitleColor(UIColor.Black, UIControlState.Normal); buttonTitle.SetImage(new UIImage(page.Icon), UIControlState.Normal); buttonTitle.SizeToFit(); parent.NavigationItem.TitleView = buttonTitle;
Vamos conferir:
Melhorou! Mas a imagem está do lado esquerdo, não do lado direito. Dá pra inverter. Para isso, vamos especificar o atributo Transform do UIButton.
UIButton buttonTitle = new UIButton(UIButtonType.Custom); buttonTitle.SetTitle(page.Title, UIControlState.Normal); buttonTitle.SetTitleColor(UIColor.Black, UIControlState.Normal); buttonTitle.SetImage(new UIImage(page.Icon), UIControlState.Normal); buttonTitle.Transform = CGAffineTransform.MakeScale(-1.0f, 1.0f); buttonTitle.SizeToFit(); parent.NavigationItem.TitleView = buttonTitle;
Olha só como fica:
Ué, estou lendo árabe?? Não, é que invertemos a posição do botão. É como inverter horizontalmente em 180 graus. E agora, como resolver? Podemos aplicar também a propriedade Transform no título e no ícone, e aí vai dar o resultado que esperamos. Note que neste ícone, nem precisa aplicar a propriedade, mas se você tiver uma imagem que não possui os lados iguais, neste caso, vai precisar aplicar.
UIButton buttonTitle = new UIButton(UIButtonType.Custom); buttonTitle.SetTitle(page.Title, UIControlState.Normal); buttonTitle.SetTitleColor(UIColor.Black, UIControlState.Normal); buttonTitle.SetImage(new UIImage(page.Icon), UIControlState.Normal); buttonTitle.Transform = CGAffineTransform.MakeScale(-1.0f, 1.0f); buttonTitle.TitleLabel.Transform = CGAffineTransform.MakeScale(-1.0f, 1.0f); buttonTitle.ImageView.Transform = CGAffineTransform.MakeScale(-1.0f, 1.0f); buttonTitle.SizeToFit(); parent.NavigationItem.TitleView = buttonTitle;
Olha como ficou:
Bem melhor! Ficou mais parecido, mas ainda faltam algumas coisas. Note que o título e o ícone estão muito próximos. Podemos fazer uma “gambiarrazinha”, apenas colocando alguns espaços no fim do título, ou então, utilizar a propriedade ImageEdgeInsets para configurar o espaçamento, o que é o mais indicado:
UIButton buttonTitle = new UIButton(UIButtonType.Custom); buttonTitle.SetTitle(page.Title, UIControlState.Normal); buttonTitle.SetTitleColor(UIColor.Black, UIControlState.Normal); buttonTitle.SetImage(new UIImage(page.Icon), UIControlState.Normal); buttonTitle.Transform = CGAffineTransform.MakeScale(-1.0f, 1.0f); buttonTitle.TitleLabel.Transform = CGAffineTransform.MakeScale(-1.0f, 1.0f); buttonTitle.ImageView.Transform = CGAffineTransform.MakeScale(-1.0f, 1.0f); buttonTitle.ImageEdgeInsets = new UIEdgeInsets(0, -20, 0, 0); buttonTitle.SizeToFit(); parent.NavigationItem.TitleView = buttonTitle;
Conferindo:
Estamos quase lá! O que falta? Algumas perfumarias… Se você conferir no app do Meetup, tem mais duas características naquele título. Uma é o título em negrito e a outra é quando, ao clicarmos nele o mesmo fica em tom de cinza. Podemos fazer isso facilmente ajustando a fonte e definindo a cor cinza quando o estado do botão estiver clicado.
UIButton buttonTitle = new UIButton(UIButtonType.Custom); buttonTitle.SetTitle(page.Title, UIControlState.Normal); buttonTitle.SetTitleColor(UIColor.Black, UIControlState.Normal); buttonTitle.SetImage(new UIImage(page.Icon), UIControlState.Normal); buttonTitle.Transform = CGAffineTransform.MakeScale(-1.0f, 1.0f); buttonTitle.TitleLabel.Transform = CGAffineTransform.MakeScale(-1.0f, 1.0f); buttonTitle.ImageView.Transform = CGAffineTransform.MakeScale(-1.0f, 1.0f); buttonTitle.ImageEdgeInsets = new UIEdgeInsets(0, -20, 0, 0); buttonTitle.SetTitleColor(UIColor.Gray, UIControlState.Highlighted); buttonTitle.TitleLabel.Font = UIFont.BoldSystemFontOfSize(14); buttonTitle.SizeToFit(); parent.NavigationItem.TitleView = buttonTitle;
Vamos conferir:
Olha aí, não é que deu certo!
E agora, o que falta? Falta uma característica, a principal, na minha opinião: esse botão precisa ser clicável para podermos direcionar o app para outra página. Para isso, podemos implementar o evento TouchUpInside do UIButton.
Mas agora as coisas começam a ficar um pouco mais complicadas. Pense comigo: até o momento, utilizamos o título e o ícone, atributos esses que já existem na ContentPage. Mas nela não existe nenhum atributo onde possamos vincular uma ação ou evento para poder realizar uma operação.
Acredito que o ideal seria estendermos a ContentPage e implementarmos um comando para que possamos efetuar a operação no custom render. Aí, quando clicarmos no botão, vamos capturar o evento no TouchUpInside e iremos chamar o comando, que estará setado na nossa ContentPage. Vamos tentar?
Primeiro, vamos estender a ContentPage, criei uma classe chamada CustomContentPage. Nela, implementei uma bindable property chamada Command.
using System.Windows.Input; using Xamarin.Forms; namespace Core.Controls { public class CustomContentPage : ContentPage { public static readonly BindableProperty CommandProperty = BindableProperty.Create( nameof(Command), typeof(ICommand), typeof(CustomContentPage), null ); public ICommand Command { get { return (ICommand)GetValue(CommandProperty); } set { SetValue(CommandProperty, value); } } } }
No code behind da página, precisei alterar a herança da ContentPage para a nova página criada. Você também terá que fazer isso.
using Core.Controls; using Core.ViewModels; namespace Core.Views { public partial class HomeView : CustomContentPage { public HomeView() { InitializeComponent(); BindingContext = new HomeViewModel(); } } }
O XAML também precisou sofrer alterações, pois agora a página herda de CustomContentPage, portanto, precisamos alterar essa referência lá também. Notem que também foi preciso declarar o namespace onde a página que criei está localizada.
<?xml version="1.0" encoding="utf-8"?> <controls:CustomContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:controls="clr-namespace:Core.Controls" x:Class="Core.Views.HomeView" Title="Blumenau" Icon="arrow_down.png" > <ContentPage.Content> <Label Text="Home" VerticalOptions="Center" HorizontalOptions="Center" /> </ContentPage.Content> </controls:CustomContentPage>
Agora, podemos adicionar um comando na propriedade Command que tem a CustomContentPage.
<?xml version="1.0" encoding="utf-8"?> <controls:CustomContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:controls="clr-namespace:Core.Controls" x:Class="Core.Views.HomeView" Title="Blumenau" Icon="arrow_down.png" Command="{Binding OpenOptionCommand}" > <ContentPage.Content> <Label Text="Home" VerticalOptions="Center" HorizontalOptions="Center" /> </ContentPage.Content> </controls:CustomContentPage>
OMG! Mas que loucura! Calma que ainda piora. Agora vamos voltar lá para nosso custom render e fazer algumas customizações.
Primeiro, iremos alterar a declaração do ExportRenderer para realizarmos o vínculo com a CustomContentPage, não mais com a ContentPage.
[assembly: ExportRenderer(typeof(CustomContentPage), typeof(CustomContentPageRenderer))] namespace Core.iOS.Renderers { public class CustomContentPageRenderer : PageRenderer { ... } }
Depois vamos customizar a implementação do WillMoveToParentViewController, fazendo o cast do Element para a classe CustomContentPage e implementando o evento TouchUpInside.
public override void WillMoveToParentViewController(UIViewController parent) { base.WillMoveToParentViewController(parent); if (Element == null) return; var page = Element as CustomContentPage; UIButton buttonTitle = new UIButton(UIButtonType.Custom); buttonTitle.SetTitle(page.Title, UIControlState.Normal); buttonTitle.SetImage(new UIImage(page.Icon), UIControlState.Normal); buttonTitle.SetTitleColor(UIColor.Black, UIControlState.Normal); buttonTitle.Transform = CGAffineTransform.MakeScale(-1.0f, 1.0f); buttonTitle.TitleLabel.Transform = CGAffineTransform.MakeScale(-1.0f, 1.0f); buttonTitle.ImageView.Transform = CGAffineTransform.MakeScale(-1.0f, 1.0f); buttonTitle.ImageEdgeInsets = new UIEdgeInsets(0, -20, 0, 0); buttonTitle.SetTitleColor(UIColor.Gray, UIControlState.Highlighted); buttonTitle.TitleLabel.Font = UIFont.BoldSystemFontOfSize(14); buttonTitle.SizeToFit(); buttonTitle.TouchUpInside += (sender, e) => { if (page.Command == null) return; if (page.Command.CanExecute(null)) page.Command.Execute(null); }; parent.NavigationItem.TitleView = buttonTitle; }
Pronto! E qual é o resultado disso?

Exemplo da tela implementada
Olha aí.. Não é que deu certo mesmo!!
Esse post ficou muito grande, talvez seja melhor eu publicar as customizações do Android em um novo post. Ainda não fiz a implementação, talvez demore um pouco. Assim que tiver novidades comunico a vocês.
O código de exemplo utilizado no post está no GitHub.
Abraço, aguardo feedback. Críticas e sugestões são sempre bem vindas ( e dinheiro na minha conta também, brincadeirinha rsrs ).
Parabéns, ficou muito bom! Continue escrevendo artigos como esse!
Obrigado, Daniel. Vou continuar sim!
Muito bom mesmo Ioni, parabéns.
Muito obrigado, Jonatan!