
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 „ RangeView
“ RangeView
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
ChartView
„
ChartView
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ü
File
→
New
→
File
:

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
,
SwiftUI
erfüllt Protokoll Animatable
.Wenn Ihre View
Figur 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, animatableData
mit 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“ GraphView
in verwandeln Shape
. Es ist sehr einfach, wir müssen nur die Funktion implementieren, die func path (in rect:CGRect) -> Path
wir im Wesentlichen bereits haben, und mit Hilfe der berechneten Eigenschaft angeben, animatableData
welche Daten wir animieren möchten:
Beachten Sie, dass das Thema der Animationssteuerung ein fortgeschrittenes Thema istSwiftUI
Weitere Informationen finden Sie im Artikel „Erweiterte SwiftUI-Animationen - Teil 1: Pfade“ . Wir können dieresultierende „Figur“ Graph
in einer viel einfacheren GraphViewNew
„Grafik“ mit Animation verwenden:
Sie sehen, dass wir GeometryReader
unsere neue „Grafik“ nicht benötigt haben GraphViewNew
, da sich Shape
unsere „Figur“ dank des Protokolls Graph
an jede Größe des Elternteils anpassen kann View
.Natürlich haben Previews
wir das gleiche Ergebnis erhalten wie im Fall von GraphView
:
In den folgenden Kombinationen verwenden wir die GraphViewNew
gleichen „Grafiken“, um die Werte anzuzeigen.GraphsForChart
- Satz von "Graphen" ("Linien")
Die Aufgabe View
besteht darin, ALLE „Diagramme“ („Linien“) aus dem „Satz von Diagrammen“ chart
in einem bestimmten Zeitbereich rangeTime
mit einer gemeinsamen Achse anzuzeigen. Y
Die Breite der „Linien“ entspricht lineWidth
:
Für GraphView
und GraphViewNew
erstellen wir eine GraphsForChart
neue Datei für GraphsForChart.swift
und 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 IndicatorView
mit Hilfe eines Previews
bestimmten „Satzes von Diagrammen“ zu testen , können chart
wir die vorgefertigte View
Konstruktion von „Diagrammen“ verwenden GraphsForChart
:
Wir können einen beliebigen, aber aufeinander abgestimmten Zeitbereich rangeTime
sowohl für den Indikator IndicatorView
als 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
- X
mit 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
:

YTickerView
— Y
.
Dieser View
zeichnet Y
mit digitalen Markierungen YMarkView
. Die Markierungen selbst YMarkView
sind sehr einfach View
mit einem vertikalen Stapel, VStack
in dem sie platziert sind Path
(horizontale Linie), und Text
mit einer Nummer:
Eine Reihe von Markierungen Y
für eine bestimmte „Gruppe von Diagrammen“ chart
wird in gebildet YTickerView
. Der Wertebereich wird rangeY
als 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
:
YTickerView
Wir überwachen die Änderung des Bereichs der „Diagramme“ -Werte rangeY
. Eigentlich die Y-Achse - die vertikale Linie - die wir overlay
unseren Markierungen auferlegen ...
, Y —
colorYAxis
, —
colorYMark
:

RangeView
- «mini-map».
(
lowerBound
,
upperBound
) « »:

RangeView
—
mini - 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 Views
müssen wir den DragGesture
Zeitbereich ( 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 userData
führt zu alle Views
von ihm abhängig neu zeichnen .Die Hauptfigur in RangeView
ist 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
— «» «».
CheckMarksView
Es handelt sich um einen horizontalen Stapel HStack
mit einer Zeile checkBoxes
zum Umschalten der Eigenschaften der isHidden
einzelnen „Grafiken“ im „Satz von Grafiken“ chart
:
CheckBox
In unserem Projekt kann er entweder über eine reguläre Schaltfläche Button
und einen Aufruf CheckButton
oder über eine simulierende Schaltfläche implementiert werden SimulatedButton
.
Die Schaltfläche Button
musste nachgeahmt werden, da sie sich beim Platzieren mehrerer dieser Schaltflächen in der List
hö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 SimulatedButton
als auch die reale TasteCheckButton
View
« » —
CheckBoxView
.
HStack
,
Tex
Image
:

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


CheckBoView
SimulatedButton
CheckButton
$
line
:


isHidden
line
SimulatedButton
onTapGesture
…

…
CheckButton
—
action
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 Views
zu großen und großen Views
zu sehr großen usw., wie in einem Spiel Lego
. Es SwiftUI
gibt 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:
- « »
List
, HStack
3D ,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 TabView
zum Kombinieren aller 3 Varianten des Kompositions- „Diagrammsatzes“ auf einem Bildschirm ChartView
.
Wir haben 3 Lesezeichen mit einem Bild Image
und Text Text
, ein vertikaler Stapel VStack
wird für ihre gemeinsame Präsentation nicht benötigt.Sie entsprechen unseren 3 Methoden zum Kombinieren von "Diagrammsätzen" ChartViews
:- "Bildlauftabelle"
ListChartViews
, - horizontaler Stapel mit 3D-Effekt
HStackChartViews
, - 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 , .