
Comenzaré con el comentario de que la aplicación que se trata en este artículo requiere
Xcode 11 y
MacOS Catalina si desea usar
Live Previews
, y
Mojave
si usa el simulador. El código de la aplicación está en
Github .
Este año en
WWDC 2019 ,
Apple
anunció
SwiftUI
, una nueva forma declarativa de construir una interfaz de usuario (UI) en todos
Apple
dispositivos
Apple
. Esto es casi una desviación completa del
UIKit
habitual, y yo, como muchos otros desarrolladores, realmente quería ver esta nueva herramienta en acción.
Este artículo presenta la experiencia de resolver con
SwiftUI
un problema cuyo código dentro de
UIKit
incomparablemente más complejo y, en mi opinión, no se puede
UIKit
de una manera legible.
La tarea está relacionada con el último concurso de
Telegram para desarrolladores de
Android
,
iOS
y
JS
, que se realizó del 10 al 24 de marzo de 2019. En esta competencia, se propuso una tarea simple para mostrar gráficamente la intensidad de uso de un determinado recurso en Internet dependiendo del tiempo basado en datos
JSON
. Como desarrollador de
iOS
, debe usar
Swift
para enviar código escrito desde cero a la competencia sin usar bibliotecas especializadas extrañas para trazar.
Esta tarea requería habilidades para trabajar con las capacidades gráficas y de animación de iOS:
Core Graphics ,
Core Animation ,
Metal ,
OpenGL ES . Algunas de estas herramientas son herramientas de programación de bajo nivel, no orientadas a objetos. Esencialmente, en
iOS
no había plantillas aceptables para resolver tareas gráficas aparentemente tan ligeras a primera vista. Por lo tanto, cada concursante inventó su propio animador (
Render ) basado en
Metal ,
CALayers ,
OpenGL ,
CADisplayLink . Esto generó toneladas de código del cual no fue posible tomar prestado y desarrollar nada, ya que estos son trabajos puramente "con derechos de autor" que solo los autores realmente pueden desarrollar. Sin embargo, esto no debería ser así.
Y a principios de junio en
WWDC 2019 , aparece
SwifUI
, un nuevo
framework
desarrollado por
Apple
, escrito en
Swift
y diseñado para describir declarativamente la interfaz de usuario (
UI
) en el código. Usted determina qué
subviews
muestran en su
View
, qué datos hacen que estas
subviews
cambien, qué modificadores necesita aplicarles, para colocarlas en el lugar correcto, para tener el tamaño y estilo correctos. Un elemento igualmente importante de
SwiftUI
es el control del flujo de datos modificables por el usuario, que a su vez actualiza la
UI
.
En este artículo quiero mostrar cómo la tarea misma del concurso
Telegram en
SwiftUI
se resuelve rápida y fácilmente. Además, este es un proceso muy emocionante.
Tarea
La aplicación competitiva debería mostrar simultáneamente 5 "conjuntos de gráficos" en la pantalla utilizando los datos proporcionados por
Telegram
. Para un "conjunto de gráficos", la
UI
siguiente:

En la parte superior hay una "zona de gráfico" con una escala común a lo largo del eje Y normal con marcas y líneas de cuadrícula horizontales. Debajo de eso hay una línea progresiva con marcas de tiempo a lo largo del eje X como fechas.
Aún más bajo está el llamado "mini mapa" (como en
Xcode 11
), es decir, una "ventana" transparente que define esa parte del período de tiempo de nuestros "Gráficos", que se presenta con más detalle en la "zona de Gráficos" superior. Este "mini mapa" no solo se puede mover a lo largo del eje
X
, sino que también se puede cambiar su ancho, lo que afecta la escala de tiempo en el "área de Gráficos".
Con la ayuda de
checkboxs
de
checkboxs
pintadas con los colores de "Gráficos" y provistos de sus nombres, puede negarse a mostrar los "Gráficos" correspondientes a este color en la "zona de Gráficos".
Hay muchos de estos "conjuntos de gráficos", en nuestro ejemplo de prueba hay 5 de ellos, por ejemplo, y todos deben ubicarse en una pantalla.
En la
UI
diseñada con
SwiftUI
no es necesario un botón para cambiar entre los modos
Dark
y
Light
, esto ya está integrado en
SwiftUI
. Además,
SwiftUI
muchas más opciones para combinar "conjuntos de gráficos" (es decir, los conjuntos de pantallas presentados anteriormente) que simplemente desplazarse hacia abajo en una tabla, y veremos algunas de estas opciones muy interesantes.
Pero primero, centrémonos en mostrar un "conjunto de
SwiftUI
" para el que crearemos un
ChartView
:

SwiftUI
permite crear y probar una
UI
compleja en piezas pequeñas, y luego es muy fácil ensamblar estas piezas en un rompecabezas. Nosotros lo haremos. Nuestro
ChartView
muy bien en estas pequeñas piezas:
GraphsForChart
: estos son los gráficos en sí, creados para un "conjunto de gráficos" específico. Los "gráficos" se muestran para el rango de tiempo controlado por el usuario utilizando el "mini mapa" RangeView
, que se presentará a continuación.YTickerView
es el eje Y
con elevaciones y la cuadrícula horizontal correspondiente.IndicatorView
es un indicador controlado horizontalmente por el usuario que le permite ver los valores de "Gráficos" y el tiempo para la posición del indicador correspondiente en el eje de tiempo en el eje X
TickerView
: "línea progresiva" que muestra marcas de tiempo en el eje X
como fechas,RangeView
: una "ventana" temporal, personalizable por el usuario mediante gestos, para establecer el intervalo de tiempo para "Gráficos",CheckMarksView
: contiene “botones” coloreados en los colores de “Gráficos” y le permite controlar la presencia de “ ChartView
” en ChartView
.
ChartView
usuario puede interactuar con
ChartView
de tres maneras:
1. controle el "mini mapa" con el gesto
DragGesture
: puede desplazar la "ventana" temporal hacia la derecha e izquierda y disminuir / aumentar su tamaño:

2. mueva el indicador en dirección horizontal, mostrando los valores de los "Gráficos" en un punto fijo en el tiempo:

3. ocultar / mostrar ciertos "Gráficos" usando botones de colores en los "Gráficos" y ubicados en la parte inferior de la
ChartView
de
ChartView
:

Podemos combinar varios "Conjuntos de gráficos" (tenemos 5 de ellos en los datos de prueba) de diferentes maneras, por ejemplo, colocándolos a todos simultáneamente en una pantalla usando la
List
Lista (como una tabla desplazable hacia arriba y hacia abajo):

o usando
ScrollView
y una pila horizontal de
HStack
con un efecto 3D:

... o en forma de una
ZStack
"cartas" superpuestas entre sí, cuyo orden se puede cambiar: la "carta" superior con "" un conjunto de Gráficos "se puede tirar lo suficiente como para mirar la siguiente carta, y si continúa arrastrándola hacia abajo, entonces" va "al último lugar en
ZStack
, y esta próxima" tarjeta "" sigue adelante ":

En estas
UI
complejas (una "tabla desplazable", una pila horizontal con un efecto
3D
, una
ZStack
"cartas" superpuestas entre sí), todos los medios de interacción con el usuario funcionan completamente: moverse a lo largo de la línea de tiempo y cambiar la "escala" del
mini - map
, el indicador y los botones de ocultar "Gráficos".
Además, consideraremos en detalle el diseño de esta
UI
de
UI
utilizando
SwiftUI
, desde elementos simples hasta sus composiciones más complejas. Pero primero, comprendamos la estructura de datos que tenemos.
Entonces, la solución a nuestro problema se dividió en varias etapas:
- Descargue datos de un archivo
JSON
y preséntelos en un conveniente formato "interno" - Crear
UI
de UI
para un "conjunto de gráficos" - Combina varios "conjuntos de gráficos"
Descargar datos
A nuestra disposición,
Telegram proporcionó datos
JSON que contienen varios "conjuntos de gráficos". Cada "conjunto de
chart
" individual de un
chart
contiene varios "Gráficos" (o "Líneas") de
chart.columns
de
chart.columns
. Cada "Gráfico" ("Líneas") tiene una marca en la posición
0
-
"x"
,
"y0"
,
"y1"
,
"y2"
,
"y3"
, seguido de los valores de tiempo en el eje X ("x") , o los valores de "Gráficos" ("Líneas") (
"y0"
,
"y1"
,
"y2"
,
"y3"
) en el eje
Y
:

La presencia de todas las "Líneas" en el "conjunto de gráficos" es opcional. Los valores para la "columna" x son marcas de tiempo UNIX en milisegundos.
Además, cada "conjunto de
chart
" individual del
chart
se suministra con colores
chart.colors
en el formato de 6 dígitos hexadecimales (por ejemplo, "#AAAAAA") y
chart.names
.
Para construir el modelo de datos ubicado en el archivo
JSON
, utilicé el excelente servicio de tipo
rápido . En este sitio, inserta un fragmento de texto de un archivo
JSON
y especifica el lenguaje de programación (
Swift
), el nombre de la estructura (
Chart
), que se formará después del "análisis" de estos datos
JSON
y eso es todo.
Se genera un código en la parte central de la pantalla, que copiamos en nuestra aplicación en un archivo separado llamado
Chart.swift
. Aquí es donde colocaremos el modelo de datos en formato JSON. Utilizando el cargador de datos del archivo
JSON
al modelo prestado de las
demostraciones Generic
SwiftUI , obtuve una serie de
columns: [ChartElement]
, que es una colección de "conjuntos de
columns: [ChartElement]
" en el formato de
Telegram
.
La
ChartElement
datos de
ChartElement
, que contiene matrices de elementos heterogéneos, no es muy adecuada para el trabajo interactivo intensivo con gráficos, además, las marcas de tiempo se presentan en formato
UNIX
en milisegundos (por ejemplo,
1542412800000, 1542499200000, 1542585600000, 1542672000000
) y colores en 6 formatos hexadecimales. dígitos (por ejemplo,
"#AAAAAA"
).
Por lo tanto, dentro de nuestra aplicación utilizaremos los mismos datos, pero en un formato "interno" y bastante simple
[LinesSet]
. La matriz
[LinesSet]
es una colección de
LinesSet
"Sets de
LinesSet
", cada uno de los cuales contiene
xTime
tiempo
xTime
en el formato
"Feb 12, 2019"
(eje
X
) y varias
lines
"Gráficos" (eje
Y
):

Se presentan los datos para cada gráfico de líneas (línea)
- una serie de
points: [Int]
enteros points: [Int]
, - titulado "Gráficos"
title: String
, - tipo "Gráficos"
type: String?
, color : UIColor
en el formato UIColor
,- número de puntos
countY: Int
.
Además, cualquier "Gráfico" se puede ocultar o mostrar dependiendo del valor de
isHidden: Bool
. Los
upperBound
lowerBound
y
upperBound
ajustar el rango de tiempo toman valores de
0
a
1
y muestran no solo el tamaño de la ventana de tiempo del "mini mapa" (
upperBound
-
lowerBound
), sino también su ubicación en el eje de tiempo
X
:

Las estructuras de datos
JSON
[ChartElement]
y las estructuras de datos de las
LinesSet
"internas" de
LinesSet
y
Line
están en el archivo
Chart.swift . El código para cargar datos
JSON
y convertirlos en una estructura interna se encuentra en el archivo
Data.swift . Los detalles sobre estas transformaciones se pueden encontrar
aquí .
Como resultado, recibimos datos sobre los "conjuntos de gráficos" en el formato interno como una matriz de
chartsData
.

Este es nuestro
datos, pero para trabajar en
SwiftUI
es necesario asegurarse de que cualquier cambio realizado por el usuario en la matriz de datos de
chartsData
(cambiar la "ventana" temporal, ocultar / mostrar los "Gráficos") conduzca a actualizaciones automáticas de nuestras
Views
.
Crearemos
@EnvironmentObject
. Esto nos permitirá usar el
datos donde sea necesario y, además, actualizar automáticamente nuestras
Views
si los datos cambian. Esto es algo así como
Singleton
o datos globales.
@EnvironmentObject
requiere que
@EnvironmentObject
una
final class UserData
, que se encuentra en el archivo
UserData.swift , almacena los datos de
chartsData
e implementa el protocolo
ObservableObject
:

La presencia de "wrappers"
@Published
le permitirá publicar "noticias" de que estas propiedades de los
charts
de la clase
UserData
han cambiado, de modo que cualquier
Views
"suscrita a esta noticia" en
SwiftUI
podrá seleccionar automáticamente nuevos datos y actualizaciones.
Recuerde que en la propiedad de
charts
, los valores de
isHidden
pueden cambiar para cualquier "
isHidden
" (le permiten ocultar o mostrar estos "Gráficos"), así como el
lowerBound
inferior inferior y el
upperBound
superior superior
upperBound
intervalo de tiempo para cada "conjunto de Gráficos" individual.
Queremos usar la propiedad de
charts
de la clase
UserData
toda nuestra aplicación y no tenemos que sincronizarlos manualmente con la
UI
usuario gracias a
@EnvironmentObject
.
Para hacer esto, al iniciar la aplicación, debemos crear una instancia de la clase
UserData ()
para que posteriormente podamos acceder a ella desde cualquier lugar de nuestra aplicación. Haremos esto en el archivo
SceneDelegate.swift
dentro de la
scene (_ : , willConnectTo: , options: )
método. Aquí es donde se crea y lanza nuestro
ContentView
, y es aquí donde debemos pasar el
ContentView
cualquier
@EnvironmentObject
que
@EnvironmentObject
para que
SwiftUI
pueda ponerlos a disposición de cualquier otra
View
:

Ahora, en cualquier
View
para acceder a los datos
@Published
de la clase
UserData
, necesitamos crear la variable
var
utilizando el contenedor
@EnvironmentObject
. Por ejemplo, al establecer el rango de tiempo en
RangeView
creamos la variable
var userData
con el
UserData
TYPE:

Por lo tanto, tan pronto como hayamos implementado algún
@EnvironmentObject
en el "entorno" de la aplicación, podemos comenzar a usarlo de inmediato en el nivel más alto o en el décimo nivel a continuación, no importa. Pero lo más importante, cada vez que una
View
cambia el "entorno", todas las
Views
que tienen este
@EnvironmentObject
se
@EnvironmentObject
automáticamente, asegurando así la sincronización con los datos.
Pasemos a diseñar la interfaz de usuario (
UI
).
Interfaz de usuario (UI) para un "conjunto de gráficos"
SwiftUI
ofrece una tecnología compuesta para crear
SwiftUI
de
SwiftUI
partir de muchas
Views
pequeñas, y ya hemos visto que nuestra aplicación encaja muy bien con esta tecnología, ya que se divide en piezas pequeñas: el conjunto "
ChartView
Charts", "
GraphsForChart
Charts",
Y
-axis
YTickerView
-
YTickerView
, el valor del indicador controlado por el usuario de "Charts"
IndicatorView
, "
TickerView
"
TickerView
con
TickerView
de tiempo en el eje
X
,
RangeView
"ventana de tiempo" controlada por el usuario, marcas al ocultar / mostrar "Charts"
CheckMarksView
. No solo podemos crear todas estas
Views
independientemente una de la otra, sino también probar de inmediato en
Xcode 11
usando Vistas
Previews
(
Previews
preliminares "en vivo") en los datos de prueba. Se sorprenderá de lo simple que es el código para crearlos desde otras
Views
más básicas.
GraphView
- "Gráfico" ("Línea")
La primera
View
, con la que comenzaremos, es en realidad el "Gráfico" en sí (o "Línea"). Lo llamaremos
GraphView
:

Crear un
GraphView
, como siempre, comienza con la creación de un nuevo archivo en
Xcode 11
usando el menú
File
→
New
→
File
:

Luego seleccionamos el TIPO deseado del archivo: este es el archivo
SwiftUI
:

... asigne el nombre "GraphView" a nuestra
View
e indique su ubicación:

Haga clic en el botón
"Create"
y obtenga una
View
estándar con
Text ( "Hello World!")
En el centro de la pantalla:

Nuestra tarea es reemplazar el
Text ("Hello World!")
con "Gráfico", pero primero, veamos qué datos iniciales tenemos para crear el "Gráfico":
- tenemos los valores de
line.points
"Graphics" line: Line
, - rango de tiempo
rangeTime
, que es un rango de índices Range
xTime
Range
tiempo xTime
en el eje X, - rango de valores
rangeY: Range
"Gráficos" para el rangeY: Range
Y, - grosor de la línea de trazo "Gráficos"
lineWidth
línea.
Agregue estas propiedades a la estructura
GraphView
:

Si queremos usar para nuestras vistas
Previews
(vistas previas) de "Gráficos", que solo son posibles para
MacOS Catalyna
, entonces debemos iniciar un
GraphView
con el rango de índices
rangeTime
y los datos de
line
de los "Gráficos" en sí:

Ya tenemos los datos de prueba de
chartsData
que obtuvimos del archivo
JSON
chart.json
, y los usamos para
Previews
.
En nuestro caso, este será el primer "conjunto de
chartsData[0]
"
chartsData[0]
y el primer "Gráfico" en este conjunto de
chartsData[0].lines[0]
, que proporcionaremos
GraphView
como parámetro de
line
.
Usaremos el rango completo de índices
0..<(chartsData[0].xTime.count - 1)
como el intervalo de tiempo
rangeTime
.
Los
lineWidth
rangeY
y
lineWidth
se pueden establecer externamente o no, ya que ya tienen valores iniciales:
rangeY
es
nil
y
lineWidth
es
1
.
Hicimos intencionalmente un TYPE de la propiedad
rangeY
Optional
con un TYPE, porque si
rangeY
no se establece externamente y
rangeY = nil
, entonces calculamos el mínimo
minY
y el máximo
maxY
"Gráficos" directamente a partir de los datos de
line.points
:

Este código se compila, pero aún tenemos una
View
estándar en la pantalla con el texto
Text ("Hello World!")
En el medio de la pantalla:

Porque en el
body
tenemos que reemplazar el texto
Text ("Hello World!")
Con
Path
, que construirá nuestro "Graph: points:
line.points
usando el
addLines(_:)
(casi como en
Core Graphics
)


Vamos a rodear el
stroke (...)
nuestra
Path
línea cuyo grosor es
lineWidth
, y el color de la línea del trazo corresponderá al color predeterminado (es decir, negro):

Podemos reemplazar el color negro de la línea de trazo con el color especificado en nuestro
line.color
"Color" particular de "Línea":

Para que nuestro "Gráfico" se coloque en rectángulos de cualquier tamaño, utilizamos el contenedor
GeometryReader
. En la documentación de
Apple
GeometryReader
es una vista de "contenedor" que define su contenido en función de su propio tamaño de
size
y espacio de coordenadas. Esencialmente,
GeometryReader
es otra
View
! ¡Porque casi TODO en
SwiftUI
es
View
!
GeometryReader
le permitirá a USTED, a diferencia de otras
Views
acceder a información adicional útil que puede usar al diseñar su
View
personalizada.
Utilizamos los contenedores
GeometryReader
y
Path
para crear
GraphView
adaptable a cualquier tamaño. Y si miramos cuidadosamente nuestro código, veremos en el cierre de
GeometryReader
variable llamada
geometry
:

Esta variable tiene el
GeometryProxy
TYPE, que a su vez es una
struct
estructura con muchas "sorpresas":
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 }
De la definición de
GeometryProxy
, vemos que hay dos variables calculadas
var size
y
var safeAreaInsets
, un
frame( in:)
función
frame( in:)
y un
subscript getter
. Solo necesitábamos la variable de
size
para determinar el ancho de
geometry.size.width
y la altura de
geometry.size.height
área de dibujo "Gráficos".
Además, habilitamos nuestro "Gráfico" para animar usando el modificador de
animation (.linear(duration: 0.6))
.

GraphView_Previews
nos permite probar fácilmente cualquier "Gráfico" de cualquier "conjunto". A continuación se muestra el "Gráfico" del "conjunto de gráficos" con el índice 4:
chartsData[4]
e índice 0 "Gráficos" en este conjunto:
chartsData[4].lines[0]
:

Establecemos la
height
"Gráficos" en 400 usando el
frame (height: 400)
, el ancho sigue siendo el mismo que el ancho de la pantalla. Si no utilizáramos el
frame (height: 400)
, entonces el "Gráfico" ocuparía toda la pantalla.
No especificamos un rango de valores rangeY
y GraphView
usamos el valor nil
predeterminado, en este caso, el "Gráfico" toma sus valores mínimos y máximos en el intervalo de tiempo rangeTime
:
aunque usamos un Path
modificador para nuestro modelo animation (.linear(duration: 0.6))
, no se producirá animación, por ejemplo, al cambiar el rango de rangeY
valores " Gráficos ". Un "gráfico" simplemente "saltará" de un valor de un rango rangeY
a otro sin ninguna animación.La razón es simple: enseñamos SwiftUI
cómo dibujar un "Gráfico" para un rango específico rangeY
, pero no enseñamos SwiftUI
cómo reproducir un "Gráfico" varias veces con valores intermedios del rango rangeY
entre el inicio y el final, y para eso enSwiftUI
cumple con el protocolo Animatable
.Afortunadamente, si el suyo View
es una "figura", es decir View
, que implementa un protocolo Shape
, entonces ya se ha implementado un protocolo para él Animatable
. Esto significa que hay una propiedad calculada animatableData
con la que podemos controlar el proceso de animación, pero de forma predeterminada se establece en EmptyAnimatableData
, es decir, no se produce animación.Para resolver el problema con la animación, primero debemos convertir nuestro "Gráfico" GraphView
en Shape
. Es muy simple, solo necesitamos implementar la función func path (in rect:CGRect) -> Path
que esencialmente ya tenemos e indicar con la ayuda de la propiedad calculada animatableData
qué datos queremos animar:
Tenga en cuenta que el tema del control de animación es un tema avanzado enSwiftUI
y puede obtener más información al respecto en el artículo "Animaciones avanzadas de SwiftUI - Parte 1: Rutas" . Podemos usar la"figura" resultante Graph
en una GraphViewNew
"Gráfica" mucho más simple con animación:
verá que no necesitábamos GeometryReader
nuestras nuevas "Gráficas" GraphViewNew
, porque gracias al protocolo Shape
nuestra "figura" Graph
podrá adaptarse a cualquier tamaño del padre View
.Naturalmente, Previews
obtuvimos el mismo resultado que en el caso con GraphView
:
En las siguientes combinaciones, usaremos los GraphViewNew
mismos "Gráficos" para mostrar los valores.GraphsForChart
- conjunto de "Gráficos" ("Líneas")
El propósito de este View
- Mostrar todo "Gráficos" ( "líneas") de "fijó gráficos" chart
en un intervalo de tiempo predeterminado rangeTime
con un eje común Y
, la anchura de las "líneas" es igual a lineWidth
:
Al igual que con GraphView
y GraphViewNew
vamos a crear GraphsForChart
un nuevo archivo GraphsForChart.swift
, y definir los datos de entrada para "Conjunto de gráficos":- el "conjunto de gráficos" en sí
chart: LineSet
(valores en Y
), - rango
rangeTime: Range
( X
) de índices de las marcas de tiempo de "Gráficos", - grosor de trazo de línea gráfica
lineWidth
El rango de valores rangeY: Range
para el "conjunto de gráficos" ( Y
) se calcula como la unión de los rangos de los isHidden = false
"Gráficos" no ocultos ( ) individuales incluidos en este "conjunto":
Para esto, utilizamos la función rangeOfRanges
: Mostramos
todos los Gráficos NO ocultos ( isHidden = false
) en ZStack
la construcción ForEach
, dando a cada "Gráfico" la posibilidad de aparecer en la pantalla y salir de la pantalla "usando el modificador" mover " transition(.move(edge: .top))
:
Gracias a este modificador, el proceso de ocultar y devolver los" Gráficos " ChartView
a la pantalla tendrá lugar en la pantalla con animación y dejará en claro al usuario por qué ha cambiado la escala Y
.Uso drawingGroup()
significa usoMetal
para dibujar formas gráficas. En nuestros datos de prueba y en el simulador, no sentirá la diferencia en la velocidad de dibujar con Metal
y Metal
, pero si reproduce muchos gráficos bastante voluminosos en alguno iPhone
, notará esta diferencia. Para una introducción más detallada, cuándo usarlo drawingGroup()
, puede ver el artículo "Animaciones avanzadas de SwiftUI - Parte 1: Rutas" o ver la sesión de video 237 WWDC 2019 ( Creación de vistas personalizadas con SwiftUI ).Como en el caso de las GraphViewNew
pruebas GraphsForChart
con vistas previas, Previews
podemos establecer cualquier "conjunto de gráficos", por ejemplo, con un índice 0
:
IndicatorView
- Indicador "Gráficos" movido horizontalmente.
Este indicador le permite obtener los valores exactos de "Gráficos" y el tiempo para el punto correspondiente en el tiempo X
:
El indicador se crea para un "conjunto de Gráficos" específico chart
y consiste en moverse a lo largo de la X
LÍNEA vertical con MARCAS en forma de "círculos" en lugar de los valores de "Gráficos". Un pequeño "CARTEL" se adjunta a la parte superior de esta línea vertical, que contiene los valores numéricos de los "Gráficos" y el tiempo.
El indicador se desliza por el usuario usando un gesto DragGesture
:
utilizamos la llamada ejecución de gesto "incremental". En lugar de una distancia continua desde el punto de partida value.translation.width
, onChanged
recibiremos constantemente la distancia desde el lugar donde estuvimos la última vez que realizamos el gesto en el controlador :value.translation.width - self.prevTranslation
. Esto nos proporcionará un movimiento suave del indicador.Para probar el indicador IndicatorView
con la ayuda de un Previews
"conjunto de Gráficos" dado, chart
podemos atraer la View
construcción de "Gráficos" GraphsForChart
:
podemos establecer cualquier rango de tiempo, pero coordinado entre sí, rangeTime
tanto para el indicador IndicatorView
como para los "Gráficos" GraphsForChart
. Esto nos permitirá asegurarnos de que los "círculos" que indican los valores de los "Gráficos" estén en los lugares correctos.TickerView
- X
con marcas
Hasta ahora, nuestros "Gráficos" están despersonalizados en el sentido de que NO X Y
tienen las escalas y marcas apropiadas. Dibujemos X
con marcas TickerMarkView
de tiempo . marca Sami TickerMarkView
son muy simples View
pila vertical VStack
en la que están dispuestos Path
y Text
:
El conjunto de marcas en el eje de tiempo para un cierto "gráficos conjunto" chart : LineSet
formados TickerView
de acuerdo con el intervalo de tiempo seleccionado por el usuario rangeTime
y la cantidad aproximada de las marcas estimatedMarksNumber
, que deben estar en el campo de visión del usuario:
Para disposición Marcas de tiempo "en ejecución" usamos una ScrollView
pila horizontalHStack
, que cambiará a medida que cambie el rango de tiempo rangeTime
.En TickerView
, formamos un paso step
con el que aparecen las marcas de tiempo TimeMarkView
, en función de un rango de tiempo rangeTime
y un ancho de pantalla determinados widthRange
...
... y luego seleccionamos marcas de tiempo en incrementos step
de la matriz chart.xTime
mediante índices indexes
.En realidad X
- línea horizontal - que impone overlay
...
... en la pila horizontal HStack
, con marcas de tiempo TimeMarkView
, que estamos impulsando con la ayuda offset
:
Además, podemos establecer el color en sí mismo X
- colorXAxis
y marcas - colorXMark
:
YTickerView
- Y
Con marcas y una cuadrícula.
Este View
dibuja Y
con marcas digitales YMarkView
. propios marcadores YMarkView
son muy simples View
pila vertical VStack
en que están dispuestos Path
(la línea horizontal) y Text
con un número:
El conjunto de marcas en Y
para un cierto "conjunto de gráficos" chart
se forma YTickerView
. El rango de valores se rangeY
calcula como la unión de los rangos de valores de todos los "Gráficos" incluidos en este "conjunto de Gráficos" utilizando la función rangeOfRanges
. Número aproximado de marcas en el eje Y se define mediante parámetro estimatedMarksNumber
:
En YTickerView
supervisamos el rango de cambio de los valores "gráficos" rangeY
. En realidad, el eje Y, la línea vertical, imponemos overlay
a nuestras marcas ...
Además, podemos establecer los colores del propio eje Y colorYAxis
y las marcas colorYMark
:
RangeView
- establecer el rango de tiempo usando el "mini-mapa".
La parte más conmovedora de nuestra interfaz de usuario es establecer el rango de tiempo ( lowerBound
, upperBound
) para mostrar el "conjunto de Gráficos":
RangeView
- es como mini - map
resaltar una sección de tiempo específica con el propósito de considerar más detalladamente el "conjunto de Gráficos" en otros Views
.Como en los anteriores View
, los datos iniciales RangeView
son:
- el "conjunto de gráficos" en sí
chart: LineSet
(valores Y
), - de altura
height
"mini-map"
RangeView
, - anchura
widthRange
"mini-map"
RangeView
, - Guión
indent
"mini-map"
RangeView
.
A diferencia de los otros discutidos anteriormente Views
, debemos cambiar el DragGesture
rango de tiempo ( lowerBound
, upperBound
) con un gesto e inmediatamente ver su cambio, por lo que el rango de tiempo definido por el usuario ( lowerBound
, upperBound
) con el que trabajaremos se almacena en una variable variable @EnvironmentObject var userData: UserData
:
cualquier cambio en la variable var userData
conducirá a redibujando a todos los Views
dependientes de él.El personaje principal en RangeView
es una "ventana" transparente, cuya posición y tamaño son controlados por el usuario con un gesto DragGesture
:1. si usamos el gesto dentro de una "ventana" transparente, la POSICIÓN de la "ventana" cambia X
, y su tamaño no cambia:
2. si usamos un gesto en la parte oscura a la izquierda, solo cambia la FRONTERA IZQUIERDA de la "ventana" lowerBound
, lo que nos permite disminuir o aumentar el ancho de la "ventana" transparente:
3. si usamos un gesto en la parte oscura a la derecha, solo cambia la FRONTERA DERECHA de la "ventana" upperBound
, lo que le permite disminuir o aumentar el ancho de la "ventana" transparente:
RangeView
consta de 3 elementos básicos muy simples: dos rectángulos Rectangle ()
y una imagen Image
, cuyos bordes están determinados por las propiedades lowerBound
y upperBound
desde @EnvironmentObject var userData: UserData
y se ajustan mediante gestos DragGesture
:
"superponemos" ( overlay
) lo familiar a esta construcción ( ) nosotros GraphsForChartView
con "Gráficos" de un "conjunto de Gráficos" dado chart
:
Esto nos permitirá controlar la cantidad de "Gráficos" que ingresa a la "ventana".Cualquier cambio en la "ventana" transparente (que se mueve por completo o cambio de fronteras) es una consecuencia de los cambios en las propiedades lowerBound
y upperBound
en userData en las funciones de onChanged
procesamiento de señal DragGesture
en las dos cajas Rectangle ()
y foto Image
...
Se trata, como ya sabemos, dará lugar automáticamente a volver a dibujar el otro Views
(en este caso, “Gráficos”, eje X con marcas, eje Y con marcas e indicador c hartView
):
dado que el nuestro View
contiene una variable @EnvironmentObject userData: UserData
, para las vistas previas Previews
, debemos establecer su valor inicial usando .environmentObject (UserData())
:
CheckMarksView
- "escondiéndose" y mostrando "Gráficos".
CheckMarksView
es una pila horizontal HStack
con una fila checkBoxes
para cambiar las propiedades de isHidden
cada "Gráficos" individuales en el "conjunto de Gráficos" chart
:
CheckBox
en nuestro proyecto se puede implementar usando un botón normal Button
y llamado CheckButton
, o usando un botón de simulación SimulatedButton
.
El botón Button
tuvo que ser imitado porque al colocar varios de estos botones en List
uno ubicado más arriba en la jerarquía, se "niegan" a funcionar correctamente. Este es un error de larga data que se ha atascado en Xcode 11 desde la versión beta 1 hasta la versión actual . La versión actual de la aplicación utiliza un botón simulado SimulatedButton
.Tanto el botón simulado SimulatedButton
como el botón realCheckButton
usar lo mismo View
para su "apariencia" - CheckBoxView
. Esto HStack
contiene Tex
y Image
:
Tenga en cuenta que el parámetro de inicialización CheckBoxView
es una @Binding
variable var line: Line
. La propiedad de isHidden
esta variable define la "apariencia" CheckBoView
:
al usar CheckBoView
in SimulatedButton
y in, CheckButton
debe usar el signo $
para line
durante la inicialización:
la propiedad de la isHidden
variable se line
cambia SimulatedButton
con onTapGesture
...
... y en CheckButton
- con el action
botón habitual Button
: tenga en
cuenta que el parámetro de inicialización para SimulatedButton
y CheckButton
también es@Binding
variables var line: Line
. Por lo tanto, su uso debe ser aplicado $
a la CheckMarksView
variable de conmutación userData.charts[self.chartIndex].lines[self.lineIndex(line: line)].isHidden
, que se almacena en una variable global variables @EnvironmentObject var userData
:
Hemos mantenido sin utilizar en el proyecto se encuentra actualmente CheckButton
en el caso, si de repente se Apple
va a corregir este error. Además, puede intentar usar CheckButton
in en su CheckMarksView
lugar SimulatedButton
y asegurarse de que no funciona para el caso de componer una multitud de "conjuntos de gráficos" ChartView
utilizando List
c ListChartsView
.Como el nuestro View
contiene una variable @EnvironmentObject var userData: UserData
, para las vistas previas Previews
, debemos establecer su valor inicial con .environmentObject(UserData())
:
Combinación de varios Views
.
SwiftUI
- esto es principalmente una combinación de varios pequeños Views
en grandes, y grandes Views
en muy grandes, etc., como en un juego Lego
. Como SwiftUI
hay una gran variedad de medios tales combinación Views
:- una pila vertical
VStack
, - pila horizontal
HStack
, - "Profundidad" de la pila
ZStack
, - grupo
Group
, ScrollView
,- lista
List
, - formar
Form
, - contenedor de marcadores
TabView
- etc.
Comenzamos nuestra combinación con la más simple GraphsViewForChart
, que proporciona el "conjunto de gráficos" sin rostro " GraphsForChart
AXIS Y" y un indicador que se mueve a lo largo del eje X usando la pila "profunda" ZStack
:
agregamos un contenedor a Previews
nuestro nuevo GraphsViewForChart
contenedor NavigationView
para mostrarlo en Dark
modo usando un modificador .collorScheme(.dark)
.Continuamos la combinación y adjuntamos al "conjunto de gráficos" obtenido anteriormente con AXIS Y y un indicador, AXIS X en forma de una "línea en ejecución", así como controles: el rango de tiempo del "mini-mapa" RangeView
y los interruptores de CheckMarksView
visualización de "Gráficos".Como resultado, obtenemos el indicado anteriormente ChartView
, que muestra un "conjunto de gráficos" y le permite controlar su visualización en el eje de tiempo:
En este caso, realizamos la combinación usando la pila vertical VStack
:
ahora consideraremos 3 opciones para combinar el conjunto de "Conjuntos de gráficos" ChartView ya recibidos:- "Mesa desplazable"
List
, - pila horizontal
HStack
con efecto 3D, ZStack
"tarjetas" superpuestas
Una "tabla desplazable" seListChartsView
organiza usando una lista List
: una
pila horizontal con un efecto 3D se organiza usando una ScrollView
pila horizontal HStack
y una lista en la forma ForEach
:
en esta vista, todos los medios de interacción del usuario funcionan completamente: moverse a lo largo de la línea de tiempo y cambiar la "escala" mini- map
, el indicador y los botones de ocultar "Gráficos".ZStack
"tarjetas" superpuestas.
Primero creamos CardView
para el "mapa": este es un "conjunto de gráficos" con AXIS X e Y, pero sin controles: sin un "mini mapa" y sin botones para controlar la apariencia / ocultación de los gráficos. CardView
muy similar a ChartView
, pero como vamos a superponer "tarjetas" una encima de la otra, necesitamos que sean opacas. Para este fin, usamos un ZStack
color adicional para colocar en el "fondo" cardBackgroundColor
. Además, vamos a hacer para "mapa" de una caja con bordes redondeados:
superposición del "mapa" organizado por una pila VStack
, ZStack
y la lista en la forma ForEach
:
Pero vamos a imponer a la otra no es sólo una "tarjeta" y "3D-macshtabiruemye" tarjetas CardViewScalable
, cuyo tamaño disminuye al aumentar el índiceindexChat
y se mueven un poco verticalmente.El orden de las "tarjetas escalables en 3D" se puede cambiar usando la secuencia ( sequenced
) de gestos LongPressGesture
y DragGesture
, que actúa solo en la "tarjeta" superior con indexChat == 0
:
Puede hacer clic ( LongPress
) en la "tarjeta" superior con un "conjunto de Gráficos", y luego tirar Drag
hacia abajo ( ) lo suficiente como para mirar la siguiente carta, y si continúa arrastrándola hacia abajo, "va" al último lugar ZStack
y aparece la siguiente "carta":
Además, para la "carta" superior podemos aplicar TapGesture
, lo que actuará junto con gestos LongPressGesture
y un DragGesture
:
Tap
"conjunto de gráficos" un gesto mostrará el modal ChartView
con el correo ementami gestión RangeView
yCheckMarksView
:
Solicitud TabView
para combinar en una pantalla las 3 variantes de la composición "conjunto de gráficos" ChartView
.
Tenemos 3 marcadores con una imagen Image
y texto Text
, VStack
no se necesita una pila vertical para su presentación conjunta.Corresponden a nuestros 3 métodos de combinación de "conjuntos de gráficos" ChartViews
:- "Mesa desplazable"
ListChartViews
, - pila horizontal con efecto 3D
HStackChartViews
, - ZStack superpuso "cartas"
OverlayCardsViews
.
Todos los elementos de interacción del usuario: moverse a lo largo de la línea de tiempo y cambiar la "escala" con la ayuda mini - map
, el indicador y los botones para ocultar los "Gráficos". funciona completamente en los 3 casos.El código está en Github .SwiftUI
...
Debe familiarizarse con los tutoriales en video, los libros y los blogs:Mang To , Permite construir esa aplicación , así como una descripción de algunas aplicaciones SwiftUI ,un libro gratuito "SwiftUI por ejemplo" y un video www.hackingwithswift.com/quick-start/swiftui- un libro pagado pero la mitad se puede descargar de forma gratuita www.bigmountainstudio.com/swiftui-views-book- curso de 100 días con SwiftUI www.hackingwithswift.com/articles/201/start-the-100-days-of-swiftui , que comienza ahora y finalizará el 31 de diciembre de 2019,- cosas impresionantes en SwiftUI se hacen en swiftui-lab.com- Majid blog ,- en pointFree.cowww.pointfree.co el "maratón" de publicaciones sobre el uso de Reductores en SwiftUI (súper interesante)es una maravillosa aplicación MovieSwiftUI que ha tomado algunas ideas.