Como animações "complexas" podem ser compostas no SwiftUI? Onde complexo significa que a animação completa é composta de múltiplas subanimações.
Exemplo
Estou trabalhando na migração de um projeto existente de UIKit
para SwiftUI
. O projeto usa um costume UIView
para exibir uma barra de progresso que anima as mudanças entre valores positivos e negativos. Valores positivos são mostrados com uma barra verde, valores negativos com uma barra vermelha.
Suponha um maxValue
valor de 100:
- Um valor de +60 será mostrado como uma barra verde de 60% .
- Alterar o valor para +20 animará a barra verde para 20% em 1 segundo
- Alterar o valor para -40 animará a barra verde até 0 em 1/3 de segundo e, em seguida, animará uma barra vermelha perfeitamente de 0 até 40% em 2/3 de segundo. Essa animação completa também leva 1 segundo no total.
Então, o comprimento total da animação é sempre 1 segundo, não importa como o valor muda. Ao mudar de um valor positivo para um negativo (ou vice-versa), a barra encolhe para 0 antes de mudar de cor e ficar mais longa novamente.
Barra de progresso simples
Criar uma barra de progresso simples no SwiftUI não foi grande coisa:
struct SomeProgressBar: View {
var height: CGFloat = 20
var minValue: Double = 0
var maxValue: Double = 100
var currentValue: Double = 20
var body: some View {
let maxV = max(minValue, maxValue)
let minV = min(minValue, maxValue)
let currentV = max(min(abs(currentValue - minValue), maxV), minV)
let isNegative = currentValue < minValue
let progress = (currentV - minV) / (maxV - minV)
Rectangle()
.fill(.gray)
.frame(height: height)
.overlay(alignment: .leading) {
GeometryReader { geometry in
Rectangle()
.fill(isNegative ? .red : .green)
.frame(width: geometry.size.width * progress)
}
}
.animation(.easeInOut(duration: 0.5), value: progress)
}
}
struct SomeProgressBarTestView: View {
@State var value: Double = 40
var body: some View {
VStack {
SomeProgressBar(currentValue: value)
Button("Change") {
value = .random(in: -100...100)
}
}
}
}
Problema
Embora isso funcione bem ao usar apenas valores positivos ou negativos, alternar entre valores positivos e negativos não é animado como pretendido. Por exemplo, alternar entre +x e -x só mudará a cor da barra, mas não animará a barra com isso. Isso não é nenhuma surpresa, pois a largura não muda, é x% antes e depois da mudança de valor. No entanto, o resultado desejado seria animar a largura de x para 0 e de volta para x.
Todas as fontes que encontrei até agora só distinguem animações implícitas e explícitas. Embora isso mude a maneira como a animação é implementada/descrita, não parece influenciar o que a animação pode fazer.
Qual seria a abordagem "correta" para criar a animação "complexa" desejada?
Duas animações diferentes?
Claro que alguém poderia calcular se uma troca entre positivo e negativo ocorreu e disparar duas animações diferentes neste caso com .withAnimation { ... }
. Calcular o comprimento da primeira animação e da segunda animação a partir da diferença de valor não seria problema.
No entanto , isso só funcionaria para animações lineares. Ao usar qualquer função de easing como spring, etc., não seria possível obter uma animação contínua com essa abordagem.
Além disso, a barra de progresso é apenas um exemplo. E se ainda mais animação precisasse ser encadeada para obter o resultado desejado.
Existe uma solução para criar animações personalizadas como essas?
Você pode estar em conformidade com
Animatable
. Extraia a parte relevante para a animação para umViewModifier
que esteja em conformidade comAnimatable
.Em cada quadro da animação, o SwiftUI definirá
animatableData
algum valor interpolado entre os pontos inicial e final da animação. Ele então chamarábody
e atualizará a UI com o novo quadro.Você também pode conformar o todo
SomeProgressBar
aAnimatable
, mas então oanimation
modificador precisa ser movido para a visualização pai.