
Je commencerai par la remarque que l'application décrite dans cet article nécessite
Xcode 11 et
MacOS Catalina si vous souhaitez utiliser les
Live Previews et
Mojave si vous utilisez le simulateur. Le code d'application est sur
Github .
Cette année, lors de la
WWDC 2019 ,
Apple annoncé
SwiftUI , une nouvelle façon déclarative de créer une interface utilisateur (UI) sur tous
Apple appareils
Apple . C'est presque un départ complet de l'
UIKit habituel, et moi - comme beaucoup d'autres développeurs - voulais vraiment voir ce nouvel outil en action.
Cet article présente l'expérience de la résolution avec
SwiftUI un problème dont le code dans
UIKit incomparablement plus complexe et ne peut pas être
UIKit à mon avis de manière lisible.
La tâche est liée au dernier concours
Telegram pour
Android développeurs
Android ,
iOS et
JS , qui s'est tenu du 10 mars au 24 mars 2019. Dans ce concours, une tâche simple a été proposée pour afficher graphiquement l'intensité d'utilisation d'une certaine ressource sur Internet en fonction du temps en fonction
JSON données
JSON . En tant que développeur
iOS , vous devez utiliser
Swift pour soumettre du code écrit à partir de zéro à la concurrence sans utiliser de bibliothèques spécialisées étrangères pour le traçage.
Cette tâche exigeait des compétences pour travailler avec les capacités graphiques et d'animation d'iOS:
Core Graphics ,
Core Animation ,
Metal ,
OpenGL ES . Certains de ces outils sont des outils de programmation de bas niveau non orientés objet. Essentiellement, dans
iOS il n'y avait pas de modèles acceptables pour résoudre des tâches graphiques aussi légères à première vue. Par conséquent, chaque candidat a inventé son propre animateur (
Render ) basé sur
Metal ,
CALayers ,
OpenGL ,
CADisplayLink . Cela a généré des tonnes de code à partir desquelles il n'a pas été possible d'emprunter et de développer quoi que ce soit, car ce sont des œuvres purement «protégées par le droit d'auteur» que seuls les auteurs peuvent réellement développer. Mais cela ne devrait pas être le cas.
Et début juin à la
WWDC 2019 ,
SwifUI apparaît - un nouveau
framework développé par
Apple , écrit en
Swift et conçu pour décrire de manière déclarative l'interface utilisateur (
UI ) dans le code. Vous déterminez quelles sous-
subviews affichées dans votre
View , quelles données entraînent la
subviews ces sous-
subviews , quels modificateurs vous devez leur appliquer, pour les positionner au bon endroit, pour avoir la bonne taille et le bon style. Un élément tout aussi important de
SwiftUI est le contrôle du flux de données modifiables par l'utilisateur, qui à son tour met à jour l'
UI .
Dans cet article, je veux montrer comment la tâche même du concours
Telegram sur
SwiftUI est résolue rapidement et facilement. De plus, c'est un processus très excitant.
Tâche
L'application concurrentielle doit afficher simultanément 5 «ensembles de graphiques» à l'écran en utilisant les données fournies par
Telegram . Pour un «ensemble de graphiques», l'
UI la suivante:

Dans la partie supérieure, il y a une «zone de graphique» avec une échelle commune le long de l'axe Y normal avec des marques et des lignes de quadrillage horizontales. En dessous, une ligne rampante avec des horodatages le long de l'axe X comme dates.
Encore plus bas est la soi-disant «mini carte» (comme dans
Xcode 11 ), c'est-à-dire une «fenêtre» transparente qui définit la partie de la période de nos «graphiques», qui est présentée plus en détail dans la «zone des graphiques» supérieure. Cette «mini-carte» peut non seulement être déplacée le long de l'axe
X , mais également sa largeur peut être modifiée, ce qui affecte l'échelle de temps dans la «zone des graphiques».
A l'aide de
checkboxs peintes aux couleurs des «Graphiques» et munies de leurs noms, vous pouvez refuser d'afficher les «Graphiques» correspondant à cette couleur dans la «Zone des Graphiques».
Il existe de nombreux "ensembles de graphiques", dans notre exemple de test, il y en a 5, par exemple, et ils devraient tous être situés sur un seul écran.
Dans l'
UI conçue à l'aide de
SwiftUI il n'est pas nécessaire d'avoir un bouton pour basculer entre les modes
Dark et
Light , il est déjà intégré à
SwiftUI . En outre,
SwiftUI beaucoup plus d'options pour combiner des «ensembles de graphiques» (c'est-à-dire les ensembles d'écrans présentés ci-dessus) que de simplement faire défiler un tableau, et nous examinerons certaines de ces options très intéressantes.
Mais d'abord, concentrons-nous sur l'affichage d'un «ensemble de
SwiftUI » pour lequel
SwiftUI allons créer un
ChartView :

SwiftUI vous permet de créer et de tester une
UI complexe en petites pièces, puis il est très facile d'assembler ces pièces en un puzzle. Nous le ferons. Notre
ChartView très bien en ces petits morceaux:
GraphsForChart - ce sont les graphiques eux-mêmes, construits pour un "ensemble de graphiques" spécifique. Les «graphiques» sont affichés pour la plage de temps contrôlée par l'utilisateur à l'aide de la «mini-carte» RangeView , qui sera présentée ci-dessous.YTickerView est l'axe Y avec des élévations et la grille horizontale correspondante.IndicatorView est un indicateur piloté par l'utilisateur horizontalement qui vous permet de visualiser les valeurs des "graphiques" et le temps pour la position de l'indicateur correspondant sur l'axe du temps sur l'axe XTickerView - "ligne rampante" montrant les horodatages sur l'axe X comme des dates,RangeView - une «fenêtre» temporaire, personnalisable par l'utilisateur à l'aide de gestes, pour définir l'intervalle de temps pour les «graphiques»,CheckMarksView - contient des «boutons» colorés aux couleurs de «Graphiques» et vous permettant de contrôler la présence de « ChartView » sur ChartView .
ChartView utilisateur peut interagir avec
ChartView de trois manières:
1. contrôler la «mini-carte» en utilisant le geste
DragGesture - il peut déplacer la «fenêtre» temporaire vers la droite et la gauche et diminuer / augmenter sa taille:

2. déplacez l'indicateur dans le sens horizontal, en montrant les valeurs des "graphiques" à un point fixe dans le temps:

3. masquer / afficher certains «graphiques» à l'aide de boutons colorés dans les couleurs des «graphiques» et situés au bas de
ChartView :

Nous pouvons combiner différents «ensembles de graphiques» (nous en avons 5 dans les données de test) de différentes manières, par exemple, en les plaçant tous simultanément sur un seul écran à l'aide de la liste (comme un tableau déroulant de haut en bas):

ou en utilisant
ScrollView et une pile horizontale de
HStack avec un effet 3D:

... ou sous la forme d'un
ZStack "cartes" superposées les unes aux autres, dont l'ordre peut être modifié: la "carte" supérieure avec "" un ensemble de graphiques "peut être tirée suffisamment loin pour regarder la carte suivante, et si vous continuez à la faire glisser, alors elle" va "à la dernière place dans
ZStack , et cette prochaine" carte "" va de l'avant ":

Dans ces
UI complexes - une «table déroulante», une pile horizontale avec un effet
3D , une
ZStack «cartes» superposées les unes aux autres - tous les moyens d'interaction avec l'utilisateur fonctionnent à fond: se déplacer le long de la chronologie et changer l '«échelle» de la
mini - map , des indicateurs et des boutons de masquage "Graphiques".
De plus, nous examinerons en détail la conception de cette
UI aide de
SwiftUI - des éléments simples à leurs compositions plus complexes. Mais d'abord, comprenons la structure de données que nous avons.
Ainsi, la solution à notre problème a été divisée en plusieurs étapes:
- Téléchargez les données d'un fichier
JSON et présentez-les dans un format "interne" pratique - Créer une
UI pour un «ensemble de graphiques» - Combinez différents «jeux de cartes»
Télécharger les données
À notre disposition,
Telegram a fourni
des données
JSON contenant plusieurs «ensembles de graphiques». Chaque «ensemble de
chart » d'un
chart contient plusieurs «graphiques» (ou «lignes») de
chart.columns de
chart.columns . Chaque «graphique» («lignes») a un repère à la position
0 -
"x" ,
"y0" ,
"y1" ,
"y2" ,
"y3" , suivi de l'une ou l'autre des valeurs de temps sur l'axe X («x») ou les valeurs de "Graphics" ("Lines") (
"y0" ,
"y1" ,
"y2" ,
"y3" ) sur l'axe
Y :

La présence de toutes les «lignes» dans le «jeu de cartes» est facultative. Les valeurs de la "colonne" x sont des horodatages UNIX en millisecondes.
De plus, chaque «jeu de
chart » individuel du
chart est fourni avec des couleurs de
chart.colors au format de 6 chiffres hexadécimaux (par exemple, «#AAAAAA») et des
chart.names .
Pour construire le modèle de données situé dans le fichier
JSON , j'ai utilisé l'excellent service
quicktype . Sur ce site, vous insérez un morceau de texte à partir d'un fichier
JSON et spécifiez le langage de programmation (
Swift ), le nom de la structure (
Chart ), qui sera formé après «l'analyse» de ces données
JSON et c'est tout.
Un code est généré dans la partie centrale de l'écran, que nous copions dans notre application dans un fichier séparé nommé
Chart.swift . C'est là que nous placerons le modèle de données au format JSON. En utilisant le chargeur de données du fichier
JSON vers le modèle emprunté aux
démos SwiftUI Generic , j'ai obtenu un tableau de
columns: [ChartElement] , qui est une collection d '«ensembles de
columns: [ChartElement] » au format
Telegram .
La
ChartElement données
ChartElement , contenant des tableaux d'éléments hétérogènes, n'est pas très appropriée pour un travail interactif intensif avec des graphiques, en outre, les horodatages sont présentés au format
UNIX en millisecondes (par exemple,
1542412800000, 1542499200000, 1542585600000, 1542672000000 ) et les couleurs au format hexadécimal 6 chiffres (par exemple,
"#AAAAAA" ).
Par conséquent, à l'intérieur de notre application, nous utiliserons les mêmes données, mais dans un format «interne» différent et assez simple
[LinesSet] . Le tableau
[LinesSet] est une collection de «jeux de
LinesSet »
LinesSet , chacun contenant des horodatages
xTime au format
"Feb 12, 2019" (axe
X ) et plusieurs
lines «Graphiques» (axe
Y ):

Les données de chaque graphique linéaire (ligne) sont présentées
- un tableau de
points: [Int] entiers points: [Int] , - nommé "Graphics"
title: String , - type "Graphics"
type: String? , color : UIColor au format UIColor ,- nombre de points
countY: Int .
De plus, tout «graphique» peut être masqué ou affiché selon la valeur de
isHidden: Bool . Les
upperBound lowerBound et
upperBound ajuster la plage de temps prennent des valeurs de
0 à
1 et affichent non seulement la taille de la fenêtre temporelle «mini-carte» (
upperBound -
lowerBound ), mais aussi son emplacement sur l'axe des temps
X :

Les structures de données
JSON [ChartElement] et les structures de données des
LinesSet et
Line «internes» se trouvent dans le fichier
Chart.swift . Le code pour charger
JSON données
JSON et les convertir en une structure interne se trouve dans le fichier
Data.swift . Des détails sur ces transformations peuvent être trouvés
ici .
En conséquence, nous avons reçu des données sur les «jeux de graphiques» au format interne sous forme de tableau de
chartsData .

Il s'agit de notre
données, mais pour travailler dans
SwiftUI il est nécessaire de s'assurer que toutes les modifications apportées par l'utilisateur dans le tableau
chartsData (modification de la «fenêtre» temporaire, masquage / affichage des «graphiques») conduisent à des mises à jour automatiques de nos
Views .
Nous allons créer
@EnvironmentObject . Cela nous permettra d'utiliser le
données partout où cela est nécessaire et, en outre, de mettre à jour automatiquement nos
Views si les données changent. C'est quelque chose comme
Singleton ou des données globales.
@EnvironmentObject nous oblige à créer une
final class UserData , qui se trouve dans
le fichier
UserData.swift , stocke les données
chartsData et implémente le protocole
ObservableObject :

La présence de "wrappers"
@Published vous permettra de publier des "nouvelles" que ces propriétés des
charts de la classe
UserData ont changé, de sorte que toutes les
Views "abonnées à ces nouvelles" dans
SwiftUI pourront sélectionner automatiquement les nouvelles données et les mettre à jour.
Rappelez-vous que dans la propriété des
charts , les valeurs
isHidden peuvent changer pour n'importe quel «
isHidden » (elles vous permettent de masquer ou d'afficher ces «graphiques»), ainsi que la
lowerBound inférieure inférieure et la
upperBound supérieure supérieure
upperBound l'intervalle de temps pour chaque «ensemble de graphiques» individuel.
Nous voulons utiliser la propriété
charts de la classe
UserData dans notre application et nous n'avons pas à les synchroniser manuellement avec l'
UI grâce à
@EnvironmentObject .
Pour ce faire, lors du démarrage de l'application, nous devons créer une instance de la classe
UserData () afin de pouvoir ensuite y accéder n'importe où dans notre application. Nous le ferons dans le fichier
SceneDelegate.swift à l'intérieur de la méthode
scene (_ : , willConnectTo: , options: ) . C'est ici que notre
ContentView est créé et lancé, et c'est ici que nous devons transmettre le
ContentView n'importe quel
@EnvironmentObject nous avons
@EnvironmentObject afin que
SwiftUI puisse les rendre disponibles à n'importe quelle autre
View :

Maintenant, dans n'importe quelle
View pour accéder aux données
@Published de la classe
UserData , nous devons créer la variable
var à l'aide du wrapper
@EnvironmentObject . Par exemple, lors de la définition de la plage de temps dans
RangeView nous créons la variable
var userData avec le
UserData TYPE:

Ainsi, dès que nous avons implémenté un
@EnvironmentObject dans «l'environnement» de l'application, nous pouvons immédiatement commencer à l'utiliser soit au plus haut niveau, soit au 10e niveau ci-dessous - cela n'a pas d'importance. Mais plus important encore, chaque fois qu'une
View change d '"environnement", toutes les
Views qui ont cet
@EnvironmentObject seront automatiquement
@EnvironmentObject à
@EnvironmentObject , assurant ainsi la synchronisation avec les données.
Passons à la conception de l'interface utilisateur (
UI ).
Interface utilisateur (UI) pour un «ensemble de graphiques»
SwiftUI propose une technologie composite pour créer des
SwiftUI partir de nombreuses petites
Views , et nous avons déjà vu que notre application correspond très bien à cette technologie, car elle se divise en petits morceaux: l'ensemble «
ChartView Charts», «
GraphsForChart Charts», les graduations en
Y -
YTickerView , indicateur de valeurs «Graphs» piloté par l'utilisateur
IndicatorView , ligne
TickerView «en cours d'exécution» avec
TickerView sur l'axe
X ,
RangeView «fenêtre temporelle»
RangeView , marques pour masquer / afficher
CheckMarksView «Graphiques». Nous pouvons non seulement créer toutes ces
Views indépendamment les unes des autres, mais également tester immédiatement dans
Xcode 11 utilisant des
Previews (vues «en direct» préliminaires) sur les données de test. Vous serez surpris de la simplicité du code pour les créer à partir d'autres
Views plus basiques.
GraphView - «Graphique» («Ligne»)
La première
View , par laquelle nous allons commencer, est en fait le «graphique» lui-même (ou «ligne»). Nous l'appellerons
GraphView :

La création d'un
GraphView , comme d'habitude, commence par la création d'un nouveau fichier dans
Xcode 11 utilisant le menu
File →
New →
File :

Ensuite, nous sélectionnons le TYPE souhaité du fichier - c'est le fichier
SwiftUI :

... donnez le nom "GraphView" à notre
View et indiquez son emplacement:

Cliquez sur le bouton
"Create" et obtenez une
View standard avec du
Text ( "Hello World!") Au milieu de l'écran:

Notre tâche est de remplacer le
Text ("Hello World!") «graphique», mais voyons d'abord quelles données initiales nous avons pour créer le «graphique»:
- nous avons les valeurs de
line.points "Graphics" line: Line , - plage temporelle
rangeTime , qui est une plage d'index Range xTime sur l'axe X, - plage de valeurs
rangeY: Range «Graphics» pour l' rangeY: Range Y, - épaisseur de la ligne de
lineWidth trait "Graphics" lineWidth .
Ajoutez ces propriétés à la structure
GraphView :

Si nous voulons utiliser pour nos
Previews «graphiques» (aperçus), qui ne sont possibles que pour
MacOS Catalyna , nous devons alors lancer un
GraphView avec la plage d'index
rangeTime et les données de
line des «graphiques» lui-même:

Nous avons déjà les données de test
chartsData que nous avons obtenues du fichier
chart.json JSON , et nous les avons utilisées pour les
Previews .
Dans notre cas, ce sera le premier «jeu de
chartsData[0] »
chartsData[0] et le premier «graphique» dans ce jeu de
chartsData[0].lines[0] , que nous fournirons
GraphView comme paramètre de
line .
Nous utiliserons la plage complète des indices
0..<(chartsData[0].xTime.count - 1) comme intervalle de temps
rangeTime .
Les
lineWidth rangeY et
lineWidth peuvent être définis en externe ou non, car ils ont déjà des valeurs initiales:
rangeY est
nil et
lineWidth vaut
1 .
Nous avons intentionnellement fait un TYPE de la propriété
Optional rangeY TYPE, car si
rangeY pas défini en externe et
rangeY = nil , alors nous calculons les valeurs minimum
minY et maximum
maxY du "Graphics" directement à partir des données
line.points :

Ce code se compile, mais nous avons toujours une
View standard à l'écran avec le texte
Text ("Hello World!") Au milieu de l'écran:

Parce que dans le
body nous devons remplacer le texte
Text ("Hello World!") Path , qui va construire notre "Graph: points:
line.points utilisant la commande
addLines(_:) (presque comme dans
Core Graphics )


Nous encerclerons le
stroke (...) notre
Path ligne dont l'épaisseur est
lineWidth , et la couleur de la ligne de trait correspondra à la couleur par défaut (c'est-à-dire le noir):

Nous pouvons remplacer la couleur noire de la ligne de trait par la couleur spécifiée dans notre
line.color «Ligne» particulier «Couleur»:

Pour que notre «graphique» soit placé dans des rectangles de n'importe quelle taille, nous utilisons le conteneur
GeometryReader . Dans la documentation
Apple GeometryReader est une vue «conteneur» qui définit son contenu en fonction de sa propre
size et de son propre espace de coordonnées. Essentiellement,
GeometryReader est une autre
View ! Parce que presque TOUT dans
SwiftUI est
View !
GeometryReader vous permettra, contrairement à d'autres
Views d'accéder à des informations utiles supplémentaires que vous pouvez utiliser lors de la conception de votre
View personnalisée.
Nous utilisons les conteneurs
GeometryReader et
Path pour créer
GraphView adaptable à n'importe quelle taille. Et si nous regardons attentivement notre code, nous verrons dans la fermeture du
GeometryReader variable appelée
geometry :

Cette variable a le
GeometryProxy TYPE, qui à son tour est une structure
struct avec de nombreuses "surprises":
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 }
D'après la définition de
GeometryProxy , nous voyons qu'il existe deux variables calculées
var size et
var safeAreaInsets , un
frame( in:) fonction
frame( in:) et un
subscript getter . Nous avions seulement besoin de la variable de
size pour déterminer la largeur de
geometry.size.width et la hauteur de
geometry.size.height zone de dessin «Graphics».
De plus, nous permettons à notre "Graphique" de s'animer en utilisant le modificateur d'
animation (.linear(duration: 0.6)) .

GraphView_Previews nous permet de tester très simplement tous les «graphiques» de n'importe quel «ensemble». Vous trouverez ci-dessous le «graphique» de «l'ensemble de graphiques» avec l'index 4:
chartsData[4] et l'index 0 «Graphics» dans cet ensemble:
chartsData[4].lines[0] :

Nous définissons la
height «Graphics» à 400 à l'aide du
frame (height: 400) , la largeur reste la même que la largeur de l'écran. Si nous n'utilisions pas de
frame (height: 400) , le "Graphique" occuperait tout l'écran.
rangeY GraphView nil , , «»
rangeTime :

Path animation (.linear(duration: 0.6)) , , ,
rangeY «». «» «»
rangeY .
:
SwiftUI , «»
rangeY ,
SwiftUI , «»
rangeY ,
SwiftUI Animatable .
,
View - «»,
View ,
Shape ,
Animatable . ,
animatableData , ,
EmptyAnimatableData , .
, , «»
GraphView Shape . ,
func path (in rect:CGRect) -> Path , , ,
animatableData , :

,
SwiftUI «Advanced SwiftUI Animations – Part 1: Paths» .
«»
Graph GraphViewNew «» :

,
GeometryReader «»
GraphViewNew ,
Shape «»
Graph View .
Previews ,
GraphView :

GraphViewNew «».
GraphsForChart — «» («»)
View - «» («») « »
chart rangeTime Y , «»
lineWidth :

GraphView GraphViewNew ,
GraphsForChart GraphsForChart.swift « »:
- « »
chart: LineSet ( Y ), rangeTime: Range ( X ) «»,- «»
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 . .
IndicatorView Previews « »
chart View «»
GraphsForChart :

, ,
rangeTime IndicatorView , «»
GraphsForChart . , «», «», .
TickerView - X .
«» ,
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 .
View Y YMarkView .
YMarkView View VStack ,
Path ( )
Text :

Y « »
chart YTickerView .
rangeY «», « »
rangeOfRanges . Y
estimatedMarksNumber :

YTickerView «»
rangeY . Y - —
overlay …

, 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 .
Views ,
DragGesture (
lowerBound ,
upperBound ) , (
lowerBound ,
upperBound ), ,
@EnvironmentObject var userData: UserData :

var userData Views , .
RangeView «»,
DragGesture :
1. «», «»
X , :

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 HStack checkBoxes isHidden «» « »
chart :

CheckBox Button CheckButton ,
SimulatedButton .

Button ,
List , , «» .
,
Xcode 11, 1 .
SimulatedButton .
SimulatedButton ,
CheckButton 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 —
Views ,
Views — ..,
Lego .
SwiftUI Views :
VStack ,HStack ,- «»
ZStack , Group ,ScrollView ,List ,Form ,- «»
TabView - ..
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 :

TabView 3- « » ChartView .

3 c
Image Text ,
VStack .
3- « »
ChartViews :
- « »
ListChartViews , - 3D
HStackChartViews , - ZStack «»
OverlayCardsViews .
: «»
mini - map , «». 3- .
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 , .