
Começarei com a observação de que o aplicativo discutido neste artigo requer o
Xcode 11 e o
MacOS Catalina, se você quiser usar as
Live Previews
, e o
Mojave
se você usar o simulador. O código do aplicativo está no
Github .
Este ano, na
WWDC 2019 , a
Apple
anunciou o
SwiftUI
, uma nova maneira declarativa de criar uma interface de usuário (UI) em todos os dispositivos
Apple
. É quase uma partida completa do
UIKit
usual, e eu - como muitos outros desenvolvedores - realmente queria ver essa nova ferramenta em ação.
Este artigo apresenta a experiência de resolver com o
SwiftUI
um problema cujo código no
UIKit
incomparavelmente mais complexo e não pode ser
UIKit
na minha opinião de maneira legível.
A tarefa está relacionada ao último concurso
Telegram para desenvolvedores de
Android
,
iOS
e
JS
, realizado de 10 a 24 de março de 2019. Nesta competição, uma tarefa simples foi proposta para exibir graficamente a intensidade de uso de um determinado recurso na Internet, dependendo do tempo com base nos dados
JSON
. Como desenvolvedor do
iOS
, você deve usar o
Swift
para enviar o código escrito do zero para a competição sem usar bibliotecas especializadas externas para plotagem.
Essa tarefa exigia habilidades para trabalhar com os recursos gráficos e de animação do iOS:
Core Graphics ,
Core Animation ,
Metal ,
OpenGL ES . Algumas dessas ferramentas são ferramentas de programação de baixo nível e não orientadas a objetos. Basicamente, no
iOS
não havia modelos aceitáveis para resolver tarefas gráficas aparentemente leves à primeira vista. Portanto, cada competidor inventou seu próprio animador (
Render ) com base em
Metal ,
CALayers ,
OpenGL ,
CADisplayLink . Isso gerou toneladas de código dos quais não foi possível emprestar e desenvolver nada, uma vez que esses são trabalhos puramente "protegidos por direitos autorais" que somente autores podem realmente desenvolver. No entanto, isso não deve ser assim.
E no início de junho na
WWDC 2019 , o
SwifUI
aparece - uma nova
framework
desenvolvida pela
Apple
, escrita em
Swift
e projetada para descrever declarativamente a interface do usuário (
UI
) no código. Você determina quais
subviews
exibidas em sua
View
, quais dados fazem com que essas
subviews
sejam alteradas, quais modificadores você precisa aplicar a eles, para posicioná-los no lugar certo, para ter o tamanho e o estilo certos. Um elemento igualmente importante do
SwiftUI
é o controle do fluxo de dados modificáveis pelo usuário, que por sua vez atualiza a
UI
do
UI
.
Neste artigo, quero mostrar como a própria tarefa do concurso
Telegram no
SwiftUI
é resolvida de maneira rápida e fácil. Além disso, este é um processo muito emocionante.
Tarefa
O aplicativo competitivo deve exibir simultaneamente 5 "conjuntos de gráficos" na tela usando os dados fornecidos pelo
Telegram
. Para um "conjunto de gráficos", a
UI
do
UI
seguinte:

Na parte superior, há uma "zona do gráfico" com uma escala comum ao longo do eixo Y normal, com marcas e linhas de grade horizontais. Abaixo disso, há uma linha rasteira com carimbos de data / hora ao longo do eixo X como datas.
Ainda mais baixo é o chamado “mini mapa” (como no
Xcode 11
), ou seja, uma “janela” transparente que define a parte do período de nossos “Gráficos”, que é apresentada com mais detalhes na “zona Gráficos” superior. Este “mini mapa” não só pode ser movido ao longo do eixo
X
, mas também sua largura pode ser alterada, o que afeta a escala de tempo na “área Gráficos”.
Com a ajuda de
checkboxs
de
checkboxs
pintadas nas cores de "Gráficos" e fornecidas com seus nomes, você pode recusar a exibição dos "Gráficos" correspondentes a essa cor na "zona Gráficos".
Existem muitos desses "conjuntos de gráficos"; no nosso exemplo de teste, existem 5 deles, por exemplo, e todos devem estar localizados em uma tela.
Na
UI
do
UI
projetada usando o
SwiftUI
não há necessidade de um botão para alternar entre os modos
Dark
e
Light
, isso já está incorporado no
SwiftUI
. Além disso, o
SwiftUI
muito mais opções para combinar "conjuntos de gráficos" (ou seja, os conjuntos de telas apresentados acima) do que simplesmente rolar uma tabela para baixo, e examinaremos algumas dessas opções muito interessantes.
Mas primeiro, vamos nos concentrar em exibir um "conjunto de
SwiftUI
" para o qual criaremos um
ChartView
:

SwiftUI
permite criar e testar uma
UI
do
UI
complexa em pedaços pequenos e, em seguida, é muito fácil montar essas peças em um quebra-cabeça. Nós faremos isso. Nosso
ChartView
muito bem em pequenos pedaços:
GraphsForChart
- estes são os próprios gráficos, criados para um "conjunto de gráficos" específico. "Gráficos" são mostrados para o intervalo de tempo controlado pelo usuário usando o RangeView
"mini mapa", que será apresentado abaixo.YTickerView
é o eixo Y
com elevações e a grade horizontal correspondente.IndicatorView
é um IndicatorView
horizontal orientado ao usuário que permite visualizar os valores de "Gráficos" e o tempo da posição correspondente do indicador no eixo do tempo no eixo X
TickerView
- "linha rasteira" mostrando carimbos de data e hora no eixo X
como datas,RangeView
- uma "janela" temporária, personalizável pelo usuário usando gestos, para definir o intervalo de tempo para "Gráficos",CheckMarksView
- contém "botões" coloridos nas cores de "Gráficos" e permite controlar a presença de " ChartView
" no ChartView
.
ChartView
usuário pode interagir com o
ChartView
de três maneiras:
1. controle o “mini mapa” usando o gesto
DragGesture
- ele pode mudar a “janela” temporária para a direita e esquerda e diminuir / aumentar seu tamanho:

2. mova o indicador na direção horizontal, mostrando os valores dos "Gráficos" em um ponto fixo no tempo:

3. oculte / mostre certos "Gráficos" usando botões coloridos nas cores "Gráficos" e localizados na parte inferior do
ChartView
:

Podemos combinar vários "Conjuntos de gráficos" (temos cinco deles em dados de teste) de diferentes maneiras, por exemplo, colocando-os todos simultaneamente em uma tela usando a
List
Lista (como uma tabela rolável para cima e para baixo):

ou usando o
ScrollView
e uma pilha horizontal do
HStack
com um efeito 3D:

... ou na forma de um
ZStack
"cartões" sobrepostos, cuja ordem pode ser alterada: o "cartão" superior com "" um conjunto de gráficos "pode ser puxado para baixo o suficiente para olhar para o próximo cartão e, se você continuar a arrastá-lo para baixo, então" vai "para o último lugar no
ZStack
, e este próximo" cartão "" segue em frente ":

Nessas
UI
complexas - uma “mesa rolável”, uma pilha horizontal com efeito
3D
, um
ZStack
“cartões” sobrepostos uns aos outros - todos os meios de interação do usuário funcionam completamente: movendo-se na linha do tempo e alterando a “escala” dos
ZStack
, indicador e ocultar "Gráficos".
Além disso, consideraremos em detalhes o design dessa
UI
do
UI
usando o
SwiftUI
- de elementos simples a composições mais complexas. Mas primeiro, vamos entender a estrutura de dados que temos.
Portanto, a solução para o nosso problema foi dividida em várias etapas:
- Faça o download dos dados de um arquivo
JSON
e apresente-os em um formato "interno" conveniente - Criar
UI
do UI
para um "conjunto de gráficos" - Combine vários "conjuntos de gráficos"
Baixar dados
À nossa disposição, o
Telegram forneceu dados
JSON contendo vários "conjuntos de gráficos". Cada "conjunto de
chart
" individual de um
chart
contém vários "Gráficos" (ou "Linhas") de
chart.columns
. Cada "Gráfico" ("Linhas") tem uma marca na posição
0
-
"x"
,
"y0"
,
"y1"
,
"y2"
,
"y3"
, seguida pelos valores de tempo no eixo X ("x") ou os valores de "Gráficos" ("Linhas") (
"y0"
,
"y1"
,
"y2"
,
"y3"
) no eixo
Y
:

A presença de todas as "Linhas" no "conjunto de gráficos" é opcional. Os valores para a "coluna" x são registros de data e hora do UNIX em milissegundos.
Além disso, cada "conjunto de
chart
" individual do
chart
é fornecido com cores
chart.colors
no formato de 6 dígitos hexadecimais (por exemplo, "#AAAAAA") e
chart.names
.
Para criar o Modelo de Dados localizado no arquivo
JSON
, usei o excelente serviço
quicktype . Neste site, você insere um pedaço de texto de um arquivo
JSON
e especifica a linguagem de programação (
Swift
), o nome da estrutura (
Chart
), que será formada após a "análise" desses dados
JSON
e é isso.
Um código é gerado na parte central da tela, que
Chart.swift
em nosso aplicativo em um arquivo separado chamado
Chart.swift
. É aqui que colocaremos o Modelo de Dados no formato JSON. Usando o carregador de dados do arquivo
JSON
para o modelo emprestado das
demos SwiftUI Generic
, obtive uma matriz de
columns: [ChartElement]
, que é uma coleção de "conjuntos de
columns: [ChartElement]
" no formato de
Telegram
.
A
ChartElement
dados
ChartElement
, contendo matrizes de elementos heterogêneos, não é muito adequada para trabalhos interativos intensivos com gráficos; além disso, os carimbos de data e hora são apresentados no formato
UNIX
em milissegundos (por exemplo,
1542412800000, 1542499200000, 1542585600000, 1542672000000
) e cores no formato hexadecimal 6 dígitos (por exemplo,
"#AAAAAA"
).
Portanto, dentro de nossa aplicação, usaremos os mesmos dados, mas em um formato "interno" e bastante simples diferente
[LinesSet]
. A matriz
[LinesSet]
é uma coleção de "Conjuntos de
LinesSet
" de
LinesSet
, cada um dos quais contém
xTime
data
xTime
hora
xTime
no formato
"Feb 12, 2019"
(eixo
X
) e várias
lines
"Gráficos" (eixo
Y
):

Os dados para cada gráfico de linhas (linha) são apresentados
- uma matriz de
points: [Int]
inteiros points: [Int]
, - chamado
title: String
"Gráficos" title: String
, - tipo "Graphics"
type: String?
, color : UIColor
no formato UIColor
do Swift
,- número de pontos
countY: Int
.
Além disso, qualquer "Gráfico" pode ser oculto ou exibido, dependendo do valor de
isHidden: Bool
. Os
upperBound
e
upperBound
ajustar o intervalo de tempo levam valores de
0
a
1
e mostram não apenas o tamanho da janela de tempo do “mini mapa” (
upperBound
-
lowerBound
), mas também sua localização no eixo temporal
X
:

As estruturas de dados
JSON
[ChartElement]
e as estruturas de dados das
LinesSet
e
Line
"internas" estão no arquivo
Chart.swift . O código para carregar dados
JSON
e convertê-los em uma estrutura interna está localizado no arquivo
Data.swift . Detalhes sobre essas transformações podem ser encontrados
aqui .
Como resultado, recebemos dados sobre os "conjuntos de gráficos" no formato interno como uma matriz de
chartsData
.

Este é o nosso
Dados, mas, para trabalhar no
SwiftUI
, é necessário garantir que quaisquer alterações feitas pelo usuário na matriz
chartsData
(alterando a "janela" temporária, ocultando / mostrando "Gráficos") levem a atualizações automáticas de nossos
chartsData
exibição.
@EnvironmentObject
. Isso nos permitirá usar o
Dados sempre que necessário e, além disso, atualizaremos automaticamente nossas
Views
se os dados forem alterados. Isso é algo como
Singleton
ou dados globais.
@EnvironmentObject
exige que
@EnvironmentObject
uma
final class UserData
, localizada no arquivo
UserData.swift , armazene os dados
chartsData
e implemente o protocolo
ObservableObject
:

A presença de "wrappers" do
@Published
permitirá que você publique "notícias" que essas propriedades dos
charts
da classe
UserData
foram alteradas, para que quaisquer
Views
"subscritas a esta notícia" no
SwiftUI
possam selecionar automaticamente novos dados e atualizar.
Lembre-se de que na propriedade de
charts
os valores
isHidden
de qualquer "
isHidden
" podem mudar (eles permitem ocultar ou mostrar esses "Gráficos"), bem como os limites de tempo inferior inferior e superior superior para cada "conjunto de gráficos" individual.
Queremos usar a propriedade de
charts
da classe
UserData
todo o aplicativo e não precisamos sincronizá-los com a
UI
do
UI
manualmente, graças a
@EnvironmentObject
.
Para fazer isso, ao iniciar o aplicativo, precisamos criar uma instância da classe
UserData ()
para que posteriormente possamos acessá-lo em qualquer lugar do aplicativo. Faremos isso no arquivo
SceneDelegate.swift
dentro do método
scene (_ : , willConnectTo: , options: )
. É aqui que nosso
ContentView
é criado e lançado, e é aqui que devemos passar o
@EnvironmentObject
ContentView
qualquer
@EnvironmentObject
que
@EnvironmentObject
para que o
SwiftUI
possa disponibilizá-los para qualquer outra
View
:

Agora, em qualquer
View
para acessar os dados
@Published
da classe
UserData
, precisamos criar a variável
var
usando o wrapper
@EnvironmentObject
. Por exemplo, ao definir o intervalo de tempo no
RangeView
criamos a variável
var userData
com o
UserData
TYPE:

Portanto, assim que implementamos algum
@EnvironmentObject
no "ambiente" do aplicativo, podemos começar a usá-lo imediatamente no nível mais alto ou no 10º nível abaixo - isso não importa. Mais importante, porém, sempre que uma
View
muda o "ambiente", todas as
Views
que possuem esse
@EnvironmentObject
serão
@EnvironmentObject
automaticamente, garantindo a sincronização com os dados.
Vamos seguir para o design da interface do usuário (
UI
).
Interface do usuário (UI) para um "conjunto de gráficos"
SwiftUI
oferece uma tecnologia composta para criar
SwiftUI
de
SwiftUI
partir de muitas pequenas
Views
, e já vimos que nosso aplicativo se encaixa muito bem com essa tecnologia, pois se divide em pequenas partes: o conjunto "
ChartView
Charts", "
GraphsForChart
Charts",
Y
YTickerView
-
YTickerView
, indicador acionado pelo usuário dos valores "Graphs"
IndicatorView
, linha "
TickerView
" em execução com
TickerView
data e hora no eixo
X
,
RangeView
"janela de tempo" acionado pelo usuário, marcas para ocultar / mostrar o
CheckMarksView
"Graphs". Não podemos apenas criar todas essas
Views
independentemente uma da outra, mas também testar imediatamente no
Xcode 11
usando
Previews
(
Previews
preliminares "ao vivo") nos dados de teste. Você ficará surpreso com a simplicidade do código para criá-los a partir de outras
Views
mais básicas.
GraphView
- "Gráfico" ("Linha")
A primeira
View
, com a qual começaremos, é na verdade o próprio “Gráfico” (ou “Linha”). Vamos chamá-lo de
GraphView
:

A criação de um
GraphView
, como sempre, começa com a criação de um novo arquivo no
Xcode 11
usando o menu
File
→
New
→
File
:

Em seguida, selecionamos o TIPO desejado do arquivo - este é o arquivo
SwiftUI
:

... atribua o nome "GraphView" à nossa
View
e indique sua localização:

Clique no botão
"Create"
e obtenha uma
View
padrão com
Text ( "Hello World!")
No meio da tela:

Nossa tarefa é substituir o
Text ("Hello World!")
do
Text ("Hello World!")
"Graph", mas primeiro vamos ver quais dados iniciais temos para criar o "Graph":
- nós temos os valores de
line.points
"Graphics" line: Line
, - intervalo de tempo
rangeTime
, que é um intervalo de índices Range
carimbos Range
hora xTime
no eixo X, - faixa de valores
rangeY: Range
“Graphics” para o rangeY: Range
Y, - espessura da linha da linha de traçado "Gráficos"
Adicione essas propriedades à estrutura
GraphView
:

Se quisermos usar para nossas
Previews
"Gráficos" (visualizações), que são possíveis apenas para o
MacOS Catalyna
, devemos iniciar um
GraphView
com o intervalo de índices
rangeTime
e os dados de
line
dos próprios "Gráficos":

Já temos os dados de teste
chartsData
que obtivemos do arquivo
JSON
chart.json
e usamos para
Previews
-
Previews
.
No nosso caso, este será o primeiro "conjunto de
chartsData[0]
"
chartsData[0]
e o primeiro "Gráfico" neste conjunto de
chartsData[0].lines[0]
, que forneceremos o
GraphView
como o parâmetro de
line
.
Usaremos todo o intervalo de índices
0..<(chartsData[0].xTime.count - 1)
como intervalo de tempo
rangeTime
.
Os
lineWidth
e
lineWidth
podem ser definidos externamente ou não, pois já possuem valores iniciais:
rangeY
é
nil
e
lineWidth
é
1
.
Intencionalmente,
rangeY
um TYPE da propriedade
rangeY
Propriedade
Optional
TYPE, porque se
rangeY
não
rangeY
definido externamente e
rangeY = nil
, calcularemos os valores mínimo
minY
e máximo
maxY
dos "Gráficos" diretamente a partir da
line.points
.

Esse código é compilado, mas ainda temos uma
View
padrão na tela com o texto
Text ("Hello World!")
No meio da tela:

Porque no
body
temos que substituir o texto
Text ("Hello World!")
Path
, que criará nosso "Graph: points:
line.points
usando o
addLines(_:)
(quase como no
Core Graphics
)


lineWidth
stroke (...)
nosso
Path
linha cuja espessura é
lineWidth
, e a cor da linha do traçado corresponderá à cor padrão (ou seja, preto):

Podemos substituir a cor preta da linha de traçado pela cor especificada em nosso
line.color
"Linha" específico "Cor":

Para que nosso "Gráfico" seja colocado em retângulos de qualquer tamanho, usamos o contêiner
GeometryReader
. Na documentação da
Apple
GeometryReader
é uma
View
"contêiner" que define seu conteúdo como uma função de seu próprio tamanho de
size
e espaço de coordenadas. Essencialmente,
GeometryReader
é outra
View
! Porque quase tudo no
SwiftUI
é
View
!
GeometryReader
permitirá que você, diferentemente de outras
Views
acesse algumas informações adicionais úteis que você pode usar ao criar sua
View
personalizada.
Usamos os contêineres
GeometryReader
e
Path
para criar o
GraphView
adaptável a qualquer tamanho. E se olharmos atentamente para o nosso código, veremos no fechamento do
GeometryReader
variável chamada
geometry
:

Essa variável possui o
GeometryProxy
TYPE, que por sua vez é uma estrutura
struct
com muitas "surpresas":
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 }
A partir da definição de
GeometryProxy
, vemos que existem duas variáveis computadas
var size
e
var safeAreaInsets
, um
frame( in:)
função
frame( in:)
e um
subscript getter
. Precisamos apenas da variável de
size
para determinar a largura da largura
geometry.size.width
e altura da
geometry.size.height
área de desenho “Graphics”.
Além disso, permitimos que nosso “Gráfico” seja animado usando o modificador de
animation (.linear(duration: 0.6))
.

GraphView_Previews
nos permite testar quaisquer "Gráficos" de qualquer "conjunto" de maneira muito simples. Abaixo está o "Gráfico" do "conjunto de gráficos" com o índice 4:
chartsData[4]
e o índice 0 "Gráficos" neste conjunto:
chartsData[4].lines[0]
:

Definimos a
height
“Gráficos” para 400 usando o
frame (height: 400)
, a largura permanece a mesma que a largura da tela. Se não usamos o
frame (height: 400)
, o "Gráfico" ocuparia a tela inteira.
Não especificamos um intervalo de valores rangeY
e GraphView
usamos o valor nil
padrão. Nesse caso, o "Gráfico" leva seus valores mínimo e máximo no intervalo de tempo rangeTime
:
embora tenhamos usado um Path
modificador para o nosso modelo animation (.linear(duration: 0.6))
, nenhuma animação ocorrerá, por exemplo, ao alterar o intervalo de rangeY
valores " Gráficos ". Um "gráfico" simplesmente "salta" de um valor de um intervalo rangeY
para outro sem nenhuma animação.O motivo é simples: ensinamos SwiftUI
como desenhar um “Gráfico” para um intervalo específico rangeY
, mas não ensinamos SwiftUI
como reproduzir um “Gráfico” várias vezes com valores intermediários do intervalo rangeY
entre o início e o fim, e para isso emSwiftUI
atende ao protocolo Animatable
.Felizmente, se a sua View
é uma "figura", isto é View
, que implementa um protocolo Shape
, então um protocolo já foi implementado para ele Animatable
. Isso significa que existe uma propriedade computada animatableData
com a qual podemos controlar o processo de animação, mas, por padrão, é definida como EmptyAnimatableData
, ou seja, nenhuma animação ocorre.Para resolver o problema da animação, primeiro precisamos transformar nosso "Gráfico" GraphView
em Shape
. É muito simples, precisamos apenas implementar a função func path (in rect:CGRect) -> Path
que já possuímos e indicar com a ajuda da propriedade computada animatableData
quais dados queremos animar:
Observe que o tema do controle de animação é um tópico avançado emSwiftUI
e você pode aprender mais sobre isso no artigo “Animações avançadas do SwiftUI - Parte 1: Caminhos” . Podemos usar a"figura" resultante Graph
em um GraphViewNew
"Gráficos" muito mais simples com animação:
você vê que não precisamos GeometryReader
dos nossos novos "Gráficos" GraphViewNew
, porque, graças ao protocolo, Shape
nossa "figura" Graph
poderá se adaptar a qualquer tamanho do pai View
.Naturalmente, Previews
obtivemos o mesmo resultado que no caso de GraphView
:
Nas seguintes combinações, usaremos os GraphViewNew
mesmos "Gráficos" para exibir os valores.GraphsForChart
- conjunto de "gráficos" ("linhas")
O objetivo deste View
- Exibe todos "Gráficos" ( "linhas") de "ajustou gráficos" chart
em um intervalo de tempo pré-determinado rangeTime
com um eixo comum Y
, a largura das "linhas" é igual a lineWidth
:
Tal como acontece com GraphView
e GraphViewNew
vamos criar GraphsForChart
um novo arquivo GraphsForChart.swift
, e definir os dados de entrada para "Conjunto de gráficos":- o próprio "conjunto de gráficos"
chart: LineSet
(valores ativados Y
), - intervalo
rangeTime: Range
( X
) de índices dos carimbos de hora de "Gráficos", - espessura do traçado da linha do gráfico
lineWidth
O intervalo de valores rangeY: Range
para o "conjunto de gráficos" ( Y
) é calculado como a união dos intervalos do indivíduo não oculto ( isHidden = false
) "Gráficos" incluídos neste "conjunto":
Para isso, usamos a função rangeOfRanges
: Mostramos
todos os gráficos NÃO ocultos ( isHidden = false
) na ZStack
construção ForEach
, dando a cada “gráfico” a possibilidade de aparecer na tela e sair da tela “usando o modificador“ mover ” transition(.move(edge: .top))
:
graças a esse modificador, o processo de ocultar e retornar o“ gráfico ” ChartView
ocorrerá na tela com animação e deixará claro para o usuário por que a escala mudou Y
.Use drawingGroup()
significa useMetal
para desenhar formas gráficas. Em nossos dados de teste e no simulador, você não sentirá a diferença na velocidade do desenho com Metal
e Metal
, mas se você reproduzir muitos gráficos bastante volumosos em qualquer um iPhone
deles, notará essa diferença. Para uma introdução mais detalhada, quando usá-lo drawingGroup()
, você pode ver o artigo "Animações avançadas do SwiftUI - Parte 1: Caminhos" ou assistir à sessão de vídeo 237 WWDC 2019 ( Criando visualizações personalizadas com o SwiftUI ).Como no caso de GraphViewNew
testes GraphsForChart
usando visualizações, Previews
podemos defina qualquer "conjunto de gráficos", por exemplo, com um índice 0
:
IndicatorView
- indicador "Gráficos" movido horizontalmente.
Este indicador permite obter os valores exatos de "Gráficos" e o tempo para o ponto correspondente no momento X
:
O indicador é criado para um "conjunto de gráficos" específico chart
e consiste em mover-se ao longo da X
LINHA vertical com MARCAS na forma de "círculos" no lugar dos valores de "Gráficos". Um pequeno "POSTER" é anexado ao topo desta linha vertical, contendo os valores numéricos dos "Gráficos" e da hora.
O indicador desliza pelo usuário usando um gesto DragGesture
:
Usamos a chamada execução de gesto “incremental”. Em vez de uma distância contínua do ponto de partida value.translation.width
, onChanged
receberemos constantemente a distância do local em que estávamos na última vez em que executamos o gesto no manipulador :value.translation.width - self.prevTranslation
. Isso nos fornecerá um movimento suave do indicador.Para testar o indicador IndicatorView
com a ajuda de um Previews
determinado "conjunto de gráficos", chart
podemos atrair a View
construção pronta de "gráficos" GraphsForChart
:
podemos definir qualquer intervalo de tempo, mas coordenado entre si, para rangeTime
o indicador IndicatorView
e "gráficos" GraphsForChart
. Isso nos permitirá garantir que os "círculos" que indicam os valores dos "Gráficos" estejam nos lugares certos.TickerView
- X
com marcas.
Até o momento, nossos “Gráficos” são despersonalizados no sentido de que NÃO X Y
possuem as escalas e marcas apropriadas. Vamos desenhar X
com timestamps TickerMarkView
nele. marca Sami TickerMarkView
são muito simples View
pilha vertical VStack
em que estão dispostas Path
e Text
:
O conjunto de marcas sobre o eixo de tempo para um certo "conjunto de gráficos" chart : LineSet
formados TickerView
de acordo com o intervalo de tempo seleccionado pelo utilizador rangeTime
e a quantidade aproximada de marcas estimatedMarksNumber
, os quais devem estar no campo de visão do utilizador:
Para arranjo Carimbos de data e hora em execução, usamos uma ScrollView
pilha horizontalHStack
, que mudará conforme o intervalo de tempo for alterado rangeTime
.Como TickerView
se formar um degrau step
, que aparecem selo de tempo TimeMarkView
com base num intervalo de tempo predeterminado rangeTime
e a largura da tela widthRange
...
... e, em seguida, seleccionar o tempo do passo c selo step
da matriz chart.xTime
por meio do índice indexes
.Na verdade X
- uma linha horizontal - vamos colocar overlay
...
... em uma pilha horizontal HStack
, com registros de data TimeMarkView
e hora , com os quais avançamos offset
:
Além disso, podemos definir as cores do X
- em si colorXAxis
e as marcas - colorXMark
:
YTickerView
- Y
com marcas e uma grade.
Este View
desenha Y
com marcas digitais YMarkView
. As próprias marcas YMarkView
são muito simples, View
com uma pilha vertical VStack
na qual são colocadas Path
(linha horizontal) e Text
com um número:
Um conjunto de marcas ativadas Y
para um determinado "conjunto de tabelas" chart
é formado YTickerView
. O intervalo de valores rangeY
é calculada como a união de todas as gamas de valores "Graphics", incluído no "conjunto de gráficos" com a função rangeOfRanges
. O número aproximado de marcas no eixo Y é definido pelo parâmetro estimatedMarksNumber
:
 YTickerView
monitoramos a alteração no intervalo dos valores de "Gráficos" rangeY
. Na verdade, o eixo Y - a linha vertical - impomos overlay
às nossas marcas ...
Além disso, podemos definir as cores do próprio eixo Y colorYAxis
e as marcas - colorYMark
:
RangeView
- definir o intervalo de tempo usando o "mini-mapa".
A parte mais comovente de nossa interface do usuário é definir o intervalo de tempo ( lowerBound
, upperBound
) para exibir o "conjunto de gráficos":
RangeView
- é meio que mini - map
destacar uma seção de tempo específica para fins de uma consideração mais detalhada do "conjunto de gráficos" em outros Views
.Como nos anteriores View
, os dados iniciais para RangeView
são:
- o próprio "conjunto de gráficos"
chart: LineSet
(valores Y
), - altura
height
"mini-map"
RangeView
, - largura
widthRange
"mini-map"
RangeView
, - recuo
indent
"mini-map"
RangeView
.
Diferentemente dos outros discutidos acima Views
, devemos alterar o DragGesture
intervalo de tempo ( lowerBound
, upperBound
) com um gesto e ver imediatamente sua alteração, para que o intervalo de tempo definido pelo usuário ( lowerBound
, upperBound
) com o qual trabalharemos seja armazenado em uma variável variável @EnvironmentObject var userData: UserData
:
qualquer alteração na variável var userData
levará a redesenhar todos Views
dependentes dele.O personagem principal RangeView
é uma “janela” transparente, cuja posição e tamanho são regulados pelo usuário com um gesto DragGesture
:1. se usarmos o gesto dentro de uma “janela” transparente, a POSIÇÃO da “janela” X
muda e seu tamanho não muda:
2. se usarmos um gesto na parte escura da esquerda, somente a FRONDA ESQUERDA da “janela” muda lowerBound
, permitindo diminuir ou aumentar a largura da “janela” transparente:
3. se usarmos um gesto na parte escura da direita, somente a FRONDA DIREITA da “janela” muda upperBound
, permitindo diminuir ou aumentar a largura da “janela” transparente:
RangeView
consiste em 3 elementos básicos muito simples: dois retângulos Rectangle ()
e uma imagem Image
, cujas bordas são determinadas pelas propriedades lowerBound
e a upperBound
partir de @EnvironmentObject var userData: UserData
e são ajustadas usando gestos DragGesture
:
“Sobrepomos” ( overlay
) o familiar desta construção ( ) -nos GraphsForChartView
para "gráfico" de um determinado "conjunto de gráficos" chart
:
Isso nos permitirá monitorar quanto dos "gráficos" entra na "janela".Qualquer mudança na "janela" transparente (ele é movido inteiramente ou mudança de fronteiras) é uma conseqüência de mudanças nas propriedades lowerBound
e upperBound
em userData nas funções de onChanged
processamento de sinal DragGesture
nas duas caixas Rectangle ()
e imagem Image
...
Isto é, como já sabemos, levará automaticamente para redesenhar o outro Views
(neste caso, “Gráficos”, eixo X com marcas, eixo Y com marcas e indicador c hartView
):
Como o nosso View
contém uma variável @EnvironmentObject userData: UserData
, para visualizações Previews
, devemos definir seu valor inicial usando .environmentObject (UserData())
:
CheckMarksView
- "ocultando" e mostrando "gráficos".
CheckMarksView
é uma pilha horizontal HStack
com uma linha checkBoxes
para alternar as propriedades de isHidden
cada "Gráficos" individuais no "conjunto de gráficos" chart
:
CheckBox
em nosso projeto, ele pode ser implementado usando um botão regular Button
e chamado CheckButton
, ou usando um botão de simulação SimulatedButton
.
O botão Button
teve que ser imitado porque, ao colocar vários desses botões no List
localizado mais alto na hierarquia, eles "recusam" a funcionar corretamente. Este é um bug de longa data que ficou preso no Xcode 11 desde o beta 1 até a versão atual . A versão atual do aplicativo usa um botão simulado SimulatedButton
.O botão simulado SimulatedButton
e o botão realCheckButton
use a mesma coisa View
para a sua "aparência" - CheckBoxView
. Isso HStack
contém Tex
e Image
:
Observe que o parâmetro de inicialização CheckBoxView
é uma @Binding
variável var line: Line
. A propriedade isHidden
desta variável define a "aparência" CheckBoView
:
Ao usar CheckBoView
in SimulatedButton
e in, CheckButton
você deve usar o sinal $
para line
durante a inicialização:
A propriedade da isHidden
variável é line
ativada SimulatedButton
com onTapGesture
...
... e in CheckButton
- com o action
botão usual Button
:
Observe que o parâmetro de inicialização para SimulatedButton
e CheckButton
também é@Binding
variável var line: Line
. Portanto, seu uso deve ser aplicada $
à CheckMarksView
variável de comutação userData.charts[self.chartIndex].lines[self.lineIndex(line: line)].isHidden
, que é armazenado em uma variável global variável @EnvironmentObject var userData
:
Mantivemos não utilizado no projeto é actualmente CheckButton
o caso, se de repente você Apple
vai corrigir este erro. Além disso, você pode tentar usar CheckButton
em CheckMarksView
vez SimulatedButton
e certifique-se de que ele não funciona no caso da composição do conjunto, "um conjunto de gráficos ChartView
usando List
no ListChartsView
.Como o nosso View
contém uma variável @EnvironmentObject var userData: UserData
, para visualizações Previews
, devemos definir seu valor inicial com .environmentObject(UserData())
:
Combinação de vários Views
.
SwiftUI
- isso é principalmente uma combinação de vários pequenos Views
em grandes e grandes Views
em muito grandes etc., como no jogo Lego
. Como SwiftUI
há uma variedade de meios, tais combinação Views
:- uma pilha vertical
VStack
, - pilha horizontal
HStack
, - "Profundidade" da pilha
ZStack
, - grupo
Group
, ScrollView
,- lista
List
, - formar
Form
, - recipiente de marcador
TabView
- etc.
Começamos nossa combinação com a mais simples GraphsViewForChart
, que fornece o GraphsForChart
AXIS Y "sem rosto" "conjunto de gráficos" e um indicador movendo-se ao longo do eixo X usando a pilha "profunda" ZStack
:
adicionamos um contêiner ao Previews
nosso novo GraphsViewForChart
contêiner NavigationView
para exibi-lo no Dark
modo usando um modificador .collorScheme(.dark)
.Continuamos a combinação e anexamos ao "conjunto de gráficos" obtido acima com o AXIS Y e um indicador, o AXIS X na forma de uma "linha de corrida", além de controles: o intervalo de tempo do "mini-mapa" RangeView
e os comutadores de CheckMarksView
exibição de "Gráficos".Como resultado, obtemos o indicado acima ChartView
, que exibe um "conjunto de gráficos" e permite controlar sua exibição no eixo do tempo:
Nesse caso, executamos a combinação usando a pilha vertical VStack
:
Agora, consideraremos 3 opções para combinar o conjunto de "ChartView Charts" já recebidos:- "Tabela rolável"
List
, - pilha horizontal
HStack
com efeito 3D, ZStack
"cartões" sobrepostos
Uma “tabela rolável” éListChartsView
organizada usando uma lista List
: Uma
pilha horizontal com efeito 3D é organizada usando uma ScrollView
pilha horizontal HStack
e uma lista no formato ForEach
:
Nesta exibição, todos os meios de interação do usuário funcionam completamente: movendo-se na linha do tempo e alterando os mini- map
botões “escala” , indicador e ocultar "Gráficos".ZStack
sobrepostos "cartões".
Primeiro criamos CardView
para o “mapa” - este é um “conjunto de gráficos” com os EIXOS X e Y, mas sem controles: sem um “mini-mapa” e sem botões para controlar a aparência / ocultação de gráficos. CardView
muito parecido com ChartView
, mas como vamos sobrepor as "cartas" umas sobre as outras, precisamos que elas sejam opacas.Para isso, usamos uma ZStack
cor adicional para ser colocada no "fundo" cardBackgroundColor
. Além disso, vamos fazer para "mapa" uma caixa com cantos arredondados:
superposição do "mapa" organizado por uma pilha VStack
, ZStack
ea lista na forma ForEach
:
Mas vamos impor uns sobre os outros não é apenas um "cartão" e "3D-macshtabiruemye" cartões CardViewScalable
, cujo tamanho diminui com o aumento do índiceindexChat
e eles mudam um pouco na vertical.A ordem dos "cartões escaláveis em 3D" pode ser alterada usando a sequência ( sequenced
) de gestos LongPressGesture
e DragGesture
, que atua apenas no "cartão" mais alto com indexChat == 0
:
Você pode clicar em ( LongPress
) no "cartão" superior com um "conjunto de gráficos" e puxá-lo Drag
para baixo ( ) longe o suficiente para olhar para a próxima carta e, se você continuar a arrastá-la para baixo, ela "vai" para o último lugar ZStack
e a próxima "carta" aparece:
Além disso, para a "carta" superior podemos aplicar TapGesture
, o que agirá juntamente com gestos LongPressGesture
e uma DragGesture
:
Tap
um gesto mostrará o modal "conjunto de gráficos" ChartView
com e gestão ementami RangeView
eCheckMarksView
:
Pedido TabView
de combinação em uma tela de todas as 3 variantes da composição "conjunto de gráficos" ChartView
.
Temos 3 marcadores com uma imagem Image
e um texto Text
, VStack
não é necessária uma pilha vertical para sua apresentação conjunta.Eles correspondem aos nossos três métodos de combinação de "conjuntos de gráficos" ChartViews
:- "Tabela rolável"
ListChartViews
, - pilha horizontal com efeito 3D
HStackChartViews
, - O ZStack sobrepôs "cartões"
OverlayCardsViews
.
Todos os elementos da interação do usuário: movendo-se ao longo da linha do tempo e alterando a “escala” com a ajuda mini - map
, indicador e botões para ocultar os “Gráficos”. funcione totalmente em todos os 3 casos.O código está no Github .SwiftUI
...
Você deve estar familiarizado com tutoriais em vídeo, livros e blogs:Mang do Para , Deixa Que a criar o aplicativo , bem como uma descrição de algumas aplicações SwiftUI ,- livre livro «SwiftUI por exemplo» e vídeo www.hackingwithswift.com/quick-start/swiftui- livro de pagamento mas metade dele pode ser baixado gratuitamente www.bigmountainstudio.com/swiftui-views-book- curso de 100 dias com SwiftUI www.hackingwithswift.com/articles/201/start-the-100-days-of-swiftui , que começa agora e terminará em 31 de dezembro de 2019,- coisas impressionantes no SwiftUI são feitas no swiftui-lab.com- Majid blog ,- no pointFree.cowww.pointfree.co A “maratona” de posts sobre o uso de redutores no SwiftUI (super interessante)é um ótimo aplicativo MovieSwiftUI que emprestou algumas idéias.