Usando Reflection para gerar formulários dinamicamente com Swift

Utilizando a Mirror, API de Reflection nativa do Swift

Paulo Lourenço
Nov 5 · 3 min read

O problema

Recentemente, em um trabalho da faculdade, tive que criar um aplicativo que consumia até 10 serviços diferentes, sendo que cada serviço possuía um objeto de request específico, com diferentes propriedades.

Imagina que loucura (e bagunça) seria criar uma tela com um formulário gigante, cheia de textfields e tendo que associar cada textfield a um request específico, além de tratar a visibilidade (isHidden) dos campos baseado nos serviços que o usuário selecionou para consumir. Inviável, né?!

A solução

A solução que encontrei foi usar reflection para iterar sobre as propriedades de um objeto e, para cada uma, criar um UITextField dinamicamente, gerando o formulário em tempo de execução.

Primeiramente vamos criar um protocolo chamado RequestBase, que deverá ser implementado por todos os objetos de request e que terá funções em comum entre eles, como o método "obterNomeDoServico()", que usaremos para separar os formulários pelo nome do serviço respectivo.

Agora, criamos os objetos de request de cada serviço que queiramos consumir. Vou criar 2 apenas como exemplo:

Agora, para iterarmos sobre as propriedades dos objetos, sem precisar conhece-las, usamos a API de Reflection nativa do Swift, chamada Mirror.

Segue um pequeno exemplo de como usar o Mirror:

Nesse caso, apenas damos um print em todas as propriedades do objeto Teste, exibindo o nome e valor delas. Segue resultado:

Agora que você já tem uma pequena noção de como funciona o Mirror / Reflection, vamos usá-lo para gerar um formulário completo dos serviços que precisamos consumir, criando os campos de texto e adicionando-os em uma UIStackView:

Com isso já temos o seguinte resultado:

Setando os novos valores

Já estamos conseguindo ler as propriedades e, a partir delas, criar o formulário. Mas como fazer para, ao digitar no campo de texto, atualizar os valores das propriedades dos objetos?

Para isso, vamos criar um UITextField personalizado, de forma que a gente consiga instancia-lo já passando uma closure, que deverá ser executada no evento de editingChanged dele.

Também precisaremos fazer uma pequena refatoração no protocolo e nos objetos que criamos anteriormente para que consigamos utilizar o método setValue(_:forKey:). Esse método será chamado dentro da closure de onChange do novo TextField para atualizar o valor conforme digitamos algo no campo de texto.

Primeiro, atualizamos o protocolo, obrigando que as classes que o implementarem também implementem NSObject:

Agora também precisamos atualizar os objetos que implementam o protocolo:

Como você pode ver, também precisamos adicionar o annotation "@objc" em cada propriedade, o que faz com que a propriedade seja acessível pelos métodos de runtime do Objective-C, como o setValue, que usaremos.

Agora só precisamos alterar o método que gera o formulário para instanciar o BindableTextField que criamos ao invés do UITextField padrão. Segue:

Pronto! Agora já conseguimos ler e atualizar os valores de todas as propriedades dos objetos, tudo dinamicamente.

Nesses exemplos eu só usei propriedades do tipo String e Int, mas é possível fazer com qualquer tipo. Portanto, fica como "lição de casa" (🤣) criar uma forma de trabalhar com campos de Enums e Arrays.

Segue código fonte deste projeto:

Caso queira ver o projeto do trabalho da faculdade em que usei isso de forma um pouco mas avançada, tratando enums e arrays, segue:

😉

Paulo Lourenço

Written by

Senior Mobile Software Engineer @ Redecard (Itaú)