Having Easein Animation and Then Scale Animation in a Sequence as the Scale Animation Repeats Forever

Having easeIn animation and then scale animation in a sequence as the scale animation repeats forever

You can join animation with value, like below

.scaleEffect(animationAmount)
.animation(Animation.easeInOut(duration: 1).repeatForever(autoreverses: true), value: animationAmount)
.onAppear {
self.animationAmount = 1.5
}

moreover, as now animation and state joined uniquely and will not conflict with other animations/states, you can move last two modifiers to the very top container (instead of repeating them three times)

For example here:

}//VStack for 3 criterias
.padding([.leading, .trailing], 20)
.animation(Animation.easeInOut(duration: 1).repeatForever(autoreverses: true), value: animationAmount)
.onAppear {
self.animationAmount = 1.5
}

Animation works the first time, but then

Probably you wanted this

Button(action: {
withAnimation(.linear(duration: 3)){
self.pressed.toggle() // << here !!
}
print("B")
}) {
...

SwiftUI: Stop an Animation that Repeats Forever

I figured it out!

An animation using .repeatForever() will not stop if you replace the animation with nil. It WILL stop if you replace it with the same animation but without .repeatForever(). ( Or alternatively with any other animation that comes to a stop, so you could use a linear animation with a duration of 0 to get a IMMEDIATE stop)

In other words, this will NOT work: .animation(active ? Animation.default.repeatForever() : nil)

But this DOES work: .animation(active ? Animation.default.repeatForever() : Animation.default)

In order to make this more readable and easy to use, I put it into an extension that you can use like this: .animation(Animation.default.repeat(while: active))

Here is an interactive example using my extension you can use with live previews to test it out:

import SwiftUI

extension Animation {
func `repeat`(while expression: Bool, autoreverses: Bool = true) -> Animation {
if expression {
return self.repeatForever(autoreverses: autoreverses)
} else {
return self
}
}
}

struct TheSolution: View {
@State var active: Bool = false
var body: some View {
Circle()
.scaleEffect( active ? 1.08: 1)
.animation(Animation.default.repeat(while: active))
.frame(width: 100, height: 100)
.onTapGesture {
self.active.toggle()
}
}
}

struct TheSolution_Previews: PreviewProvider {
static var previews: some View {
TheSolution()
}
}

As far as I have been able to tell, once you assign the animation, it will not ever go away until your View comes to a complete stop. So if you have a .default animation that is set to repeat forever and auto reverse and then you assign a linear animation with a duration of 4, you will notice that the default repeating animation is still going, but it's movements are getting slower until it stops completely at the end of our 4 seconds. So we are animating our default animation to a stop through a linear animation.

How to STOP Animation().repeatForever in SwiftUI

Update - retested with Xcode 13.4 / iOS 15.5

Sample Image

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

How to have a move up animation like Apple's stocks app?

Try this -

import SwiftUI

struct ContentView: View {
@State private var offsetY: CGFloat = 150
@State private var opacityAmount: Double = 0
@State private var animationAmount: CGFloat = 1
var body: some View {
VStack {
HStack {
Image(systemName: "sparkle").foregroundColor(.yellow)
.offset(y: offsetY)
.opacity(opacityAmount)
.animation(
Animation.easeOut(duration: 0.5).delay(0.1)
)
.scaleEffect(animationAmount)
.animation(Animation.easeInOut(duration: 1).repeatForever(autoreverses: true))
Text("All new design").font(.largeTitle)
.offset(y: offsetY)
.opacity(opacityAmount)
.animation(
Animation.easeOut(duration: 0.5).delay(0.1)
)
}

Text("Flexible").font(.largeTitle)
.offset(y: offsetY)
.opacity(opacityAmount)
.animation(
Animation.easeOut(duration: 0.5).delay(0.2)
)
.onAppear {
offsetY = 0
opacityAmount = 0.8
animationAmount = 1.5
}
Spacer()
}
}
}

how repeat this animation forever

put infinite on animation value

animation: sample 3s ease-in backwards infinite;

but you have to sequence your animation delay for each element. :D

animation-delay: 10s;

Remove SKAction and restore node state

Thanks to the helpful comment and answer I came to my own solution. I think the state machine would be bit too heavy here. Instead I created a wrapper node, which main purpose is run the animation. It also has a state: isAimating property. But, first of all, it allows to keep startAnimating() and stopAnimating() methods close to each other, incapsulated, so it's more difficult to mess up.

class ShowMoveAnimNode: SKNode {
let animKey = "showMove"

var isAnimating: Bool = false {
didSet {
guard oldValue != isAnimating else { return }
if isAnimating {
startAnimating()
} else {
stopAnimating()
}
}
}

private func startAnimating() {
let shortPeriod = 0.2
let scaleDown = SKAction.scale(by: 0.75, duration: shortPeriod)
let seq = SKAction.sequence([scaleDown,
scaleDown.reversed(),
scaleDown,
scaleDown.reversed(),
SKAction.wait(forDuration: shortPeriod * 6)])
let repeated = SKAction.repeatForever(seq)
run(repeated, withKey: animKey)
}

private func stopAnimating() {
removeAction(forKey: animKey)
xScale = 1
yScale = 1
}
}

Usage: just add everything that should be animated to this node. Works well with simple animations, like: fade, scale and move.

SwiftUI - in sheet have a fixed continue button that is not scrollable

Here is a demo of possible approach (tuning & effects are out of scope - try to make demo code short). The idea is to inject UIView holder with button above sheet so it persist during sheet drag down (because as findings shown any dynamic offsets gives some ugly undesired shaking effects).

Tested with Xcode 12 / iOS 14

demo

            // ... your above code here

}//VStack for 3 criterias
.padding([.leading, .trailing], 20)

Spacer()

// button moved from here into below background view !!

}.background(BottomView(presentation: presentationMode) {
Button {
presentationMode.wrappedValue.dismiss()
UserDefaults.standard.set(true, forKey: "LaunchedBefore")
} label: {
Text("Continue")
.fontWeight(.medium)
.padding([.top, .bottom], 15)
.padding([.leading, .trailing], 90)
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(15)
}
})
//Main VStack
}
}

struct BottomView<Content: View>: UIViewRepresentable {
@Binding var presentationMode: PresentationMode
private var content: () -> Content

init(presentation: Binding<PresentationMode>, @ViewBuilder _ content: @escaping () -> Content) {
_presentationMode = presentation
self.content = content
}

func makeUIView(context: Context) -> UIView {
let view = UIView()

DispatchQueue.main.async {
if let window = view.window {
let holder = UIView()
context.coordinator.holder = holder

// simple demo background to make it visible
holder.layer.backgroundColor = UIColor.gray.withAlphaComponent(0.5).cgColor

holder.translatesAutoresizingMaskIntoConstraints = false

window.addSubview(holder)
holder.heightAnchor.constraint(equalToConstant: 140).isActive = true
holder.bottomAnchor.constraint(equalTo: window.bottomAnchor, constant: 0).isActive = true
holder.leadingAnchor.constraint(equalTo: window.leadingAnchor, constant: 0).isActive = true
holder.trailingAnchor.constraint(equalTo: window.trailingAnchor, constant: 0).isActive = true

if let contentView = UIHostingController(rootView: content()).view {
contentView.backgroundColor = UIColor.clear
contentView.translatesAutoresizingMaskIntoConstraints = false
holder.addSubview(contentView)

contentView.topAnchor.constraint(equalTo: holder.topAnchor, constant: 0).isActive = true
contentView.bottomAnchor.constraint(equalTo: holder.bottomAnchor, constant: 0).isActive = true
contentView.leadingAnchor.constraint(equalTo: holder.leadingAnchor, constant: 0).isActive = true
contentView.trailingAnchor.constraint(equalTo: holder.trailingAnchor, constant: 0).isActive = true
}
}
}
return view
}

func updateUIView(_ uiView: UIView, context: Context) {
if !presentationMode.isPresented {
context.coordinator.holder.removeFromSuperview()
}
}

func makeCoordinator() -> Coordinator {
Coordinator()
}

class Coordinator {
var holder: UIView!

deinit {
holder.removeFromSuperview()
}
}
}


Related Topics



Leave a reply



Submit