
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.
- YTickerViewes el eje- Ycon elevaciones y la cuadrícula horizontal correspondiente.
- IndicatorViewes 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- Xcomo 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 JSONy preséntelos en un conveniente formato "interno"
- Crear UIdeUIpara 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]enterospoints: [Int],
- titulado "Gráficos" title: String,
- tipo "Gráficos" type: String?,
- color : UIColoren 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 índicesRangexTimeRangetiempoxTimeen el eje X,
- rango de valores rangeY: Range"Gráficos" para elrangeY: RangeY,
- grosor de la línea de trazo "Gráficos" lineWidthlí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 rangeYy GraphViewusamos el valor nilpredeterminado, en este caso, el "Gráfico" toma sus valores mínimos y máximos en el intervalo de tiempo rangeTime: aunque usamos un
aunque usamos un Pathmodificador para nuestro modelo animation (.linear(duration: 0.6)), no se producirá animación, por ejemplo, al cambiar el rango de rangeYvalores " Gráficos ". Un "gráfico" simplemente "saltará" de un valor de un rango rangeYa otro sin ninguna animación.La razón es simple: enseñamos SwiftUIcómo dibujar un "Gráfico" para un rango específico rangeY, pero no enseñamos SwiftUIcómo reproducir un "Gráfico" varias veces con valores intermedios del rango rangeYentre el inicio y el final, y para eso enSwiftUIcumple con el protocolo Animatable.Afortunadamente, si el suyo Viewes 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 animatableDatacon 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" GraphViewen Shape. Es muy simple, solo necesitamos implementar la función func path (in rect:CGRect) -> Pathque esencialmente ya tenemos e indicar con la ayuda de la propiedad calculada animatableDataqué datos queremos animar: Tenga en cuenta que el tema del control de animación es un tema avanzado en
Tenga en cuenta que el tema del control de animación es un tema avanzado enSwiftUIy puede obtener más información al respecto en el artículo "Animaciones avanzadas de SwiftUI - Parte 1: Rutas" . Podemos usar la"figura" resultante Graphen una GraphViewNew"Gráfica" mucho más simple con animación: verá que no necesitábamos
verá que no necesitábamos GeometryReadernuestras nuevas "Gráficas" GraphViewNew, porque gracias al protocolo Shapenuestra "figura" Graphpodrá adaptarse a cualquier tamaño del padre View.Naturalmente, Previewsobtuvimos el mismo resultado que en el caso con GraphView: En las siguientes combinaciones, usaremos los
En las siguientes combinaciones, usaremos los GraphViewNewmismos "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" charten un intervalo de tiempo predeterminado rangeTimecon un eje común Y, la anchura de las "líneas" es igual a lineWidth: Al igual que con
Al igual que con GraphViewy GraphViewNewvamos a crear GraphsForChartun 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 enY),
- 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 valoresrangeY: Rangepara 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
Para esto, utilizamos la función rangeOfRanges: Mostramos todos los Gráficos NO ocultos (
todos los Gráficos NO ocultos ( isHidden = false) en ZStackla 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 "
Gracias a este modificador, el proceso de ocultar y devolver los" Gráficos " ChartViewa 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 usoMetalpara dibujar formas gráficas. En nuestros datos de prueba y en el simulador, no sentirá la diferencia en la velocidad de dibujar con Metaly 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 GraphViewNewpruebas GraphsForChartcon vistas previas, Previewspodemos 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
El indicador se crea para un "conjunto de Gráficos" específico charty consiste en moverse a lo largo de la XLÍ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
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
utilizamos la llamada ejecución de gesto "incremental". En lugar de una distancia continua desde el punto de partida value.translation.width, onChangedrecibiremos 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 IndicatorViewcon la ayuda de un Previews"conjunto de Gráficos" dado, chartpodemos atraer la Viewconstrucción de "Gráficos" GraphsForChart: podemos establecer cualquier rango de tiempo, pero coordinado entre sí,
podemos establecer cualquier rango de tiempo, pero coordinado entre sí, rangeTimetanto para el indicador IndicatorViewcomo 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- Xcon marcas
Hasta ahora, nuestros "Gráficos" están despersonalizados en el sentido de que NO X Ytienen las escalas y marcas apropiadas. Dibujemos Xcon marcas TickerMarkViewde tiempo . marca Sami TickerMarkViewson muy simples Viewpila vertical VStacken la que están dispuestos Pathy Text: El conjunto de marcas en el eje de tiempo para un cierto "gráficos conjunto"
El conjunto de marcas en el eje de tiempo para un cierto "gráficos conjunto" chart : LineSetformados TickerViewde acuerdo con el intervalo de tiempo seleccionado por el usuario rangeTimey 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
Para disposición Marcas de tiempo "en ejecución" usamos una ScrollViewpila horizontalHStack, que cambiará a medida que cambie el rango de tiempo rangeTime.En TickerView, formamos un paso stepcon el que aparecen las marcas de tiempo TimeMarkView, en función de un rango de tiempo rangeTimey un ancho de pantalla determinados widthRange... ... y luego seleccionamos marcas de tiempo en incrementos
... y luego seleccionamos marcas de tiempo en incrementos stepde la matriz chart.xTimemediante índices indexes.En realidad X- línea horizontal - que impone overlay... ... en la pila horizontal
... 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
Además, podemos establecer el color en sí mismo X- colorXAxisy marcas - colorXMark:
YTickerView- YCon marcas y una cuadrícula.
Este Viewdibuja Ycon marcas digitales YMarkView. propios marcadores YMarkViewson muy simples Viewpila vertical VStacken que están dispuestos Path(la línea horizontal) y Textcon un número: El conjunto de marcas en
El conjunto de marcas en Ypara un cierto "conjunto de gráficos" chartse forma YTickerView. El rango de valores se rangeYcalcula 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
En YTickerViewsupervisamos el rango de cambio de los valores "gráficos" rangeY. En realidad, el eje Y, la línea vertical, imponemos overlaya nuestras marcas ... Además, podemos establecer los colores del propio eje Y
Además, podemos establecer los colores del propio eje Y colorYAxisy 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 - mapresaltar 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 RangeViewson:
- el "conjunto de gráficos" en sí chart: LineSet(valoresY),
- de altura height"mini-map"RangeView,
- anchura widthRange"mini-map"RangeView,
- Guión indent"mini-map"RangeView.
A diferencia de los otros discutidos anteriormenteViews, debemos cambiar el DragGesturerango 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
cualquier cambio en la variable var userDataconducirá a redibujando a todos los Viewsdependientes de él.El personaje principal en RangeViewes 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"
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"
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:
RangeViewconsta de 3 elementos básicos muy simples: dos rectángulos Rectangle ()y una imagen Image, cuyos bordes están determinados por las propiedades lowerBoundy upperBounddesde @EnvironmentObject var userData: UserDatay se ajustan mediante gestos DragGesture: "superponemos" (
"superponemos" ( overlay) lo familiar a esta construcción ( ) nosotros GraphsForChartViewcon "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
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 lowerBoundy upperBounden userData en las funciones de onChangedprocesamiento de señal DragGestureen las dos cajas Rectangle ()y foto Image... Se trata, como ya sabemos, dará lugar automáticamente a volver a dibujar el otro
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
dado que el nuestro Viewcontiene 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".
CheckMarksViewes una pila horizontal HStackcon una fila checkBoxespara cambiar las propiedades de isHiddencada "Gráficos" individuales en el "conjunto de Gráficos" chart:
CheckBoxen nuestro proyecto se puede implementar usando un botón normal Buttony llamado CheckButton, o usando un botón de simulación SimulatedButton. El botón
El botón Buttontuvo que ser imitado porque al colocar varios de estos botones en Listuno 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 SimulatedButtoncomo el botón realCheckButtonusar lo mismo Viewpara su "apariencia" - CheckBoxView. Esto HStackcontiene Texy Image: Tenga en cuenta que el parámetro de inicialización
Tenga en cuenta que el parámetro de inicialización CheckBoxViewes una @Bindingvariable var line: Line. La propiedad de isHiddenesta variable define la "apariencia" CheckBoView:
 al usar
al usar CheckBoViewin SimulatedButtony in, CheckButtondebe usar el signo $para linedurante la inicialización:
 la propiedad de la
la propiedad de la isHiddenvariable se linecambia SimulatedButtoncon onTapGesture... ... y en
... y en CheckButton- con el actionbotón habitual Button: tenga en cuenta que el parámetro de inicialización para
cuenta que el parámetro de inicialización para SimulatedButtony CheckButtontambién es@Bindingvariables var line: Line. Por lo tanto, su uso debe ser aplicado $a la CheckMarksViewvariable 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
Hemos mantenido sin utilizar en el proyecto se encuentra actualmente CheckButtonen el caso, si de repente se Appleva a corregir este error. Además, puede intentar usar CheckButtonin en su CheckMarksViewlugar SimulatedButtony asegurarse de que no funciona para el caso de componer una multitud de "conjuntos de gráficos" ChartViewutilizando Listc ListChartsView.Como el nuestro Viewcontiene 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 Viewsen grandes, y grandes Viewsen muy grandes, etc., como en un juego Lego. Como SwiftUIhay 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 simpleGraphsViewForChart, que proporciona el "conjunto de gráficos" sin rostro " GraphsForChartAXIS Y" y un indicador que se mueve a lo largo del eje X usando la pila "profunda" ZStack: agregamos un contenedor a
agregamos un contenedor a Previewsnuestro nuevo GraphsViewForChartcontenedor NavigationViewpara mostrarlo en Darkmodo 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" RangeViewy los interruptores de CheckMarksViewvisualizació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
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:
ahora consideraremos 3 opciones para combinar el conjunto de "Conjuntos de gráficos" ChartView ya recibidos:- "Mesa desplazable" List,
- pila horizontal HStackcon 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
pila horizontal con un efecto 3D se organiza usando una ScrollViewpila horizontal HStacky 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"
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 CardViewpara 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. CardViewmuy similar a ChartView, pero como vamos a superponer "tarjetas" una encima de la otra, necesitamos que sean opacas. Para este fin, usamos un ZStackcolor 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
superposición del "mapa" organizado por una pila VStack, ZStacky la lista en la forma ForEach: Pero vamos a imponer a la otra no es sólo una "tarjeta" y "3D-macshtabiruemye" tarjetas
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 índiceindexChaty se mueven un poco verticalmente.El orden de las "tarjetas escalables en 3D" se puede cambiar usando la secuencia ( sequenced) de gestos LongPressGesturey DragGesture, que actúa solo en la "tarjeta" superior con indexChat == 0: Puede hacer clic (
Puede hacer clic ( LongPress) en la "tarjeta" superior con un "conjunto de Gráficos", y luego tirar Draghacia abajo ( ) lo suficiente como para mirar la siguiente carta, y si continúa arrastrándola hacia abajo, "va" al último lugar ZStacky aparece la siguiente "carta": Además, para la "carta" superior podemos aplicar
Además, para la "carta" superior podemos aplicar TapGesture, lo que actuará junto con gestos LongPressGesturey un DragGesture:
Tap"conjunto de gráficos" un gesto mostrará el modal ChartViewcon el correo ementami gestión RangeViewyCheckMarksView :
Solicitud TabViewpara combinar en una pantalla las 3 variantes de la composición "conjunto de gráficos" ChartView.
 Tenemos 3 marcadores con una imagen
Tenemos 3 marcadores con una imagen Imagey texto Text, VStackno 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 ayudamini - 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.