In the following code, an Image is animated when you tap on it by changing the @State
property value. What I would like to be able to do is animate the image every time a @Published
property value changes, this property is located inside an ObservableObject
and is dynamically changing.
Using Local @State property wrappers, works fine.
struct ContentView: View {
@State private var scaleValue = 1.0
@State private var isAnimating: Bool = true
var body: some View {
Image(systemName: "circle.fill")
.scaleEffect(scaleValue)
.font(.title)
.foregroundColor(.green)
.animation(.easeOut(duration: 0.3), value: isAnimating)
.onTapGesture {
self.isAnimating.toggle()
self.scaleValue = 1.5
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() 0.3){
self.scaleValue = 1.0
}
}
}
}
Using Observed @Published property wrappers, not working. Not sure where to place the animation.
struct ContentView: View {
@State private var scaleValue = 1.0
@StateObject private var contentVM = ContentViewModel()
var body: some View {
Image(systemName: "circle.fill")
.scaleEffect(scaleValue)
.font(.title)
.foregroundColor(.green)
.animation(.easeOut(duration: 0.3), value: contentVM.isAnimating)
// not sure where to add the animation
}
}
CodePudding user response:
I think the issue here is the wrong usage of the .animation
modifier.
You don´t need to trigger the animation with a different boolean value. If the animation is "driven" by changing a value, in this case scaleValue
, use that value in the modifier.
Take a look at your animation in your first example. It doesn´t complete. It scales to the desired size and then shrinks. But it jumps in the middle of the animation while shrinking back.
This would be a proper implementation in your first example:
struct ContentView: View {
@State private var scaleValue = 1.0
var body: some View {
Image(systemName: "circle.fill")
.scaleEffect(scaleValue)
.font(.title)
.foregroundColor(.green)
.animation(.easeOut(duration: 0.3), value: scaleValue)
.onTapGesture {
self.scaleValue = 1.5
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() 0.3){
self.scaleValue = 1.0
}
}
}
}
and in your second example:
class ContentViewModel: ObservableObject{
@Published var scaleValue = 1.0
}
struct ContentView2: View {
@StateObject private var contentVM = ContentViewModel()
var body: some View {
Image(systemName: "circle.fill")
.scaleEffect(contentVM.scaleValue)
.font(.title)
.foregroundColor(.green)
.animation(.easeOut(duration: 0.3), value: contentVM.scaleValue)
.onTapGesture {
contentVM.scaleValue = 1.5
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() 0.3){
contentVM.scaleValue = 1.0
}
}
}
}
CodePudding user response:
This is why we shouldn't be using view model objects in SwiftUI and this is not what @StateObject
is designed for - it's designed for when we need a reference type in an @State
which is not the case here. If you want to group your view data vars together you can put them in a struct, e.g.
struct ContentViewConfig {
var isAnimating = false
var scaleValue = 1.0
// mutating func someLogic() {}
}
// then in the View struct:
@State var config = ContentViewConfig()