Assuming I have a SwiftUI component that display two numbers. 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?
- First display the updated number 1
- 1 second later display the updated number 2
- 1 second later proceed to blink the two numbers a few times.
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?
CodePudding user response:
SwiftUI has a built animation functions, and they are triggered by a change in a variable. 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.
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:
.onChange(of: number1) { _ in
updateNumber1and2(number1: number1, number2:number2)
}