简体   繁体   English

SwiftUI 在值更改时为文本组件设置动画

[英]SwiftUI animate text component when value changes

Assuming I have a SwiftUI component that display two numbers.假设我有一个显示两个数字的 SwiftUI 组件。 The following sample code simulate a simple situation where the two numbers is updated from time to time randomly.下面的示例代码模拟了一个简单的情况,即随机不时更新两个数字。

struct ContentView: View {
    @State var number1: Int = 0
    @State var number2: Int = 0
    var timer:Timer {
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
            if (Int.random(in: 0...100)<10) {
                print("random event happened, increase numbers!")
                self.number1 = self.number1 + 1
                self.number2 = self.number2 + 1
            }
        }
    }
    var body: some View {
        ZStack {
            SampleNumbersView(numbers1: number1, numbers2: number2)
        }.onAppear {
            let _ = self.timer
        }
    }
}

struct SampleNumbersView: View {
    var numbers1:Int = 0
    var numbers2:Int = 0
    var body: some View {
        VStack {
            Text("Number 1: \(numbers1)")
            Text("Number 2: \(numbers2)")
        }
    }
}

The above code works.上面的代码有效。 However, what should I do if I want the following animation sequence to happens whenever the two numbers are updated?但是,如果我希望在更新两个数字时发生以下动画序列,该怎么办?

  1. First display the updated number 1先显示更新后的数字1
  2. 1 second later display the updated number 2 1 秒后显示更新的数字 2
  3. 1 second later proceed to blink the two numbers a few times. 1 秒后继续使这两个数字闪烁几次。

In theory the function to animate is roughly like this理论上动画的功能大致是这样的

    func updateNumber1and2(number1: Int, number2:Int) {
    //first show number1
    self.number1 = number1
    Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { _ in
        //next show number 2
        self.number2 = number2

        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { _ in
            //next blink text twice
            print("start blinking")
            self.numbersBlinking = true
            Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false) { _ in
                print("stop blinking")
                self.numbersBlinking = false
            }
        }
    }
}

But I couldnt figure out when or how to execute a function like this in the SwiftUI component?但是我不知道何时或如何在 SwiftUI 组件中执行这样的函数?

SwiftUI has a built animation functions, and they are triggered by a change in a variable. SwiftUI 有一个内置的动画函数,它们是由变量的变化触发的。 You wouldn't have to roll your own.你不必自己滚动。 And certainly not with multiple timers.当然不是多个计时器。 If you were to use timers, you could use one and just keep track of when things changed based on the one timer.如果您要使用计时器,则可以使用一个计时器,并根据该计时器跟踪事情何时发生变化。 Multiple timers are not recommended.不建议使用多个定时器。

Because of your listed requirements, this code is a bit more complicated than a normal animation, but you wanted the delays.由于您列出的要求,此代码比普通动画稍微复杂一些,但您需要延迟。 If you just wanted the view to blink in the change, I would have simply wrapped animator in the withAnimation() block and it would have blinked immediately upon the change.如果您只是希望视图在更改时闪烁,我会简单地将animator包装在withAnimation()块中,它会在更改时立即闪烁。

struct AnimatedTextView: View {
    @State var number1: Int = 0
    @State var number2: Int = 0
    @State var animator = false
    
    var body: some View {
        VStack {
            Button { // Changed your timer to a button so that you don't have to wait for some random event
                self.number1 = self.number1 + 1
                // This delays the execution by 1 second.
                DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                    self.number2 = self.number2 + 1
                }
                // This delays the execution by 2 seconds.
                DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                    withAnimation(.easeInOut(duration: 0.2).repeatCount(10)) {
                        // Changing this to true causes the opacity of the view to go to 0.
                        // By putting it in an animated block, the change will be slowed and repeated 5 times
                        // On and off each separately count as a time
                        animator = true
                    }
                    // This delays the execution by 1 second after the animator variable is changed.
                    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                        // This gives time for the animation to complete and then sets the opacity back to 1
                        // Otherwise the view will be hidden. It is essentially a reset.
                        animator = false
                    }
                }
                
            } label: {
                Text("Change Numbers")
            }
            
            SampleNumbersView(numbers1: number1, numbers2: number2)
                // animator is used here to change the opacity, and it is animated repeatedly
                // causing the view to blink.
                .opacity(animator ? 0 : 1)
        }
    }
}

struct SampleNumbersView: View {
    // Since you are not changing the numbers in this view, declare them as let
    // and do not give them an initial value. There is less system overhead.
    let numbers1: Int
    let numbers2: Int
    
    var body: some View {
        VStack {
            Text("Number 1: \(numbers1)")
            Text("Number 2: \(numbers2)")
        }
    }
}

And, to answer your original question, if you were on iOS 14 or later, you would use .onChange(of:perform:) on one of the views to catch the update and run your function like this:而且,为了回答您的原始问题,如果您使用的是 iOS 14 或更高版本,您将在其中一个视图上使用.onChange(of:perform:)来捕获更新并像这样运行您的函数:

        .onChange(of: number1) { _ in
            updateNumber1and2(number1: number1, number2:number2)
        }

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

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