Mobile Anwendungen sind nicht immer einfach und prägnant, da wir Entwickler es lieben. Andere Anwendungen werden erstellt, um komplexe Benutzerprobleme zu lösen und enthalten viele Bildschirme und Skripte. Zum Beispiel Anwendungen für die Durchführung von Tests, Fragebögen und Umfragen - überall dort, wo Sie viele Formulare ausfüllen müssen. Diese Anwendung wird in diesem Artikel behandelt.

Wir haben begonnen, eine mobile Anwendung für Agenten zu entwickeln, die Versicherungspolicen vor Ort registrieren. Sie füllen große Formulare in der Anwendung mit Kundendaten aus: Informationen über das Auto, Besitzer, Fahrer usw. Obwohl jedes Formular seine eigenen Abschnitte, Zellen und Strukturen hat und für jedes Fragebogenelement ein eindeutiger Datentyp (Zeichenfolge, Datum, angehängtes Dokument) erforderlich ist, waren die Bildschirmformulare ziemlich ähnlich. Aber die Hauptsache ist ihre Anzahl ... Niemand möchte sich mehrmals auf die Wiederholung der Visualisierung und Verarbeitung derselben Elemente einlassen.
Um die vielen Stunden manueller Arbeit beim Erstellen von Formularen zu vermeiden, müssen Sie ein wenig Einfallsreichtum und viel dynamische Konstruktion der Benutzeroberfläche anwenden. In diesem Artikel möchten wir Ihnen mitteilen, wie wir dieses Problem gelöst haben.
Für eine elegante Lösung des Problems haben wir den Mechanismus zum Generieren von Objekten verwendet - ViewModels, mit denen benutzerdefinierte Formulare mithilfe von Tabellen erstellt werden.

Bei der normalen Arbeit muss für jede einzelne Tabelle, die der Entwickler auf dem Bildschirm sehen möchte, eine separate ViewModel-Klasse erstellt werden. Es definiert die visuelle Komponente der Tabelle. Wir haben uns entschlossen, eine Ebene höher zu gehen und ViewModels und Modelle selbst dynamisch zu generieren, indem wir eine einfache Beschreibung der Struktur über die Enum-Felder verwenden.
Wie funktioniert es?
Alles begann mit Enum. Für jedes Profil erstellen wir eine eindeutige Aufzählung - dies sind unsere Abschnitte des Profils. Eine seiner Methoden besteht darin, das Zellenarray in diesem Abschnitt zurückzugeben.
Die Zellen in der Tabelle enthalten außerdem zusätzliche Funktionen, die die Eigenschaften der Zellen beschreiben. In solchen Funktionen setzen wir den Namen der Zelle, den Anfangswert. Später hinzugefügte Parameter wie
- Anzeigeprüfung: Einige Zellen müssen ausgeblendet sein,
- Liste der "übergeordneten" Zellen: Zellen, von denen der Wert, die Validierung oder die Anzeige dieser Zelle abhängt,
- Zelltyp: einfache Zellen mit Werten, Zellen im Schalter, Zellen mit der Funktion zum Hinzufügen von Elementen usw.
Wir abonnieren alle Abschnitte des allgemeinen QuestionnaireSectionCellType-Protokolls, um die Bindung an einen bestimmten Abschnitt auszuschließen. Wir werden dasselbe mit allen Zellen der Tabelle (QuestionnaireCellType) tun.
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 } … }
Ein solches Modell ist sehr einfach auszufüllen. Wir durchlaufen einfach alle Abschnitte, in jedem Abschnitt durchlaufen wir ein Array von Zellen und fügen sie dem Modellarray hinzu.
Am Beispiel des Bildschirms des Versicherungsnehmers (Aufzählung mit Abschnitten - 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) } } } } }
Fertig! Jetzt haben wir eine Tabelle mit Anfangswerten. Fügen Sie Methoden hinzu, um den Wert mit dem QuestionnaireCellType-Schlüssel zu lesen und im gewünschten Array-Element zu speichern.
Einige Modelle verfügen möglicherweise über optionale Felder. Daher haben wir ein Array mit optionalen Schlüsseln hinzugefügt. Während der Modellvalidierung enthalten diese Schlüssel möglicherweise keine Werte, das Modell wird jedoch als gefüllt betrachtet.
Der Einfachheit halber abonnieren alle Werte im ValueModel das gemeinsame Protokoll StringRepresentable, um die Liste der möglichen Werte zu begrenzen und eine Methode zum Anzeigen des Werts in der Zelle hinzuzufügen.
protocol StringRepresentable { var stringValue: String? { get } }
Die Funktionalität wurde erweitert, und viele andere Eigenschaften und Methoden wurden in den Modellen angezeigt: Bereinigen des Modells (in einigen Modellen sollten Anfangswerte festgelegt werden), Unterstützung für ein dynamisches Array von Werten (Wert: Array) usw.
Dieser Ansatz erwies sich als sehr praktisch für das Speichern in der Datenbank mit Realm. Um den Fragebogen auszufüllen, können Sie ein zuvor gespeichertes ausgefülltes Modell auswählen. Um die CTP-Richtlinie zu erweitern, muss der Agent die Dokumente des Benutzers, die von ihm angehängten Treiber und die TCP-Daten für die neue nicht mehr ausfüllen. Stattdessen können Sie es einfach wiederverwenden, um das vorhandene auszufüllen.
Um Tabellen zu ändern oder zu ergänzen, müssen Sie nur das ViewModel für einen bestimmten Bildschirm suchen, die erforderliche Aufzählung finden, die für die Anzeige des gewünschten Blocks verantwortlich ist, und mehrere Fälle hinzufügen oder beheben. Alles, der Tisch wird die notwendige Form annehmen!
Das Ausfüllen des Formulars mit Testwerten war ebenfalls sehr bequem und schnell. Auf diese Weise können Sie schnell Testdaten generieren. Wenn Sie eine separate Datei mit den Anfangsdaten hinzufügen, aus der das Programm den Wert in jedes bestimmte Feld des Fragebogens überträgt, kann selbst ein Anfänger vorgefertigte Fragebögen erstellen, ohne den Rest des Codes mit Ausnahme einer bestimmten Datei zu zerlegen.
Abhängigkeiten
Eine separate Aufgabe, die wir während des Entwicklungsprozesses gelöst haben, ist die Abhängigkeitsbehandlung. Einige Elemente des Fragebogens waren miteinander verbunden. Daher kann die Dokumentennummer nicht ausgefüllt werden, ohne den Typ dieses Dokuments selbst zu wählen. Die Hausnummer kann nicht angegeben werden, ohne die Stadt und die Straße usw. anzugeben.

Wir haben die Werte des Fragebogens aktualisiert, indem alle abhängigen Felder gelöscht wurden. (Wenn Sie beispielsweise den Typ eines Dokuments löschen oder ändern, löschen Sie das Feld „Dokumentennummer“):
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) } }
Fallstricke, die wir während der Entwicklung lösen mussten und wie wir es geschafft haben
Es ist klar, dass diese Methode für Bildschirme mit derselben Funktionalität (Ausfüllen der Felder) praktisch ist, jedoch nicht so praktisch, wenn eindeutige Elemente oder Funktionen auf einem separaten Bildschirm angezeigt werden, die sich nicht auf anderen Bildschirmen befinden. In unserer Bewerbung waren dies:
- Ein Bildschirm mit Motorleistung, der separat generiert werden musste, weshalb er sich in der Funktionalität unterschied. Auf diesem Bildschirm sollte die Anforderung verschwinden und der Wert vom Server wird automatisch ersetzt. Ich musste separat eine Klasse dafür erstellen, die für das Anzeigen, Laden, Validieren, Laden vom Server und Ersetzen eines Werts in einem leeren Feld verantwortlich ist, ohne den Benutzer zu stören, wenn dieser beschließt, seinen eigenen Wert einzugeben.
- Der Registrierungsnummernbildschirm, in dem nur der Schalter angezeigt wird, der sich auf die Anzeige oder das Ausblenden des Textfelds auswirkt. Für diesen Fall musste eine zusätzliche Bedingung gestellt werden, die programmgesteuert Fälle mit eingeschalteter Schalterstellung als leeren Wert ermittelt.
- Dynamische Listen, z. B. eine Liste von Treibern, die gespeichert und an ein Formular gebunden werden mussten, das ebenfalls aus dem Konzept geriet.
- Einzigartige Arten der Datenvalidierung. Es könnten viele Masken sein, die mit Regex'ami gemischt sind. Und Datumsvalidierung für verschiedene Felder, in denen sich die Validierung dramatisch unterschied (Einschränkungen der Minimal- / Maximalwerte) usw.
- Dateneingabebildschirme werden als collectionView-Zellen erstellt. (Dies war für das Design erforderlich!) Aus diesem Grund erforderte die Anzeige von Modalfenstern eine präzise Kontrolle über den ausgewählten Index. Ich musste die zum Ausfüllen verfügbaren Felder überprüfen und diejenigen aus der Liste ausschließen, die der Benutzer nicht sehen sollte.
- Um die Daten in der Tabelle korrekt anzuzeigen, mussten Änderungen an den Modellmethoden einiger Bildschirme vorgenommen werden. Zellen wie Name und Adresse werden in der Tabelle als ein einzelnes Element angezeigt, erfordern jedoch, dass mehrere Popup-Bildschirme vollständig ausgefüllt sind.
Fazit
Diese Erfahrung ermöglichte es uns bei True Engineering, schnell eine mobile Anwendung zu implementieren, die einfach zu warten ist. Durch die Vielseitigkeit können Sie schnell Tabellen mit verschiedenen Arten von Eingabedaten erstellen: Wir haben in nur einer Woche 20 Fenster erstellt. Dieser Ansatz beschleunigt auch den Anwendungstestprozess. In naher Zukunft werden wir die fertige Fabrik wiederverwenden, um schnell neue Tabellen und neue Funktionen zu generieren.