Aplicação móvel com geração automática de formulários: nosso caso

Aplicativos móveis nem sempre são simples e concisos, como nós os desenvolvedores adoramos. Outros aplicativos são criados para resolver problemas complexos do usuário e contêm muitas telas e scripts. Por exemplo, aplicativos para a realização de testes, questionários e pesquisas - sempre que você precisar preencher muitos formulários no processo. Esta aplicação será discutida neste artigo.



Começamos a desenvolver um aplicativo móvel para agentes envolvidos no registro de apólices de seguros no local. Eles preenchem grandes formulários no aplicativo com dados do cliente: informações sobre o carro, proprietários, motoristas, etc. Embora cada formulário tenha suas próprias seções, células e estrutura, e cada item do questionário exija um tipo de dados exclusivo (sequência, data, documento em anexo), os formulários de tela eram bastante semelhantes. Mas o principal é o número deles ... Ninguém quer se envolver na repetição da visualização e no processamento dos mesmos elementos muitas vezes.

Para evitar as muitas horas de trabalho manual na criação de formulários, você precisa aplicar um pouco de criatividade e muita construção dinâmica da interface do usuário. Neste artigo, queremos compartilhar como resolvemos esse problema.

Para uma solução elegante para o problema, usamos o mecanismo para gerar objetos - ViewModels, que são usados ​​para criar formulários personalizados usando tabelas.



No trabalho normal, para cada tabela individual que o desenvolvedor deseja ver na tela, uma classe ViewModel separada deve ser criada. Ele define o componente visual da tabela. Decidimos subir um nível acima e gerar ViewModels e Models dinamicamente, usando uma descrição simples da estrutura através dos campos Enum.

Como isso funciona


Tudo começou com enum. Para cada perfil, criamos uma enumeração única - essas são nossas seções do perfil. Um de seus métodos é retornar a matriz de células nesta seção.

As células na tabela também serão enumeradas com funções adicionais que descreverão as propriedades das células. Em tais funções, definimos o nome da célula, o valor inicial. Posteriormente, adicionou parâmetros como

  • display check: algumas células devem estar ocultas,
  • lista de células “principais”: células das quais depende o valor, validação ou exibição dessa célula,
  • tipo de célula: células simples com valores, células no switch, células com a função de adicionar elementos, etc.

Assinamos todas as seções do protocolo geral QuestionnaireSectionCellType para excluir a ligação a uma seção específica; faremos o mesmo com todas as células da tabela (QuestionnaireCellType).

protocol QuestionnaireSectionCellType { var title: String { get } var sectionCellTypes: [QuestionnaireCellType] { get } } protocol QuestionnaireCellType { var title: String { get } var initialValue: Any? { get } var isHidden: Bool { get } var parentFields: [QuestionnaireCellType] { get } … } 

Esse modelo será muito fácil de preencher. Simplesmente percorremos todas as seções, em cada seção percorremos uma matriz de células e as adicionamos à matriz do modelo.

No exemplo da tela do tomador de seguro (enumeração com seções - InsurantSectionType):

 final class InsurantModel: BaseModel<QuestionnaireCellType> { override init() { super.init() initParameters() } private func initParameters() { InsurantSectionType.allCases.forEach { type in type.sectionCellTypes.forEach { if let valueModel = ValueModel(type: $0, parentFields: $0.parentFields, value: $0.initialValue) { valueModels.append(valueModel) } } } } } 

Feito! Agora temos uma tabela com valores iniciais. Adicione métodos para ler o valor com a chave QuestionnaireCellType e salve-o no elemento de matriz desejado.

Alguns modelos podem ter campos opcionais, portanto, adicionamos uma matriz com chaves opcionais. Durante a validação do modelo, essas chaves podem não conter valores, mas o modelo será considerado preenchido.

Além disso, por conveniência, todos os valores no ValueModel que subscrevemos no protocolo comum StringRepresentable protocol para limitar a lista de valores possíveis e adicionar um método para exibir o valor na célula.

 protocol StringRepresentable { var stringValue: String? { get } } 

A funcionalidade aumentou e muitas outras propriedades e métodos apareceram nos modelos: limpeza do modelo (os valores iniciais devem ser definidos em alguns modelos), suporte para uma matriz dinâmica de valores (valor: Matriz), etc.

Essa abordagem acabou sendo muito conveniente para armazenar no banco de dados usando o Realm. Para preencher o questionário, é possível selecionar um modelo completo salvo anteriormente. Para estender a política CTP, o agente não precisará mais preencher os documentos do usuário, os drivers anexados por ele e os dados TCP do novo. Em vez disso, você pode simplesmente reutilizá-lo para preencher o existente.

Para alterar ou suplementar tabelas, você só precisa encontrar o ViewModel relacionado a uma tela específica, encontrar a enumeração necessária para exibir o bloco desejado e adicionar ou corrigir vários casos. Tudo, a mesa terá a forma necessária!

O preenchimento do formulário com os valores dos testes também foi muito conveniente e rápido. Dessa forma, você pode gerar rapidamente quaisquer dados de teste. E se você adicionar um arquivo separado com os dados iniciais, de onde o programa levará o valor para cada campo específico do questionário, mesmo um iniciante poderá gerar questionários prontos, sem precisar desmontar o restante do código, exceto um arquivo específico.

Dependências


Uma tarefa separada que resolvemos durante o processo de desenvolvimento é o tratamento de dependências. Alguns elementos do questionário foram interconectados. Portanto, o número do documento não pode ser preenchido sem a escolha do tipo do documento, o número da casa não pode ser indicado sem a indicação da cidade e da rua etc.



Fizemos a atualização dos valores do questionário com a limpeza de todos os campos dependentes (por exemplo, ao excluir ou alterar o tipo de um documento, limpamos o campo "número do documento"):

 func updateValueModel(value: StringRepresentable?, for type: QuestionnaireCellType) { guard let model = valueModels.first(where: { $0.type.equal(to: type) }) else { return } model.value = value clearRelativeValues(type: type) } func clearRelativeValues(type: QuestionnaireCellType) { _ = valueModels.filter { $0.parentFields.contains(where: { $0.equal(to: type) }) } .compactMap { $0.type } .compactMap { updateValueModel(value: nil, for: $0) } } 

Armadilhas que tivemos que resolver durante o desenvolvimento e como conseguimos


É claro que esse método é conveniente para telas com a mesma funcionalidade (preenchimento dos campos), mas não é tão conveniente se elementos ou funções exclusivas aparecerem em uma tela separada e não em outras telas. Em nossa aplicação, foram eles:

  • Uma tela com a potência do motor, que teve que ser gerada separadamente, e é por isso que diferia na funcionalidade. Nesta tela, a solicitação deve desaparecer e o valor do servidor é substituído automaticamente. Eu tive que criar separadamente uma classe para ela, que seria responsável por exibir, carregar, validar, carregar do servidor e substituir um valor em um campo vazio, sem incomodar o usuário se este decidir inserir seu próprio valor.
  • A tela do número de registro, na qual o único é o comutador, que afeta a exibição ou ocultação do campo de texto. Para este caso, uma condição adicional teve que ser feita, que determinaria programaticamente os casos com a posição do interruptor ativada como um valor vazio.
  • Listas dinâmicas, como uma lista de drivers que precisavam ser armazenados e vinculados a um formulário, também ficaram fora de conceito.
  • Tipos exclusivos de validação de dados. Pode haver muitas máscaras misturadas com regex'ami. E validação de data para vários campos, onde a validação diferiu dramaticamente (restrições nos valores mínimo / máximo), etc.
  • As telas de entrada de dados são feitas como células collectionView. (Isso foi exigido pelo design!) Por causa disso, a exibição de janelas modais exigia controle preciso sobre o índice selecionado. Eu tive que verificar os campos disponíveis para preenchimento e excluir da lista aqueles que o usuário não deveria ver.
  • Para exibir corretamente os dados na tabela, foi necessário fazer alterações nos métodos de modelo de algumas telas. Células como nome e endereço são exibidas na tabela como um único elemento, mas requerem que várias telas pop-up sejam totalmente preenchidas.

Conclusão


Essa experiência nos permitiu na True Engineering implementar rapidamente um aplicativo móvel fácil de manter. A versatilidade permite gerar rapidamente tabelas com diferentes tipos de dados de entrada: criamos 20 janelas em apenas uma semana. Essa abordagem também acelera o processo de teste de aplicativos. Num futuro próximo, reutilizaremos a fábrica pronta para gerar rapidamente novas tabelas e novas funcionalidades.

Source: https://habr.com/ru/post/pt459430/


All Articles