'GradientAnimation ONLY Works in AnimatableModifier, Other Simultaneous Animations Working Regardless (SwiftUI)
I'm having a lot of trouble narrowing down the underlying implementation that might cause things to work this way. I've tried tons of combinations of using @State vs initializing, overlays, whatever.
1.
The only thing that works is when it is in an AnimatableModifier.
2. Other animations on the same view, and based on same value work regardless.
Visual Example
You can see the scale animation working fine, but only the bottom rectangle is animating the gradient
Reproducible Code
I got some of the original code here. They mention weird issues with containers, but it's not clear what is expected behavior and otherwise
Works with Previews
NOTE: if you're using previews, make sure to play or animations won't run at all
import SwiftUI
import UIKit
public struct AnimatableGradient: View, Animatable {
public let from: [Color]
public let to: [Color]
public var pct: CGFloat
public var animatableData: CGFloat {
get { pct }
set { pct = newValue }
}
private var current: [Color] {
zip(from, to).map { from, to in
from.mix(with: to, percent: pct)
}
}
public var body: some View {
LinearGradient(
gradient: Gradient(colors: current),
startPoint: UnitPoint(x: 0, y: 0),
endPoint: UnitPoint(x: 1, y: 1)
)
/// temporary to show that animating is working
.scaleEffect(1 - (0.2 * pct))
}
}
// MARK: AnimatableModifier
struct PassthroughModifier<Body: View>: AnimatableModifier {
var animatableData: CGFloat
let body: (CGFloat) -> Body
func body(content: Content) -> some View {
// also works to just pass body(animatableData)
content.overlay(body(animatableData))
}
}
// MARK: Content
fileprivate struct GradientExample: View, Animatable {
@State var pct: CGFloat = 0
private var base: some View {
Rectangle()
.fill(.black)
.frame(width: 200, height: 100)
}
private func gradient(pct: CGFloat) -> some View {
AnimatableGradient(from: [.orange, .red],
to: [.blue, .green],
pct: pct)
}
var body: some View {
VStack(alignment: .leading) {
base.overlay(
gradient(pct: pct)
)
base.modifier(
PassthroughModifier(animatableData: pct){ pct in
gradient(pct: pct)
}
)
}
.onAppear{
withAnimation(.linear(duration: 2.8).repeatForever(autoreverses: true)) {
self.pct = pct == 0 ? 1 : 0
}
}
}
}
// MARK: Preview
struct GradientPreview: PreviewProvider {
static var previews: some View {
GradientExample()
}
}
// MARK: Helpers
extension Color {
public var components: (r: Double, g: Double, b: Double, a: Double) {
/// passing through UIColor because things like `Color.red`
/// always return `nil` otherwise :/
let comps = UIColor(self).cgColor.components ?? []
return (
comps[safe: 0] ?? 0,
comps[safe: 1] ?? 0,
comps[safe: 2] ?? 0,
comps[safe: 3] ?? 0
)
}
}
extension Array {
subscript(safe idx: Int) -> Element? {
guard idx < count else { return nil }
return self[idx]
}
}
extension Color {
public func mix(with other: Color, percent: Double) -> Color {
let left = self.components
let right = other.components
let r = left.r + right.r - (left.r * percent)
let g = left.g + right.g - (left.g * percent)
let b = left.b + right.b - (left.b * percent)
return Color(red: Double(r), green: Double(g), blue: Double(b))
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|

