Delay a repeating animation in SwiftUI with between full autoreverse repeat cycles
A possible solution is to chain single pieces of animation using DispatchQueue.main.asyncAfter
. This gives you control when to delay specific parts.
Here is a demo:
struct SimpleBeatingView: View {
@State private var isBeating = false
@State private var heartState: HeartState = .normal
@State private var beatLength: TimeInterval = 1
@State private var beatDelay: TimeInterval = 3
var body: some View {
VStack {
Image(systemName: "heart.fill")
.imageScale(.large)
.font(.largeTitle)
.foregroundColor(.red)
.scaleEffect(heartState.scale)
Button("isBeating: \(String(isBeating))") {
isBeating.toggle()
}
HStack {
Text("beatLength")
Slider(value: $beatLength, in: 0.25...2)
}
HStack {
Text("beatDelay")
Slider(value: $beatDelay, in: 0...5)
}
}
.onChange(of: isBeating) { isBeating in
if isBeating {
startAnimation()
} else {
stopAnimation()
}
}
}
}
private extension SimpleBeatingView {
func startAnimation() {
isBeating = true
withAnimation(Animation.linear(duration: beatLength * 0.25)) {
heartState = .large
}
DispatchQueue.main.asyncAfter(deadline: .now() + beatLength * 0.25) {
withAnimation(Animation.linear(duration: beatLength * 0.5)) {
heartState = .small
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + beatLength * 0.75) {
withAnimation(Animation.linear(duration: beatLength * 0.25)) {
heartState = .normal
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + beatLength + beatDelay) {
withAnimation {
if isBeating {
startAnimation()
}
}
}
}
func stopAnimation() {
isBeating = false
}
}
enum HeartState {
case small, normal, large
var scale: CGFloat {
switch self {
case .small: return 0.5
case .normal: return 0.75
case .large: return 1
}
}
}
Swift 3 - Delay Repeating Animation
Use a Timer
:
// note that I used 17.5 here because the animation itself takes 7.5 seconds
// so that will be 7.5 seconds of animating, 10 seconds of doing nothing
// and start animating again
Timer.scheduledTimer(withTimeInterval: 17.5, repeats: true) {
UIView.animate(withDuration: 7.5, animations: {
self.imageAnimate.center.x += self.view.bounds.width * 2
})
}
Delay SwiftUI combined transitions
Try this
extension AnyTransition {
static var delayAndFade: AnyTransition {
return AnyTransition.identity
.combined(with: .opacity)
.animation(.default.delay(3))
}
}
If you want to move a view, you should animate its offset
using the withAnimation
function.
Text("Move and fade.")
.offset(y: offset)
.transition(.delayAndFade)
struct ContentView: View {
@State private var showDetails = false
@State var offset:CGFloat = 0
var body: some View {
VStack {
Button("Press to show details") {
showDetails.toggle()
withAnimation(.default.delay(3)) {
self.offset = -20
}
}
if showDetails {
Text("Move and fade.")
.offset(y: offset)
.transition(.delayAndFade)
}
}
}
}
Update
extension AnyTransition {
static var moveAndFade: AnyTransition {
return AnyTransition.move(edge: .top)
.combined(with: .opacity)
}
}
Try this
HStack {
Text("Move and fade.")
}
.animation(Animation.default.delay(2))
.transition(.moveAndFade)
It works with all kind of views except Text.
struct ContentView: View {
@State private var showDetails = false
@State var offset:CGFloat = 0
var body: some View {
VStack {
Button("Press to show details") {
showDetails.toggle()
}
if showDetails {
// Works!
HStack {
Text("Move and fade.")
}
.animation(Animation.default.delay(2))
.transition(.moveAndFade)
Button("Move and fade.") {}
.animation(Animation.default.delay(2))
.transition(.moveAndFade)
// Does not work
Text("Move and fade.")
.animation(Animation.default.delay(2))
.transition(.moveAndFade)
}
}
}
}
SwiftUI animation: How to stagger repeating animation with delay
I think you can implement it using Timer and DispatchQueue, try this and see it's working as you want or no
struct Arrows: View {
private let arrowCount = 3
let timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect()
@State var scale:CGFloat = 1.0
@State var fade:Double = 0.5
var body: some View {
ZStack {
Color(red: 29.0/255.0, green: 161.0/255.0, blue: 224.0/255.0).edgesIgnoringSafeArea(.all)
HStack{
ForEach(0..<self.arrowCount) { i in
ArrowShape()
.stroke(style: StrokeStyle(lineWidth: CGFloat(10),
lineCap: .round,
lineJoin: .round ))
.foregroundColor(Color.white)
.aspectRatio(CGSize(width: 28, height: 70), contentMode: .fit)
.frame(maxWidth: 20)
.animation(nil)
.opacity(self.fade)
.scaleEffect(self.scale)
.animation(
Animation.easeOut(duration: 0.5)
//.repeatForever(autoreverses: true)
.repeatCount(1, autoreverses: true)
.delay(0.2 * Double(i))
)
}.onReceive(self.timer) { _ in
self.scale = self.scale > 1 ? 1 : 1.2
self.fade = self.fade > 0.5 ? 0.5 : 1.0
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.scale = 1
self.fade = 0.5
}
}
}
}
}
}
How to STOP Animation().repeatForever in SwiftUI
Update - retested with Xcode 13.4 / iOS 15.5
The following should work, moved animation into overlay-only and added conditional to .default
(tested with Xcode 11.2 / iOS 13.2)
Button("Start", action: { self.start.toggle() })
.font(.largeTitle)
.padding()
.foregroundColor(.white)
.background(RoundedRectangle(cornerRadius: 10).fill(Color.green))
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.green, lineWidth: 4)
.scaleEffect(start ? 2 : 0.9)
.opacity(start ? 0 : 1)
.animation(start ? Animation.easeOut(duration: 0.6)
.delay(1) // Add 1 second between animations
.repeatForever(autoreverses: false) : .default, value: start)
)
On GitHub
Related Topics
Running Swift Build in Terminal Leading to "Platform Path" Errors
Swift, Error Exc_Breakpoint (Code=1, Subcode=0X100695474)
How to Open Your App's Settings (Inside the Settings App) with Swift (iOS 11)
C-Style Uninitialized Pointer Passing in Apple Swift
Differences Between Filtering Realm with Nspredicate and Block
Swiftui Share Sheet Crashes iPad
Swift Compiler Error Int Is Not Convertible to Cgfloat
Create a Navigationlink Without Back Button Swiftui
How to Get Mouse Location with Swiftui
Swift/Cloudkit: After Record Changed, Upload Triggers "Service Record Changed"
Avplayerviewcontroller Black Screen When Swiping on iOS 11
Is .Playground a Swift File? Who Can 'See' It
Ambigious Reference to Member Request() Issues with Alamofire After Migration to Swift 3
How to Implement a Generic Struct That Manages Key-Value Pairs for Userdefaults in Swift
How to Build a Swift Executable for Linux on Macos
Property with '= {Return}()' or '{Return}'
How to Hide the Back Button from the Status Bar on the Apple Watch