Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animating Text in Swift UI

Tags:

ios

swift

swiftui

How would it be possible to animate Text or TextField views from Swift UI?

By animation I mean, that when the text changes it will "count up".

For example given some label, how can an animation be created that when I set the labels text to "100" it goes up from 0 to 100. I know this was possible in UIKit using layers and CAAnimations, but using the .animation() function in Swift UI and changing the text of a Text or TextField does not seem to do anything in terms of animation.

I've taken a look at Animatable protocol and its related animatableData property but it doesn't seem like Text nor TextField conform to this. I'm trying to create a label that counts up, so given some value, say a Double the changes to that value would be tracked, either using @State or @Binding and then the Text or TextField would animate its content (the actual string text) from what the value was at to what it was set to.

Edit:

To make it clearer, I'd like to recreate a label that looks like this when animated:

enter image description here

like image 783
Luis Avatar asked Jul 10 '19 21:07

Luis


People also ask

How do you animate text in SwiftUI?

To animate the change, we can add animation(_:value:) modifier. For the value parameter, we should pass the value of the state variable that alters text font weight.

How do I animate in SwiftUI?

SwiftUI has built-in support for animations with its animation() modifier. To use this modifier, place it after any other modifiers for your views, tell it what kind of animation you want, and also make sure you attach it to a particular value so the animation triggers only when that specific value changes.

How do I animate a view in SwiftUI?

If you want a SwiftUI view to start animating as soon as it appears, you should use the onAppear() modifier to attach an animation.

Does SwiftUI use Core Animation?

SwiftUI uses Core Animation for its rendering by default, which offers great performance out of the box. However, for complex rendering you might find your code starts to slow down – anything below 60 frames per second (FPS) is a problem, but really you ought to aim higher because many iOS devices now render at 120fps.


1 Answers

There is a pure way of animating text in SwiftUI. Here's an implementation of your progress indicator using the AnimatableModifier protocol in SwiftUI:

enter image description here

I've written an extensive article documenting the use of AnimatableModifier (and its bugs). It includes the progress indicator too. You can read it here: https://swiftui-lab.com/swiftui-animations-part3/

struct ContentView: View {
    @State private var percent: CGFloat = 0

    var body: some View {
        VStack {
            Spacer()
            Color.clear.overlay(Indicator(pct: self.percent))

            Spacer()
            HStack(spacing: 10) {
                MyButton(label: "0%", font: .headline) { withAnimation(.easeInOut(duration: 1.0)) { self.percent = 0 } }

                MyButton(label: "27%", font: .headline) { withAnimation(.easeInOut(duration: 1.0)) { self.percent = 0.27 } }

                MyButton(label: "100%", font: .headline) { withAnimation(.easeInOut(duration: 1.0)) { self.percent = 1.0 } }
            }
        }.navigationBarTitle("Example 10")
    }
}

struct Indicator: View {
    var pct: CGFloat

    var body: some View {
        return Circle()
            .fill(LinearGradient(gradient: Gradient(colors: [.blue, .purple]), startPoint: .topLeading, endPoint: .bottomTrailing))
            .frame(width: 150, height: 150)
            .modifier(PercentageIndicator(pct: self.pct))
    }
}

struct PercentageIndicator: AnimatableModifier {
    var pct: CGFloat = 0

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

    func body(content: Content) -> some View {
        content
            .overlay(ArcShape(pct: pct).foregroundColor(.red))
            .overlay(LabelView(pct: pct))
    }

    struct ArcShape: Shape {
        let pct: CGFloat

        func path(in rect: CGRect) -> Path {

            var p = Path()

            p.addArc(center: CGPoint(x: rect.width / 2.0, y:rect.height / 2.0),
                     radius: rect.height / 2.0 + 5.0,
                     startAngle: .degrees(0),
                     endAngle: .degrees(360.0 * Double(pct)), clockwise: false)

            return p.strokedPath(.init(lineWidth: 10, dash: [6, 3], dashPhase: 10))
        }
    }

    struct LabelView: View {
        let pct: CGFloat

        var body: some View {
            Text("\(Int(pct * 100)) %")
                .font(.largeTitle)
                .fontWeight(.bold)
                .foregroundColor(.white)
        }
    }
}
like image 86
kontiki Avatar answered Sep 18 '22 15:09

kontiki