简体   繁体   English

如何在 SwiftUI 中动画/过渡文本值更改

[英]How to animate/transition text value change in SwiftUI

I am trying to animate value change in a text using withAnimation but it doesn't seem to work.我正在尝试使用withAnimation为文本中的值更改设置动画,但它似乎不起作用。 I have come across a similar question but the answer is not animating the text value.我遇到了一个类似的问题,但答案不是动画文本值。

I am trying to recreate this behaviour in pure SwiftUI ( UIKit Example ):我正在尝试在纯 SwiftUI( UIKit 示例)中重新创建此行为:

在此处输入图片说明

I have tried this code but it doesn't animate the text change:我已经尝试过这段代码,但它没有为文本更改设置动画:

struct TextAnimationView: View {
    @State private var textValue = "0"
    var body: some View {
        VStack (spacing: 50) {
            Text(textValue)
                .font(.largeTitle)
                .frame(width: 200, height: 200)
                .transition(.opacity)
            Button("Next") {
                withAnimation (.easeInOut(duration: 1)) {
                    self.textValue = "\(Int.random(in: 1...100))"
                }
            }
        }
    }
}

I have a very little experience with SwiftUI, is there another way to achieve this?我对 SwiftUI 的经验很少,还有其他方法可以实现吗?

Thanks in advance :)提前致谢 :)

So it turns out this is really easy所以事实证明这真的很容易

Text(textValue)
  .font(.largeTitle)
  .frame(width: 200, height: 200)
  .transition(.opacity)
  .id("MyTitleComponent" + textValue)

Note the additional id at the end.请注意末尾的附加id SwiftUI uses this to decide if it's dealing with the same view or not when doing a redraw. SwiftUI 使用它来决定在重绘时是否处理相同的视图。 If the id is different then it assumes the previous view was removed and this one has been added.如果 id 不同,则假定先前的视图已被删除,而已添加此视图。 Because it's adding a new view it applies the specified transition as expected.因为它正在添加一个新视图,所以它按预期应用了指定的转换。

NB: It's quite possible that this id should be unique for the entire view tree so you probably want to take care to namespace it accordingly (hence the MyTitleComponent prefix in the example).注意:这个 id 很可能在整个视图树中应该是唯一的,因此您可能需要相应地注意命名空间(因此是示例中的MyTitleComponent前缀)。

Below is an approach with AnimatableModifier .下面是一种使用AnimatableModifier的方法。 It only fades in the new value.它只会在新值中消失。 If you want to fade out the old value as well, it won't be that hard to customize the modifier.如果您还想淡出旧值,自定义修改器并不难。 Also since your display value is numeric, you can use that itself as the control variable with some minor modifications.此外,由于您的显示值是数字,您可以使用它本身作为控制变量,并进行一些小的修改。

This approach can be used for not only fade but also other types of animations, in response to a change in the value of the view.这种方法不仅可以用于淡入淡出,还可以用于其他类型的动画,以响应视图值的变化。 You can have additional arguments passed to the modifier.您可以将其他参数传递给修饰符。 You may also completely ignore the passed content in the body and create and return an entirely new view.您也可以完全忽略body传递的content并创建并返回一个全新的视图。 Overlay , EmptyView etc too can be handy in such cases. OverlayEmptyView等在这​​种情况下也很方便。

import SwiftUI

struct FadeModifier: AnimatableModifier {
    // To trigger the animation as well as to hold its final state
    private let control: Bool

    // SwiftUI gradually varies it from old value to the new value
    var animatableData: Double = 0.0

    // Re-created every time the control argument changes
    init(control: Bool) {
        // Set control to the new value
        self.control = control

        // Set animatableData to the new value. But SwiftUI again directly
        // and gradually varies it from 0 to 1 or 1 to 0, while the body
        // is being called to animate. Following line serves the purpose of
        // associating the extenal control argument with the animatableData.
        self.animatableData = control ? 1.0 : 0.0
    }

    // Called after each gradual change in animatableData to allow the
    // modifier to animate
    func body(content: Content) -> some View {
        // content is the view on which .modifier is applied
        content
            // Map each "0 to 1" and "1 to 0" change to a "0 to 1" change
            .opacity(control ? animatableData : 1.0 - animatableData)

            // This modifier is animating the opacity by gradually setting
            // incremental values. We don't want the system also to
            // implicitly animate it each time we set it. It will also cancel
            // out other implicit animations now present on the content.
            .animation(nil)
    }
}

struct ExampleView: View {
    // Dummy control to trigger animation
    @State var control: Bool = false

    // Actual display value
    @State var message: String = "Hi" {
        didSet {
            // Toggle the control to trigger a new fade animation
            control.toggle()
        }
    }

    var body: some View {
        VStack {
            Spacer()

            Text(message)
                .font(.largeTitle)

                // Toggling the control causes the re-creation of FadeModifier()
                // It is followed by a system managed gradual change in the
                // animatableData from old value of control to new value. With
                // each change in animatableData, the body() of FadeModifier is
                // called, thus giving the effect of animation
                .modifier(FadeModifier(control: control))

                // Duration of the fade animation
                .animation(.easeInOut(duration: 1.0))

            Spacer()

            Button(action: {
                self.message = self.message == "Hi" ? "Hello" : "Hi"
            }) {
                Text("Change Text")
            }

            Spacer()
        }
    }
}

struct ExampleView_Previews: PreviewProvider {
    static var previews: some View {
        ExampleView()
    }
}

I couldn't find a way to animate the text value with a fade.我找不到通过淡入淡出为文本值设置动画的方法。 When setting the animation property of a Text you will see three dots (...) when animating.设置Text的动画属性时,您将在动画时看到三个点 (...)。

As for now I figured out a work around which will change the opacity:至于现在我想出了一个可以改变不透明度的工作:

@State private var textValue: Int = 1
@State private var opacity: Double = 1

var body: some View {
    VStack (spacing: 50) {
        Text("\(textValue)")
            .font(.largeTitle)
            .frame(width: 200, height: 200)
            .opacity(opacity)
        Button("Next") {
            withAnimation(.easeInOut(duration: 0.5), {
                self.opacity = 0
            })
            self.textValue += 1
            withAnimation(.easeInOut(duration: 1), {
                self.opacity = 1
            })
        }
    }
}

This will fade out and fade in the text when you change it.当您更改它时,这将淡出并淡入文本。

Here is the approach using standard transition.这是使用标准转换的方法。 Font sizes, frames, animation durations are configurable up to your needs.字体大小、帧、动画持续时间可根据您的需要进行配置。 Demo includes only important things for approach.演示仅包括重要的方法。

SwiftUI 淡入淡出过渡

struct TestFadeNumbers: View {
    @State private var textValue: Int = 0

    var body: some View {
        VStack (spacing: 50) {
            if textValue % 2 == 0 {
                Text("\(textValue)")
                    .font(.system(size: 200))
                    .transition(.opacity)
            }
            if textValue % 2 == 1 {
                Text("\(textValue)")
                    .font(.system(size: 200))
                    .transition(.opacity)
            }
            Button("Next") {
                withAnimation(.linear(duration: 0.25), {
                    self.textValue += 1
                })
            }
            Button("Reset") {
                withAnimation(.easeInOut(duration: 0.25), {
                    self.textValue = 0
                })
            }
        }
    }
}

if @Tobias Hesselink's code doesn't working,consider use this:如果@Tobias Hesselink 的代码不起作用,请考虑使用:

VStack {
            Text(文本)
                .transition(AnyTransition.opacity.combined(with: .slide))
                .id("MyTitleComponent" + 文本)
            Button("Button") {
                withAnimation(.easeInOut(duration: 1.0)) {
                    文本 = "NewText"
                }

} }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM