Repeating Animation on Swiftui Image

Repeating animation on SwiftUI Image

Here is possible solution for continuous progressing on appear & start/stop. Tested with Xcode 11.4 / iOS 13.4.

demo

struct PeopleList : View {
@State private var isAnimating = false
@State private var showProgress = false
var foreverAnimation: Animation {
Animation.linear(duration: 2.0)
.repeatForever(autoreverses: false)
}

var body: some View {
Button(action: { self.showProgress.toggle() }, label: {
if showProgress {
Image(systemName: "arrow.2.circlepath")
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
.animation(self.isAnimating ? foreverAnimation : .default)
.onAppear { self.isAnimating = true }
.onDisappear { self.isAnimating = false }
} else {
Image(systemName: "arrow.2.circlepath")
}
})
.onAppear { self.showProgress = true }
}
}

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
}
}
}
}
}
}

Repeating SwiftUI animation gets choppy over time


Update: Xcode 13.4 / iOS 15.5

As on now it seems drawingGroup does not have such positive effect as was before, because with used .animation(.., value:) animation works properly and with low resource consumption.

demo

Test module on GitHub

Original

Here is a solution - activating Metal by using .drawingGroup and using explicit animation.

Works fine with Xcode 11.4 / iOS 13.4 - tested during 5 mins, CPU load 4-5%

ZStack {

// .. circles here

}.frame(maxWidth: .infinity, maxHeight: .infinity)
.drawingGroup()
.onAppear() {
withAnimation(Animation.linear(duration: 30).repeatForever(autoreverses: false)) {
self.animationPercent = 1
}
}

Note: findings - it looks like implicit animation recreates update stack in this case again and again, so they multiplied, but explicit animation activated only once.

Repeating Action Continuously In SwiftUI

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

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:

Sample Image

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
}
}
}

How to combine rotation and fade-out animation in SwiftUI

You need transition, because view (image in this case) is removed from view hierarchy. And transition is animated by container of removing view.

Note: it is better to link every animation to own switching state to avoid affect on other animations

Here is solution. Tested with Xcode 12.1 / iOS 14.1

struct Indicator: View {

@Binding var shown: Bool
@State private var rotating = false

@ViewBuilder
var body: some View {
VStack {
if shown {
Image("Ring")
.rotationEffect(Angle(degrees: rotating ? 360 : 0))
.animation(Animation.linear(duration: 1).repeatForever(autoreverses: false), value: rotating)
.transition(.opacity)
.onAppear {
self.rotating = true
}
}
}.animation(.default, value: shown)
}
}


Related Topics



Leave a reply



Submit