繁体   English   中英

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

[英]SwiftUI animate text component when value changes

假设我有一个显示两个数字的 SwiftUI 组件。 下面的示例代码模拟了一个简单的情况,即随机不时更新两个数字。

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)")
        }
    }
}

上面的代码有效。 但是,如果我希望在更新两个数字时发生以下动画序列,该怎么办?

  1. 先显示更新后的数字1
  2. 1 秒后显示更新的数字 2
  3. 1 秒后继续使这两个数字闪烁几次。

理论上动画的功能大致是这样的

    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
            }
        }
    }
}

但是我不知道何时或如何在 SwiftUI 组件中执行这样的函数?

SwiftUI 有一个内置的动画函数,它们是由变量的变化触发的。 你不必自己滚动。 当然不是多个计时器。 如果您要使用计时器,则可以使用一个计时器,并根据该计时器跟踪事情何时发生变化。 不建议使用多个定时器。

由于您列出的要求,此代码比普通动画稍微复杂一些,但您需要延迟。 如果您只是希望视图在更改时闪烁,我会简单地将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)")
        }
    }
}

而且,为了回答您的原始问题,如果您使用的是 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