Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI Text animation on opacity does not work

Question is simple: how in the world do i get a Text to animate properly?

struct ContentView: View {
    @State var foozle: String = ""

    var body: some View {
        VStack() {
            Spacer()

            Text(self.foozle)
                .frame(maxWidth: .infinity)
                .transition(.opacity)

            Button(action: {
                withAnimation(.easeInOut(duration: 2)) {
                    self.foozle = "uuuuuuuuu"
                }
            }) { Text("ugh") }

            Spacer()
        }.frame(width: 320, height: 240)
    }
}

The problem: the view insists on doing some dumb animation where the text is replaced with the new text, but truncated with ellipses, and it slowly expands widthwise until the entirety of the new text is shown.

Naturally, this is not an animation on opacity. It's not a frame width problem, as I've verified with drawing the borders.

Is this just another dumb bug in SwiftUI that i'm going to have to deal with, and pray that someone fixes it?

EDIT: ok, so thanks to @Mac3n, i got this inspiration, which works correctly, even if it's a little ugly:

Text(self.foozle)
    .frame(maxWidth: .infinity)
    .opacity(op)

Button(action: {
    withAnimation(.easeOut(duration: 0.3)) {
        self.op = 0

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
            self.foozle += "omo"

            withAnimation(.easeIn(duration: 0.3)) {
                self.op = 1
            }
        }
    }

}) {
    Text("ugh")
}
like image 438
zhiayang Avatar asked Feb 11 '20 23:02

zhiayang


People also ask

Can we animate opacity?

transparency is called the alpha property in animate (with an alpha = 0 being transparent and an alpha =1 being fully opaque). Absolutely.

How do I make an animation 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.


3 Answers

The problem is that SwiftUI sees Text view as the same view. You can use the .id() method on the view to set it. In this case I've just set the value to a hash of the text itself, so if you change the text, the entire view will get replaced.

struct ContentView: View {
    @State var foozle: String = ""

    var body: some View {
        VStack() {
            Spacer()

            Text(self.foozle)
                .id(self.foozle.hashValue)
                .frame(maxWidth: .infinity)
                .transition(.opacity)

            Button(action: {
                withAnimation(.easeInOut(duration: 2)) {
                    self.foozle = "uuuuuuuuu"
                }
            }) { Text("ugh") }

            Spacer()
        }.frame(width: 320, height: 240)
    }
}
like image 101
Theis Egeberg Avatar answered Oct 24 '22 05:10

Theis Egeberg


Transition works when view appeared/disappeared. In your use-case there is no such workflow.

Here is a demo of possible approach to hide/unhide text with opacity animation:

enter image description here

struct DemoTextOpacity: View {
    var foozle: String = "uuuuuuuuu"
    @State private var hidden = true

    var body: some View {
        VStack() {
            Spacer()

            Text(self.foozle)
                .frame(maxWidth: .infinity)
                .opacity(hidden ? 0 : 1)

            Button(action: {
                withAnimation(.easeInOut(duration: 2)) {
                    self.hidden.toggle()
                }
            }) { Text("ugh") }

            Spacer()
        }.frame(width: 320, height: 240)
    }
}
like image 38
Asperi Avatar answered Oct 24 '22 03:10

Asperi


If you want to animate on opacity you need to change opacity value on your text element.

code example:

@State private var textValue: String = "Sample Data"
@State private var opacity: Double = 1
    var body: some View {
        VStack{
            Text("\(textValue)")
                .opacity(opacity)
            Button("Next") {
                withAnimation(.easeInOut(duration: 0.5), {
                    self.opacity = 0
                })
                self.textValue = "uuuuuuuuuuuuuuu"
                withAnimation(.easeInOut(duration: 1), {
                    self.opacity = 1
                })
            }
        }
    }
like image 43
Mac3n Avatar answered Oct 24 '22 05:10

Mac3n