Aplicación móvil con generación automática de formularios: nuestro caso

Las aplicaciones móviles no siempre son simples y concisas, ya que a los desarrolladores nos encanta. Se crean otras aplicaciones para resolver problemas complejos del usuario y contienen muchas pantallas y scripts. Por ejemplo, las solicitudes para realizar pruebas, cuestionarios y encuestas, siempre que necesite completar muchos formularios en el proceso. Esta aplicación se discutirá en este artículo.



Comenzamos a desarrollar una aplicación móvil para agentes que participan en el registro in situ de pólizas de seguro. Completan formularios grandes en la solicitud con datos del cliente: información sobre el automóvil, los propietarios, los conductores, etc. Aunque cada formulario tiene sus propias secciones, celdas y estructura, y cada elemento del cuestionario requiere un tipo de datos único (cadena, fecha, documento adjunto), los formularios de pantalla eran bastante similares. Pero lo principal es su número ... Nadie quiere participar en la repetición de la visualización y el procesamiento de los mismos elementos muchas veces.

Para evitar las muchas horas de trabajo manual en la creación de formularios, debe aplicar un poco de ingenio y mucha construcción dinámica de la interfaz de usuario. En este artículo, queremos compartir cómo resolvimos este problema.

Para una solución elegante al problema, utilizamos el mecanismo para generar objetos: ViewModels, que se utilizan para crear formularios personalizados utilizando tablas.



En el trabajo normal, para cada tabla individual que el desarrollador quiera ver en la pantalla, se debe crear una clase ViewModel separada. Define el componente visual de la tabla. Decidimos ir un nivel más arriba y generar ViewModels y Modelos nosotros mismos dinámicamente, usando una descripción simple de la estructura a través de los campos Enum.

Como funciona


Todo comenzó con enum. Para cada perfil creamos una enumeración única: estas son nuestras secciones del perfil. Uno de sus métodos es devolver la matriz de celdas en esta sección.

Las celdas de la tabla también serán enumeradas con funciones adicionales que describirán las propiedades de las celdas. En tales funciones, establecemos el nombre de la celda, el valor inicial. Parámetros añadidos más tarde, como

  • verificación de visualización: algunas celdas deben estar ocultas,
  • lista de celdas "primarias": celdas de las que depende el valor, la validación o la visualización de esta celda,
  • tipo de celda: celdas simples con valores, celdas en switch, celdas con la función de agregar elementos, etc.

Suscribimos todas las secciones al protocolo general QuestionnaireSectionCellType para excluir la unión a una sección específica, haremos lo mismo con todas las celdas de la tabla (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 } … } 

Tal modelo será muy fácil de completar. Simplemente corremos a través de todas las secciones, en cada sección corremos a través de una matriz de celdas y las agregamos a la matriz del modelo.

En el ejemplo de la pantalla del asegurado (enumeración con secciones - 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) } } } } } 

Hecho Ahora tenemos una tabla con valores iniciales. Agregue métodos para leer el valor con la clave QuestionnaireCellType y guardarlo en el elemento de matriz deseado.

Algunos modelos pueden tener campos opcionales, por lo que agregamos una matriz con teclas opcionales. Durante la validación del modelo, estas claves pueden no contener valores, pero el modelo se considerará completado.

Además, por conveniencia, todos los valores en el ValueModel suscribimos al protocolo común StringRepresentable del protocolo para limitar la lista de valores posibles y agregar un método para mostrar el valor en la celda.

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

La funcionalidad creció y aparecieron muchas otras propiedades y métodos en los modelos: limpieza del modelo (los valores iniciales deben establecerse en algunos modelos), soporte para una matriz dinámica de valores (valor: matriz), etc.

Este enfoque resultó ser muy conveniente para almacenar en la base de datos usando Realm. Para completar el cuestionario, es posible seleccionar un modelo completado previamente guardado. Para extender la política de CTP, el agente ya no necesitará completar los documentos del usuario, los controladores adjuntos y los datos de TCP para el nuevo. En cambio, simplemente puede reutilizarlo para completar el existente.

Para cambiar o complementar tablas, solo necesita encontrar el ViewModel relacionado con una pantalla en particular, encontrar la enumeración necesaria que es responsable de mostrar el bloque deseado y agregar o corregir varios casos. ¡Todo, la mesa tomará la forma necesaria!

Completar el formulario con los valores de prueba también fue muy conveniente y rápido. De esta manera, puede generar rápidamente cualquier dato de prueba. Y si agrega un archivo separado con los datos iniciales, desde donde el programa tomará el valor en cada campo específico del cuestionario, incluso un principiante puede generar cuestionarios listos sin entrar y desensamblar el resto del código, excepto un archivo específico.

Dependencias


Una tarea separada que resolvimos durante el proceso de desarrollo es el manejo de dependencias. Algunos elementos del cuestionario estaban interconectados. Por lo tanto, el número de documento no se puede completar sin elegir el tipo de este documento, el número de casa no se puede indicar sin indicar la ciudad y la calle, etc.



Realizamos la actualización de los valores del cuestionario borrando todos los campos dependientes (por ejemplo, eliminando o cambiando el tipo de documento, borramos el campo "número de 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) } } 

Problemas que tuvimos que resolver durante el desarrollo y cómo logramos


Está claro que este método es conveniente para pantallas con la misma funcionalidad (rellenando los campos), pero no es tan conveniente si aparecen elementos o funciones únicos en una pantalla separada que no está en otras pantallas. En nuestra aplicación, estos fueron:

  • Una pantalla con potencia del motor, que tenía que generarse por separado, por lo que difería en funcionalidad. En esta pantalla, la solicitud debe desaparecer y el valor del servidor se sustituye automáticamente. Tuve que crear por separado una clase para él que sería responsable de mostrar, cargar, validar, cargar desde el servidor y sustituir un valor en un campo vacío, sin molestar al usuario si este último decide ingresar su propio valor.
  • La pantalla del número de registro, en la que el único es el interruptor, que afecta la visualización u ocultación del campo de texto. Para este caso, se tenía que hacer una condición adicional, que determinaría mediante programación los casos con la posición del interruptor activada como un valor vacío.
  • Listas dinámicas, como una lista de controladores que tuvieron que almacenarse y vincularse a un formulario, que también salió del concepto.
  • Tipos únicos de validación de datos. Podría haber muchas máscaras mezcladas con regex'ami. Y la validación de la fecha para varios campos, donde la validación difería dramáticamente (restricciones en los valores mínimos / máximos), etc.
  • Las pantallas de entrada de datos se hacen como celdas de collectionView. (¡Eso fue requerido por el diseño!) Debido a esto, mostrar ventanas modales requería un control preciso sobre el índice seleccionado. Tuve que verificar los campos disponibles para rellenar y excluir de la lista aquellos que el usuario no debería ver.
  • Para mostrar correctamente los datos en la tabla, fue necesario realizar cambios en los métodos del modelo de algunas pantallas. Las celdas, como el nombre y la dirección, se muestran en la tabla como un solo elemento, pero requieren varias pantallas emergentes para estar completamente pobladas.

Conclusión


Esta experiencia nos permitió en True Engineering implementar rápidamente una aplicación móvil que es fácil de mantener. La versatilidad le permite generar rápidamente tablas con diferentes tipos de datos de entrada: creamos 20 ventanas en solo una semana. Este enfoque también acelera el proceso de prueba de aplicaciones. En un futuro cercano, reutilizaremos la fábrica terminada para generar rápidamente nuevas tablas y nuevas funcionalidades.

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


All Articles