
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 XTickerView : "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 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 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 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 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 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 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 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: 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 rangeOfRanges: Mostramos
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 " 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 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 DragGesture:
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í, 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" 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 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 stepde la matriz chart.xTimemediante í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- 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 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 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 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(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 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 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" 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:
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" ( 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 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 Views(en este caso, “Gráficos”, eje X con marcas, eje Y con marcas e indicador c hartView):
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 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 CheckBoxViewes una @Bindingvariable var line: Line. La propiedad de isHiddenesta variable define la "apariencia" CheckBoView:
al usar CheckBoViewin SimulatedButtony in, CheckButtondebe usar el signo $para linedurante la inicialización:
la propiedad de la isHiddenvariable se linecambia SimulatedButtoncon onTapGesture...
... y en CheckButton- con el actionbotón habitual Button: tenga en
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 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 simple GraphsViewForChart, 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 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 VStack:
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 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" 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 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 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 ( 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 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 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 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.