Repeating Action Continuously In SwiftUI

A possible solution is to use a (repeating, auto-reversing) animation:

struct ContentView : View {
    @State var size: CGFloat = 0.5
    
    var repeatingAnimation: Animation {
        Animation
            .easeInOut(duration: 2) //.easeIn, .easyOut, .linear, etc...
            .repeatForever()
    }

    var body: some View {
        Text("Hello!")
            .padding()
            .scaleEffect(size)
            .onAppear() {
                withAnimation(self.repeatingAnimation) { self.size = 1.3 }
        }
    }
}

The best way is to create separate animation struct and configure all the options you need(this way your code will be more compact).

To make it more clear and logical use @State property isAnimating. You will be able to stop your animation and resume again and understand when it is in progress.

    @State private var isAnimating = false

    var foreverAnimation: Animation {
        Animation.linear(duration: 0.3)
        .repeatForever()
    }

    var body: some View {

        Text("Hello")
            .scaleEffect(isAnimating ? 1.5 : 1)
            .animation(foreverAnimation)
            .onAppear {
                self.isAnimating = true
        }
}

Using a repeating animation on a view has weird behaviour when used inside if statements.

If you want to do:

if something {
    BlinkingView()
}

use a transition with an animation modifier, otherwise the view stays on the screen even after something is set to false.

I made this extension to show a view that repeats change from one state to the next and back:

extension AnyTransition {
    static func repeating<T: ViewModifier>(from: T, to: T, duration: Double = 1) -> AnyTransition {
       .asymmetric(
            insertion: AnyTransition
                .modifier(active: from, identity: to)
                .animation(Animation.easeInOut(duration: duration).repeatForever())
                .combined(with: .opacity), 
            removal: .opacity
        )
    }
}

This makes the view appear and disappear with AnyTransition.opacity and while it is shown it switches between the from and to state with a delay of duration.

Example usage:

struct Opacity: ViewModifier {
    private let opacity: Double
    init(_ opacity: Double) {
        self.opacity = opacity
    }

    func body(content: Content) -> some View {
        content.opacity(opacity)
    }
}

struct ContentView: View {
    @State var showBlinkingView: Bool = false

    var body: some View {
        VStack {
            if showBlinkingView {
                Text("I am blinking")
                    .transition(.repeating(from: Opacity(0.3), to: Opacity(0.7)))
            }
            Spacer()
            Button(action: {
                self.showBlinkingView.toggle()
            }, label: {
                Text("Toggle blinking view")
            })
        }.padding(.vertical, 50)
    }
}

Edit:

When the show condition is true on appear, the transition doesn't start. To fix this I do toggle the condition on appear of the superview (The VStack in my example):

.onAppear {
    if self.showBlinkingView {
        self.showBlinkingView.toggle()
        DispatchQueue.main.async {
            self.showBlinkingView.toggle()
        }
    }
}

Animation.basic is deprecated. Basic animations are now named after their curve types: like linear, etc:

var foreverAnimation: Animation {
        Animation.linear(duration: 0.3)
        .repeatForever()
 }

Source: https://forums.swift.org/t/swiftui-animation-basic-duration-curve-deprecated/27076

Tags:

Swift

Swiftui