Home > Enterprise >  Animate view every time an Observed property changes in SwiftUI
Animate view every time an Observed property changes in SwiftUI

Time:01-31

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()
  • Related