SwiftUI: Animate Text color - foregroundColor()

enter image description here

Color property of Text is not animatable in SwiftUI or UIKit. BUT YOU CAN achieve the result you need like this:

struct ContentView: View {

    @State var highlighted = false

    var body: some View {
        VStack {
            ZStack {
            // Highlighted State
            Text("Text To Change Color")
                .foregroundColor(.red)
                .opacity(highlighted ? 1 : 0)

            // Normal State
            Text("Text To Change Color")
                .foregroundColor(.blue)
                .opacity(highlighted ? 0 : 1)
            }
            Button("Change") {
                withAnimation(.easeIn) {
                    self.highlighted.toggle()
                }
            }
        }
    }
}

You can encapsulate this functionality in a custom View and use it anywhere you like.


There is a much easier way, borrowing from Apple's "Animating Views and Transitions" tutorial code. The instantiation of GraphCapsule in HikeGraph demonstrates this.

While foregroundColor cannot be animated, colorMultiply can. Set the foreground color to white and use colorMultiply to set the actual color you want. To animate from red to blue:

struct AnimateDemo: View {
    @State private var color = Color.red

    var body: some View {
        Text("Animate Me!")
            .foregroundColor(Color.white)
            .colorMultiply(self.color)
            .onTapGesture {
                withAnimation(.easeInOut(duration: 1)) {
                    self.color = Color.blue
                }
        }
    }
}

struct AnimateDemo_Previews: PreviewProvider {
    static var previews: some View {
        AnimateDemo()
    }
}

There is a nice protocol in SwiftUI that let you animate anything. Even things that are not animatable! (such as the text color). The protocol is called AnimatableModifier.

If you would like to learn more about it, I wrote a full article explaining how this works: https://swiftui-lab.com/swiftui-animations-part3/

Here's an example on how you can accomplish such a view:

AnimatableColorText(from: UIColor.systemRed, to: UIColor.systemGreen, pct: flag ? 1 : 0) {
    Text("Hello World").font(.largeTitle)
}.onTapGesture {
    withAnimation(.easeInOut(duration: 2.0)) {
      self.flag.toggle()
    }
}

And the implementation:

struct AnimatableColorText: View {
    let from: UIColor
    let to: UIColor
    let pct: CGFloat
    let text: () -> Text

    var body: some View {
        let textView = text()

        // This should be enough, but there is a bug, so we implement a workaround
        // AnimatableColorTextModifier(from: from, to: to, pct: pct, text: textView)

        // This is the workaround
        return textView.foregroundColor(Color.clear)
            .overlay(Color.clear.modifier(AnimatableColorTextModifier(from: from, to: to, pct: pct, text: textView)))
    }

    struct AnimatableColorTextModifier: AnimatableModifier {
        let from: UIColor
        let to: UIColor
        var pct: CGFloat
        let text: Text

        var animatableData: CGFloat {
            get { pct }
            set { pct = newValue }
        }

        func body(content: Content) -> some View {
            return text.foregroundColor(colorMixer(c1: from, c2: to, pct: pct))
        }

        // This is a very basic implementation of a color interpolation
        // between two values.
        func colorMixer(c1: UIColor, c2: UIColor, pct: CGFloat) -> Color {
            guard let cc1 = c1.cgColor.components else { return Color(c1) }
            guard let cc2 = c2.cgColor.components else { return Color(c1) }

            let r = (cc1[0] + (cc2[0] - cc1[0]) * pct)
            let g = (cc1[1] + (cc2[1] - cc1[1]) * pct)
            let b = (cc1[2] + (cc2[2] - cc1[2]) * pct)

            return Color(red: Double(r), green: Double(g), blue: Double(b))
        }

    }
}

Tags:

Ios

Swift

Swiftui