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
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
// ... 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
Screen Recording When My iOS App Is in Background with Replaykit
Change Buttonstyle Modifier Based on Light or Dark Mode in Swiftui
Longer Subtitles in Mapview Annotations (Swift)
Format Currency in Textfield in Swift on Input
Modifing One Variable from Another View Controller Swift
Setting Default Tab in Uitabbar in Swift
Uitextview - Adjust Size Based on the Content in Swiftui
How to Get Camera Calibration Data on iOS? Aka Avcameracalibrationdata
How to Make Uiscrollview Zoom in Only One Direction When Using Auto Layout
Get Total Include Months Between 2 Date in Swift
Why Print() Is Printing My String as an Optional
How to Make Custom Keyboard Only for My App in Swift
Swift/iOS Refreshing App Data When in Background
How to Insert a Override Function into a If Else Statement
Target Is Not Found, Please Reconnect the Device, Xcode:Device Support File