iOS — Personalização de componentes com UIAppearance

Elias Medeiros
CWI Software
Published in
4 min readMay 4, 2018

Quando usamos componentes de UI nativos em iOS ganhamos muita produtividade, afinal eles já estão prontinhos e testados e muitos deles nos dão fácil acesso à sua lógica de comportamento através de delegates. E ganhamos de barbada sua adaptabilidade às variações de tela, o que se tornou ainda mais relevante desde o iPhone X.

Porém uma das principais dificuldades ao lidar com estes componentes é personalizá-los visualmente em alguns casos. Por exemplo, é muito fácil utilizar uma UISearchBar para filtrar os dados de uma tabela, além de termos várias configurações visuais disponíveis sobre ela. Mas e se tivermos alguma necessidade específica não disponível para configuração externa? Digamos que o nosso placeholder no campo busca precise ser um texto ridiculamente longo e não possamos conviver com as reticências que serão exibidas em uma tela menor, algo assim:

e agora? 😕

Podemos criar nosso próprio componente de busca, e ali dentro fazer tudo do nosso jeito. Ou podemos dar um jeito de alterar o comportamento deste label — sim, o placeholder do UITextField nada mais é que um UILabel. Faremos isso usando um método definido pelo protocolo UIAppearance.

Criei um repositório com um app bem simples para demonstrar alguns usos deste protocolo. Nele, eu usei a barra de busca embutida na barra de navegação, um recurso muito legal do iOS 11.

Para resolver a questão das reticências, criamos um serviço para encapsular as personalizações necessárias.

Nele utilizamos o método appearance, que retorna um proxy de uma instância da classe na qual o invocamos, no nosso caso, um UILabel. Como podemos ver, esse proxy nos dá acesso a todas propriedades e métodos que utilizaríamos para personalizar individual e manualmente instâncias desta classe. Por fim, chamamos este serviço dentro doAppDelegate:

Resultado:

Conseguimos. E o melhor: se tivermos outras barras de busca como esta em outras telas, o comportamento já estará aplicado a elas, sem qualquer esforço extra necessário, pois nossa alteração se aplica a quaisquer instâncias de UILabel que vierem a ser criadas.

No dia seguinte, alguém decide que nosso texto de busca precisa de uma leve sombra, por algum motivo obscuro. Em épocas remotas, diríamos que esta alteração não valeria o custo, afinal estamos utilizando um componente nativo e precisaríamos desenvolver tudo do zero para chegar a este nível de detalhe. Mas hoje não! A resposta é "deixa comigo", votamos essa tarefa como minúscula na planning e lá vamos nós customizar nossa aparência mais um pouco, já temos tudo pronto, é só adicionar uma linha de código, algo como labelAppearance.shadowColor = .gray. Rodamos o app para visualizar a alteração e:

Não está bom. Vários outros labels foram afetados indevidamente. Para ter mais controle sobre o que estamos alterando, podemos utilizar outras assinaturas do método appearance.

O método é auto-descritivo: estamos trabalhando com labels que estejam dentro de uma search bar e nenhum outro. Desta forma, evitamos os efeitos colaterais que ocorreram antes.

Mais alguns dias se passam e chegam à conclusão que a sombra não ficou legal em telas pequenas de iPhone. Porém, no iPad ficou uma beleza. ¯\_(ツ)_/¯

Como já vimos a documentação de UIAppearance, sabemos que podemos utilizar outro parâmetro na nossa chamada, especificando ainda mais os casos onde a nossa alteração será aplicada. Nosso método, então, ficaria assim:

Ao definir uma trait collection para o parâmetro for, dizemos, neste caso, que as alterações deste proxy somente serão aplicadas a dispositivos que tenham tamanho horizontal regular, em vez de compacto, que é o caso dos iPad's.

Detalhe: isto também engloba os iPhone's Plus quando na orientação horizontal, pois sua categoria de tamanho muda quando o viramos de lado. Se quisermos excluir esta variante, podemos basear nosso filtro também no trait verticalSizeClass, que será sempre compacta para iPhone's e regular para iPad's. A versão final disponível no repositório git estará assim.

Com isso, chegamos ao resultado que tínhamos antes no iPhone e este aqui no iPad:

Enfim, aplicar uma personalização com este método é produtivo e indolor. Ele permite tirar um pouco a carga de fazer estas definições dentro do Interface Builder, seja em storyboard's ou xib's, fazendo com que alterações globais posteriores nos causem um menor retrabalho; e com as opções de seleção que temos no método appearance, podemos obter comportamentos bem definidos e não conflitantes. Então faça alguns testes e nos conte como foi :)

--

--

Elias Medeiros
CWI Software

Enfileirar palavras é meu ofício; algumas vezes em português, outras em Swift ou Kotlin :)