SwiftUI für die letzte Wettbewerbsaufgabe Telegram Charts (März 2019): Alles ist einfach



Ich beginne mit der Beobachtung, dass für die in diesem Artikel beschriebene Anwendung Xcode 11 und MacOS Catalina erforderlich sind, wenn Sie Live Previews möchten, und Mojave wenn Sie den Simulator verwenden. Der Anwendungscode befindet sich auf Github .

In diesem Jahr kündigte Apple auf der WWDC 2019 SwiftUI , eine neue deklarative Methode zum Erstellen einer Benutzeroberfläche auf allen Apple Geräten. Dies ist fast eine völlige Abkehr vom üblichen UIKit , und ich wollte - wie viele andere Entwickler - dieses neue Tool unbedingt in Aktion sehen.

In diesem Artikel wird die Erfahrung mit der Lösung eines Problems mit SwiftUI , dessen Code in UIKit unvergleichlich komplexer ist und meiner Meinung nach nicht lesbar UIKit kann.

Die Aufgabe bezieht sich auf den letzten Telegrammwettbewerb für Android , iOS und JS Entwickler, der vom 10. bis 24. März 2019 stattfand. In diesem Wettbewerb wurde eine einfache Aufgabe vorgeschlagen, um die Nutzungsintensität einer bestimmten Ressource im Internet in Abhängigkeit von der Zeit basierend auf JSON Daten grafisch darzustellen. Als iOS Entwickler sollten Sie Swift , um von Grund auf neu geschriebenen Code an die Konkurrenz zu senden, ohne fremde Spezialbibliotheken zum Plotten zu verwenden.

Diese Aufgabe erforderte Kenntnisse für die Arbeit mit den Grafik- und Animationsfunktionen von iOS: Core Graphics , Core Animation , Metal , OpenGL ES . Einige dieser Tools sind einfache, nicht objektorientierte Programmiertools. Im Wesentlichen gab es in iOS keine akzeptablen Vorlagen zum Lösen derart scheinbar leichter grafischer Aufgaben auf den ersten Blick. Daher hat jeder Teilnehmer seinen eigenen Animator ( Render ) basierend auf Metal , CALayers , OpenGL , CADisplayLink erfunden . Dies erzeugte Tonnen von Code, aus denen es nicht möglich war, etwas auszuleihen und zu entwickeln, da es sich um rein „urheberrechtlich geschützte“ Werke handelt, die nur Autoren wirklich entwickeln können. Dies sollte jedoch nicht so sein.

Anfang Juni erscheint auf der WWDC 2019 SwifUI - ein neues framework das von Apple entwickelt wurde, in Swift geschrieben wurde und die Benutzeroberfläche ( UI ) im Code deklarativ beschreibt. Sie bestimmen, welche subviews in Ihrer View subviews , welche Daten dazu führen, dass sich diese subviews ändern, welche Modifikatoren Sie auf sie anwenden müssen, damit sie an der richtigen Stelle positioniert werden und die richtige Größe und den richtigen Stil haben. Ein ebenso wichtiges Element von SwiftUI ist die Steuerung des Flusses von vom Benutzer veränderbaren Daten, wodurch wiederum die UI aktualisiert wird.

In diesem Artikel möchte ich zeigen, wie die eigentliche Aufgabe des Telegrammwettbewerbs auf SwiftUI schnell und einfach gelöst werden kann. Darüber hinaus ist dies ein sehr aufregender Prozess.

Aufgabe


Die Konkurrenzanwendung sollte gleichzeitig 5 "Sätze von Diagrammen" auf dem Bildschirm anzeigen, wobei die von Telegram bereitgestellten Daten verwendet werden. Für einen „Satz von Diagrammen“ lautet die UI wie folgt:



Im oberen Teil befindet sich eine „Diagrammzone“ mit einer gemeinsamen Skala entlang der normalen Y-Achse mit Markierungen und horizontalen Gitterlinien. Darunter befindet sich eine Kriechlinie mit Zeitstempeln entlang der X-Achse als Datumsangaben.

Noch niedriger ist die sogenannte „Minikarte“ (wie in Xcode 11 ), Xcode 11 ein transparentes „Fenster“, das den Teil des Zeitraums unserer „Diagramme“ definiert, der in der oberen „Diagrammzone“ ausführlicher dargestellt wird. Diese „Minikarte“ kann nicht nur entlang der X Achse verschoben werden, sondern auch ihre Breite kann geändert werden, was sich auf die Zeitskala im Bereich „Diagramme“ auswirkt.

Mit Hilfe von checkboxs die in den Farben von „Diagrammen“ gemalt und mit ihren Namen versehen sind, können Sie die Anzeige der dieser Farbe entsprechenden „Grafiken“ in der „Diagrammzone“ ablehnen.

Es gibt viele solcher „Sätze von Graphen“. In unserem Testbeispiel gibt es beispielsweise 5 davon, und alle sollten sich auf einem Bildschirm befinden.

In der mit SwiftUI entwickelten SwiftUI ist keine Schaltfläche zum Umschalten zwischen SwiftUI und SwiftUI . Diese ist bereits in SwiftUI . Darüber hinaus bietet SwiftUI weitaus mehr Optionen zum Kombinieren von „ SwiftUI “ ( SwiftUI den oben dargestellten Bildschirmsätzen) als nur eine Bildlauftabelle nach unten, und wir werden uns einige dieser sehr interessanten Optionen ansehen.

Aber zuerst konzentrieren wir uns auf die Anzeige eines „ SwiftUI “, für den SwiftUI ChartView eine ChartView :



SwiftUI können Sie eine komplexe UI in kleinen Teilen erstellen und testen. Anschließend können Sie diese Teile ganz einfach zu einem Puzzle zusammensetzen. Wir werden es tun. Unser ChartView sehr gut in diese kleinen Teile ChartView :

  • GraphsForChart - Dies sind die Diagramme selbst, die für einen bestimmten "Satz von Diagrammen" erstellt wurden. "Diagramme" werden für den vom Benutzer mithilfe der RangeView "Minikarte" RangeView , der unten dargestellt wird.
  • YTickerView ist die Y Achse mit Höhen und dem entsprechenden horizontalen Raster.
  • IndicatorView ist ein horizontal benutzergesteuerter Indikator, mit dem Sie die Werte von "Diagramme" und die Zeit für die entsprechende Indikatorposition auf der Zeitachse auf der X Achse anzeigen können.
  • TickerView - " TickerView ", die Zeitstempel auf der X Achse als Datumsangaben TickerView ,
  • RangeView - ein temporäres „Fenster“, das vom Benutzer mithilfe von Gesten angepasst werden kann, um das Zeitintervall für „ RangeViewRangeView
  • CheckMarksView - enthält "Schaltflächen" in den Farben von " CheckMarksView ", mit denen Sie das Vorhandensein von " ChartView " in ChartView .

ChartView Benutzer kann auf drei Arten mit ChartView interagieren:

1. Steuern Sie die „Minikarte“ mit der DragGesture Geste. Sie kann das temporäre „Fenster“ nach rechts und links verschieben und seine Größe verringern / vergrößern:



2. Bewegen Sie den Indikator in horizontaler Richtung und zeigen Sie die Werte der "Diagramme" zu einem festgelegten Zeitpunkt an:



3. Verbergen / Anzeigen bestimmter „Diagramme“ mithilfe von Schaltflächen in den Farben „ ChartView “ am unteren Rand der ChartView :



Wir können verschiedene „Diagrammsätze“ (wir haben 5 davon in Testdaten) auf unterschiedliche Weise kombinieren, indem wir sie beispielsweise alle gleichzeitig auf einem Bildschirm mithilfe der List (wie eine nach oben und unten scrollbare Tabelle):



oder mit ScrollView und einem horizontalen Stapel von HStack mit einem 3D-Effekt:



... oder in Form eines ZStack „Karten“, dessen Reihenfolge geändert werden kann: Die obere „Karte“ mit „einer Reihe von Diagrammen“ kann weit genug nach unten gezogen werden, um die nächste Karte anzuzeigen, und wenn Sie sie weiter nach unten ziehen, wird sie „ geht "bis zum letzten Platz in ZStack , und diese nächste" Karte "" geht weiter ":



In diesen komplexen UI - einer „Bildlauftabelle“, einem horizontalen Stapel mit 3D Effekt, einem ZStack „Karten“ - funktionieren alle Mittel der Benutzerinteraktion vollständig: Bewegen Sie sich entlang der Zeitachse und ändern Sie den „Maßstab“ der Schaltflächen für mini - map , Anzeige und Ausblenden "Charts".

Weiter werden wir das Design dieser UI Verwendung von SwiftUI im Detail SwiftUI - von einfachen Elementen bis zu ihren komplexeren Kompositionen. Aber lassen Sie uns zuerst die Datenstruktur verstehen, die wir haben.

Die Lösung unseres Problems war also in mehrere Phasen unterteilt:

  • Laden Sie Daten aus einer JSON Datei herunter und präsentieren Sie sie in einem praktischen "internen" Format
  • Erstellen Sie eine UI für einen „Satz von Diagrammen“.
  • Kombinieren Sie verschiedene "Kartensätze"

Daten herunterladen


Zu unserer Verfügung stellte Telegram JSON- Daten zur Verfügung, die mehrere "Sätze von Diagrammen" enthielten. Jeder einzelne " chart " eines chart enthält mehrere " chart " (oder "Linien") von chart.columns . Jeder "Graph" ("Linien") hat eine Markierung an Position 0 - "x" , "y0" , "y1" , "y2" , "y3" , gefolgt von beiden Zeitwerten auf der X-Achse ("x"). oder die Werte von "Grafiken" ("Linien") ( "y0" , "y1" , "y2" , "y3" ) auf der Y Achse:



Das Vorhandensein aller „Linien“ im „Diagrammsatz“ ist optional. Die Werte für die "Spalte" x sind UNIX-Zeitstempel in Millisekunden.

Darüber hinaus wird jeder einzelne „ chart “ des chart mit chart.colors Farben im Format von 6 hexadezimalen Ziffern (z. B. „#AAAAAA“) und chart.names .

Um das Datenmodell in der JSON Datei zu erstellen, habe ich den hervorragenden Quicktype- Dienst verwendet. Auf dieser Site fügen Sie einen Text aus einer JSON Datei ein und geben die Programmiersprache ( Swift ) an, den Namen der Struktur ( Chart ), die nach dem "Parsen" dieser JSON Daten erstellt wird.

Im zentralen Teil des Bildschirms wird ein Code generiert, den wir in einer separaten Datei mit dem Namen Chart.swift in unsere Anwendung Chart.swift . Hier platzieren wir das Datenmodell im JSON-Format. Mit dem Loader von Daten aus der JSON Datei in das Modell, das aus den SwiftUI Generic Demos ausgeliehen wurde , erhielt ich eine Reihe von columns: [ChartElement] , eine Sammlung von „ columns: [ChartElement] “ im Telegram .

Die ChartElement Datenstruktur, die Arrays heterogener Elemente enthält, eignet sich nicht besonders für die intensive interaktive Arbeit mit Diagrammen. Außerdem werden Zeitstempel im UNIX Format in Millisekunden (z. B. 1542412800000, 1542499200000, 1542585600000, 1542672000000 ) und Farben im 6-Hexadezimalformat 1542412800000, 1542499200000, 1542585600000, 1542672000000 Ziffern (zum Beispiel "#AAAAAA" ).

Daher werden wir in unserer Anwendung dieselben Daten verwenden, jedoch in einem anderen „internen“ und ziemlich einfachen Format [LinesSet] . Das Array [LinesSet] ist eine Sammlung von LinesSet , von denen jeder xTime Zeitstempel im Format "Feb 12, 2019" ( X Achse) und mehrere „Diagramm“ lines ( Y Achse) enthält:



Daten für jedes Liniendiagramm (Linie) werden angezeigt

  • ein Array von ganzzahligen points: [Int] ,
  • benannt "Grafik" title: String ,
  • Typ "Grafik" type: String? ,
  • color : UIColor im Swift UIColor Format,
  • Anzahl der Punkte countY: Int .

Darüber hinaus kann jedes „Diagramm“ je nach Wert von isHidden: Bool ausgeblendet oder angezeigt werden. Die upperBound lowerBound und upperBound Anpassen des Zeitbereichs nehmen Werte von 0 bis 1 und zeigen nicht nur die Größe des Zeitfensters „Mini Map“ ( upperBound - lowerBound ), sondern auch dessen Position auf der Zeitachse X :



Die JSON Datenstrukturen [ChartElement] und die Datenstrukturen der LinesSet „intern“ LinesSet und Line befinden sich in der Datei Chart.swift . Der Code zum Laden und Konvertieren von JSON Daten in eine interne Struktur befindet sich in der Datei Data.swift . Details zu diesen Transformationen finden Sie hier .

Als Ergebnis haben wir Daten über die „Diagrammsätze“ im internen Format als Array von chartsData .



Dies ist unser Datenmodell. SwiftUI jedoch in SwiftUI zu arbeiten, muss sichergestellt werden, dass alle vom Benutzer am chartsData Array vorgenommenen Änderungen (Ändern des temporären „Fensters“, Ausblenden / chartsData der „ chartsData “) zu automatischen Aktualisierungen unserer Views .

Wir werden @EnvironmentObject erstellen. Auf diese Weise können wir das Datenmodell überall dort verwenden, wo es benötigt wird, und außerdem unsere Views automatisch aktualisieren, wenn sich die Daten ändern. Dies ist so etwas wie Singleton oder globale Daten.

@EnvironmentObject erfordert, dass wir eine final class UserData der final class UserData , die sich in der Datei UserData.swift befindet , die chartsData Daten speichert und das ObservableObject Protokoll implementiert:



Das Vorhandensein von @Published "Wrappern" ermöglicht es Ihnen, "Nachrichten" zu veröffentlichen, dass sich diese Eigenschaften der charts der UserData Klasse geändert haben, sodass alle Views , die diese Nachrichten "abonniert" haben, in SwiftUI automatisch neue Daten auswählen und aktualisieren können.

Denken Sie daran, dass sich in der Eigenschaft " isHidden " die isHidden Werte für jedes " isHidden " ändern können (Sie können diese " isHidden " ausblenden oder lowerBound ) sowie die unteren lowerBound für die untere und obere Grenze für jeden einzelnen "Satz von upperBound ".

Wir möchten die charts der UserData Klasse in unserer gesamten Anwendung verwenden und müssen sie dank @EnvironmentObject nicht manuell mit der UI @EnvironmentObject .

Dazu müssen wir beim Starten der Anwendung eine Instanz der UserData () -Klasse erstellen, damit wir anschließend überall in unserer Anwendung darauf zugreifen können. Wir werden dies in der SceneDelegate.swift Datei innerhalb der scene (_ : , willConnectTo: , options: ) tun scene (_ : , willConnectTo: , options: ) -Methode. Hier wird unsere ContentView erstellt und gestartet. Hier müssen wir die ContentView jedes @EnvironmentObject uns @EnvironmentObject damit SwiftUI sie für jede andere View verfügbar machen kann:



@Published nun in einer beliebigen View auf die @Published Daten der UserData Klasse UserData , müssen Sie die Variable var mit dem Wrapper @EnvironmentObject . Wenn Sie beispielsweise den Zeitbereich in RangeView erstellen wir die Variable var userData mit dem UserData TYPE:



Sobald wir also @EnvironmentObject in die "Umgebung" der Anwendung implementiert haben, können wir sofort damit beginnen, es entweder auf der höchsten Ebene oder auf der 10. Ebene darunter zu verwenden - es spielt keine Rolle. Noch wichtiger ist jedoch, dass alle Views mit diesem @EnvironmentObject automatisch @EnvironmentObject , wenn eine View die "Umgebung" ändert, wodurch die Synchronisation mit den Daten sichergestellt wird.

Fahren wir mit dem Entwerfen der Benutzeroberfläche fort.

Benutzeroberfläche (UI) für einen „Satz von Grafiken“


SwiftUI bietet eine zusammengesetzte Technologie zum Erstellen von SwiftUI aus vielen kleinen Views , und wir haben bereits gesehen, dass unsere Anwendung sehr gut zu dieser Technologie passt, da sie sich in kleine Teile ChartView : das ChartViewChartView Charts“, „ GraphsForChart Charts“, Y Achsen- YTickerView - YTickerView , Benutzergesteuerter Indikatorwert von "Charts" IndicatorView , " TickerView " TickerView mit TickerView auf der X Achse, benutzergesteuertes "Zeitfenster" RangeView , Markierungen beim Ausblenden / CheckMarksView "Charts" CheckMarksView . Wir können nicht nur alle diese Views unabhängig voneinander erstellen, sondern auch sofort in Xcode 11 mithilfe von Previews (vorläufige Live-Ansichten) auf Testdaten testen. Sie werden überrascht sein, wie einfach der Code ist, um sie aus anderen grundlegenderen Views zu erstellen.

GraphView - "Graph" ("Linie")


Die erste View , mit der wir beginnen werden, ist eigentlich der „Graph“ selbst (oder die „Linie“). Wir werden es GraphView :



Das Erstellen einer GraphView beginnt wie gewohnt mit dem Erstellen einer neuen Datei in Xcode 11 über das Menü FileNewFile :



Dann wählen wir den gewünschten TYP der Datei aus - dies ist die SwiftUI Datei:



... geben Sie unserer View den Namen "GraphView" und geben Sie ihren Standort an:



Klicken Sie auf die Schaltfläche "Create" und erhalten Sie eine Standardansicht mit Text ( "Hello World!") In der Mitte des Bildschirms:



Unsere Aufgabe ist es, den Text ("Hello World!") "Grafik" zu ersetzen. Lassen Sie uns jedoch zunächst sehen, welche Anfangsdaten wir zum Erstellen der "Grafik" haben:

  • Wir haben die Werte von line.points "Graphics" line: Line ,
  • Zeitbereich rangeTime , ein Bereich von Indizes Range Zeitstempeln xTime auf der X-Achse,
  • Wertebereich rangeY: Range „Grafiken“ für die Y- rangeY: Range ,
  • Dicke der lineWidth „Grafik“.

Fügen Sie der GraphView Struktur diese Eigenschaften GraphView :



Wenn wir für unsere "Grafiken" Previews (Vorschauen) verwenden möchten, die nur für MacOS Catalyna , müssen wir eine GraphView mit dem rangeTime und den rangeTime der "Grafiken" selbst initiieren:



Wir haben bereits die chartsData Testdaten, die wir aus der JSON Datei chart.json , und wir haben sie für Previews .

In unserem Fall ist dies der erste " chartsData[0] " chartsData[0] und der erste "Chart" in diesem chartsData[0].lines[0] , den wir GraphView als line bereitstellen.

Wir werden den gesamten Bereich der Indizes 0..<(chartsData[0].xTime.count - 1) als Zeitintervall rangeTime .
Die lineWidth rangeY und lineWidth können extern oder nicht festgelegt werden, da sie bereits Anfangswerte haben: rangeY ist nil und lineWidth ist 1 .

Wir haben absichtlich einen TYP der rangeY TYPE erstellt. Wenn rangeY nicht extern festgelegt ist und rangeY = nil , berechnen wir die minimalen minY und maximalen maxY Werte der "Grafiken" direkt aus den Daten von line.points :



Dieser Code wird kompiliert, aber wir haben immer noch eine Standardansicht auf dem Bildschirm mit dem Text Text ("Hello World!") In der Mitte des Bildschirms:



Weil wir im body den Text Text ("Hello World!") Durch Path ersetzen müssen, wodurch unser "Graph: points: line.points mit dem addLines(_:) (fast wie in Core Graphics ).




Wir werden unseren Path Linie umkreisen stroke (...) , deren Dicke lineWidth , und die Farbe der Strichlinie entspricht der Standardfarbe ( lineWidth schwarz):



Wir können die schwarze Farbe für die Strichlinie durch die Farbe ersetzen, die in unserer speziellen "Linien" line.color "Farbe" angegeben ist:



Damit unser „Diagramm“ in Rechtecken beliebiger Größe platziert werden kann, verwenden wir den GeometryReader Container. In der Apple Dokumentation Apple GeometryReader eine "Container" View , die ihren Inhalt als Funktion ihrer eigenen size und ihres Koordinatenraums definiert. Im Wesentlichen ist GeometryReader eine andere View ! Denn fast ALLES in SwiftUI ist View ! GeometryReader können SIE im Gegensatz zu anderen Views auf einige zusätzliche nützliche Informationen zugreifen, die Sie beim Entwerfen Ihrer benutzerdefinierten View .

Wir verwenden die Container GeometryReader und Path , um GraphView zu erstellen, GraphView an jede Größe GraphView werden kann. Und wenn wir uns unseren Code genau ansehen, sehen wir im Abschluss des GeometryReader Variable namens geometry :



Diese Variable hat den GeometryProxy TYPE, der wiederum eine Strukturstruktur mit vielen "Überraschungen" ist:

 public var size: CGSize { get } public var safeAreaInsets: EdgeInsets { get } public func frame(in coordinateSpace: CoordinateSpace) -> CGRect public subscript<T>(anchor: Anchor<T>) -> T where T : Equatable { get } 

Aus der GeometryProxy Definition geht hervor, dass es zwei berechnete Variablen var size und var safeAreaInsets , einen Funktionsrahmen frame( in:) und einen subscript getter . Wir brauchten nur die Größenvariable, um die Breite der geometry.size.width und die Höhe der geometry.size.height Zeichenbereichs „Grafik“ zu bestimmen.

Zusätzlich ermöglichen wir die animation (.linear(duration: 0.6)) unseres „Graphen“ mit dem animation (.linear(duration: 0.6)) .



GraphView_Previews können wir ganz einfach alle „Charts“ aus jedem „Set“ testen. Unten ist das "Diagramm" aus dem "Diagrammsatz" mit Index 4: chartsData[4] und Index 0 "Grafik" in diesem Satz: chartsData[4].lines[0] :



Wir stellen die height „Grafik“ mithilfe des frame (height: 400) auf 400 ein frame (height: 400) . Die Breite bleibt gleich der Breite des Bildschirms. Wenn wir keinen frame (height: 400) , würde der "Graph" den gesamten Bildschirm einnehmen. rangeY GraphView nil , , «» rangeTime :



Path animation (.linear(duration: 0.6)) , , , rangeY «». «» «» rangeY .

: SwiftUI , «» rangeY , SwiftUI , «» rangeY , SwiftUIerfüllt Protokoll Animatable.

Wenn Ihre ViewFigur eine "Figur" ist View, dh ein Protokoll implementiert Shape, wurde glücklicherweise bereits ein Protokoll dafür implementiert Animatable. Dies bedeutet, dass es eine berechnete Eigenschaft gibt, animatableDatamit der wir den Animationsprozess steuern können, aber standardmäßig ist sie so eingestellt EmptyAnimatableData, dass keine Animation stattfindet.

Um das Problem mit der Animation zu lösen, müssen wir zuerst unseren „Graphen“ GraphViewin verwandeln Shape. Es ist sehr einfach, wir müssen nur die Funktion implementieren, die func path (in rect:CGRect) -> Pathwir im Wesentlichen bereits haben, und mit Hilfe der berechneten Eigenschaft angeben, animatableDatawelche Daten wir animieren möchten:



Beachten Sie, dass das Thema der Animationssteuerung ein fortgeschrittenes Thema istSwiftUIWeitere Informationen finden Sie im Artikel „Erweiterte SwiftUI-Animationen - Teil 1: Pfade“ . Wir können die

resultierende „Figur“ Graphin einer viel einfacheren GraphViewNew„Grafik“ mit Animation verwenden:



Sie sehen, dass wir GeometryReaderunsere neue „Grafik“ nicht benötigt haben GraphViewNew, da sich Shapeunsere „Figur“ dank des Protokolls Graphan jede Größe des Elternteils anpassen kann View.

Natürlich haben Previewswir das gleiche Ergebnis erhalten wie im Fall von GraphView:



In den folgenden Kombinationen verwenden wir die GraphViewNewgleichen „Grafiken“, um die Werte anzuzeigen.

GraphsForChart - Satz von "Graphen" ("Linien")


Die Aufgabe Viewbesteht darin, ALLE „Diagramme“ („Linien“) aus dem „Satz von Diagrammen“ chartin einem bestimmten Zeitbereich rangeTimemit einer gemeinsamen Achse anzuzeigen. YDie Breite der „Linien“ entspricht lineWidth:



Für GraphViewund GraphViewNewerstellen wir eine GraphsForChartneue Datei für GraphsForChart.swiftund definieren die Anfangsdaten für "Chart Set":

  • der "Satz von Diagrammen" selbst chart: LineSet(Werte an Y),
  • Bereich rangeTime: Range( X) der Indizes der Zeitstempel von „Charts“,
  • Grafik Strichstrichstärke lineWidth

rangeY: Range « » ( Y ) c ( isHidden = false ) «», «»:



rangeOfRanges :



«» ( isHidden = false ) ZStack ForEach , «» « „“ transition(.move(edge: .top)) :



„“ ChartView , Y .

drawingGroup() Metal . Metal Metal , iPhone , . , drawingGroup() , »Advanced SwiftUI Animations – Part 1: Paths" 237 WWDC 2019 ( Building Custom Views with SwiftUI ).

GraphViewNew GraphsForChart Previews « », , 0 :



IndicatorView — «».


«» X :



« » chart X «» «». «», «» .



DragGesture :



“” . value.translation.width , onChanged , , : value.translation.width - self.prevTranslation. Dies ermöglicht uns eine reibungslose Bewegung des Indikators.

Um den Indikator IndicatorViewmit Hilfe eines Previewsbestimmten „Satzes von Diagrammen“ zu testen , können chartwir die vorgefertigte ViewKonstruktion von „Diagrammen“ verwenden GraphsForChart:



Wir können einen beliebigen, aber aufeinander abgestimmten Zeitbereich rangeTimesowohl für den Indikator IndicatorViewals auch für „Diagramme“ festlegen GraphsForChart. Auf diese Weise können wir sicherstellen, dass sich die "Kreise", die die Werte der "Diagramme" angeben, an den richtigen Stellen befinden.

TickerView- Xmit Markierungen.


«» , X Y . X TickerMarkView . TickerMarkView View VStack , Path Text :



« » chart : LineSet TickerView rangeTime estimatedMarksNumber , :



«» ScrollView HStack , rangeTime .

TickerView step , TimeMarkView , rangeTime widthRange



… c step chart.xTime indexes .

X — — overlay



HStack , TimeMarkView , offset :



, X - colorXAxis , — colorXMark :



YTickerViewY .


Dieser Viewzeichnet Ymit digitalen Markierungen YMarkView. Die Markierungen selbst YMarkViewsind sehr einfach Viewmit einem vertikalen Stapel, VStackin dem sie platziert sind Path(horizontale Linie), und Textmit einer Nummer:



Eine Reihe von Markierungen Yfür eine bestimmte „Gruppe von Diagrammen“ chartwird in gebildet YTickerView. Der Wertebereich wird rangeYals Vereinigung der Wertebereiche aller "Diagramme" berechnet, die in diesem "Satz von Diagrammen" enthalten sind, wobei die Funktion verwendet wird rangeOfRanges. Die ungefähre Anzahl der Markierungen auf der Y-Achse wird durch den Parameter festgelegt estimatedMarksNumber:



YTickerViewWir überwachen die Änderung des Bereichs der „Diagramme“ -Werte rangeY. Eigentlich die Y-Achse - die vertikale Linie - die wir overlayunseren Markierungen auferlegen ...



, Y — colorYAxis , — colorYMark :



RangeView - «mini-map».


( lowerBound , upperBound ) « »:



RangeViewmini - map " " Views .

View , RangeView :



  • « » chart: LineSet ( Y ),
  • height "mini-map" RangeView ,
  • widthRange "mini-map" RangeView ,
  • indent "mini-map" RangeView .

Im Gegensatz zu den anderen oben diskutierten Viewsmüssen wir den DragGestureZeitbereich ( lowerBound, upperBound) mit einer Geste ändern und sofort seine Änderung sehen, damit der benutzerdefinierte Zeitbereich ( lowerBound, upperBound), mit dem wir arbeiten, in einer Variablenvariablen gespeichert wird @EnvironmentObject var userData: UserData:



Jede Änderung der Variablen var userDataführt zu alle Viewsvon ihm abhängig neu zeichnen .

Die Hauptfigur in RangeViewist ein transparentes „Fenster“, dessen Position und Größe vom Benutzer mit einer Geste geregelt wird DragGesture:

1. Wenn wir die Geste in einem transparenten „Fenster“ verwenden, Xändert sich die Position des „Fensters“ entlang und seine Größe ändert sich nicht:



2. , «» lowerBound , «»:



3. , «» upperBound , «»:



RangeView 3- : Rectangle () Image , lowerBound upperBound @EnvironmentObject var userData: UserData DragGesture :



«» ( overlay ) GraphsForChartView «» « » chart :



, «» «».

«» ( ), lowerBound upperBound userData onChanged DragGesture Rectangle () Image ...



, , Views ( «», X , Y c hartView ):



View @EnvironmentObject userData: UserData , Previews , .environmentObject (UserData()) :



CheckMarksView — «» «».


CheckMarksViewEs handelt sich um einen horizontalen Stapel HStackmit einer Zeile checkBoxeszum Umschalten der Eigenschaften der isHiddeneinzelnen „Grafiken“ im „Satz von Grafiken“ chart:



CheckBoxIn unserem Projekt kann er entweder über eine reguläre Schaltfläche Buttonund einen Aufruf CheckButtonoder über eine simulierende Schaltfläche implementiert werden SimulatedButton.



Die Schaltfläche Buttonmusste nachgeahmt werden, da sie sich beim Platzieren mehrerer dieser Schaltflächen in der Listhöheren Hierarchie „weigert“, ordnungsgemäß zu funktionieren. Dies ist ein langjähriger Fehler , der seit Beta 1 in der aktuellen Version in Xcode 11 steckt . Die aktuelle Version der Anwendung verwendet eine simulierte Schaltfläche SimulatedButton.

Sowohl die simulierte SimulatedButtonals auch die reale TasteCheckButton View « » — CheckBoxView . HStack , Tex Image :



, CheckBoxView @Binding var line: Line . isHidden « » CheckBoView :




CheckBoView SimulatedButton CheckButton $ line :




isHidden line SimulatedButton onTapGesture



CheckButtonaction Button :



, SimulatedButton CheckButton @Binding var line: Line . $ CheckMarksView userData.charts[self.chartIndex].lines[self.lineIndex(line: line)].isHidden , @EnvironmentObject var userData :



CheckButton , Apple . , CheckButton CheckMarksView SimulatedButton , « » ChartView List ListChartsView .

View @EnvironmentObject var userData: UserData , Previews , .environmentObject(UserData()) :



Views .


SwiftUI- Dies ist in erster Linie eine Kombination aus verschiedenen kleinen Viewszu großen und großen Viewszu sehr großen usw., wie in einem Spiel Lego. Es SwiftUIgibt viele Mittel für eine solche Kombination Views:

  • Ein vertikaler Stapel VStack,
  • horizontale Stapel HStack,
  • „Tiefe“ des Stapels ZStack,
  • Gruppe Group,
  • ScrollView ,
  • Liste List,
  • bilden Form,
  • Lesezeichenbehälter TabView
  • usw.

GraphsViewForChart , «» « » GraphsForChart Y , X, «» ZStack :



Previews GraphsViewForChart NavigationView , Dark .collorScheme(.dark) .

« » Y , X « », : «mini — map» RangeView CheckMarksView «».

ChartView , « » :



VStack :



3 « » ChartView:

  1. « » List ,
  2. HStack 3D ,
  3. ZStack «»

« » ListChartsView List :



3D ScrollView , HStack ForEach :



: «» mini- map , «».

ZStack «».


CardView «»- « » X Y, : «mini — map» / . CardView ChartView , «» , , , ZStack « » cardBackgroundColor . , «» :



«» VStack , ZStack ForEach :



«», «3D-c» CardViewScalable , indexChat .

«3D-c » ( sequenced ) LongPressGesture DragGesture , «» indexChat == 0 :



( LongPress ) «» « », ( Drag ) , , , «» ZStack , «» «»:



«» TapGesture , LongPressGesture DragGesture :



Tap « » ChartView RangeView CheckMarksView :



Anwendung TabViewzum Kombinieren aller 3 Varianten des Kompositions- „Diagrammsatzes“ auf einem Bildschirm ChartView.





Wir haben 3 Lesezeichen mit einem Bild Imageund Text Text, ein vertikaler Stapel VStackwird für ihre gemeinsame Präsentation nicht benötigt.

Sie entsprechen unseren 3 Methoden zum Kombinieren von "Diagrammsätzen" ChartViews:

  1. "Bildlauftabelle" ListChartViews,
  2. horizontaler Stapel mit 3D-Effekt HStackChartViews,
  3. ZStack überlagerte "Karten" OverlayCardsViews.

Alle Elemente der Benutzerinteraktion: Bewegen Sie sich entlang der Zeitachse und ändern Sie die „Skala“ mit Hilfe mini - map, Anzeige und Schaltflächen, um die „Diagramme“ auszublenden. in allen 3 Fällen voll arbeiten.

Der Code ist auf Github .

SwiftUI ...


, :

Mang To , Lets Build That Application , SwiftUI ,
— «SwiftUI by example» www.hackingwithswift.com/quick-start/swiftui
— , www.bigmountainstudio.com/swiftui-views-book
— 100 SwiftUI www.hackingwithswift.com/articles/201/start-the-100-days-of-swiftui , 31 2019 ,
— SwiftUI swiftui-lab.com
Majid ,
— pointFree.co www.pointfree.co «» Reducers SwiftUI ( ),
MovieSwiftUI , .

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


All Articles