Xcode wait for animation to finish before executing next task - swift

I have a layer that moves up when I swipe up and then the same layer moves down when I swipe down.
When I swipe up, I don’t want the user to be able to swipe down to activate that animation until the swipe up animation is complete and vice-versa.
How do I accomplish this? I’ve tried disabling the swipe gesture using “isEnabled” but no dice. Some other similar questions have answers that are from very long ago and the syntax is very different. I’m using the latest version of xcode.

This issue could be solved by adding boolean variable, IF Statement along with a DispatchQueue function to prevent another animation from occurring until the current animation has finished.
import UIKit
var animation_active = false
let animation_duration = 2 //For easy maintenance
func swipe_down() {
if animation_active == false {
animation_active == true
//Animation code goes here. Variable 'animation_active' should be used when performing the animation for easy matinence
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(animation_duration), execute: {
animation_active = false
})
}
}
func swipe_up() { //This is exactly the same as swipe_up. Yet it will prevent the swipe animation from occuring when the swipe down animation is occuruing thanks to the variable 'animation_active'
if animation_active == false {
animation_active == true
//Animation code goes here. Variable 'animation_active' should be used when performing the animation for easy matinence
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(animation_duration), execute: {
animation_active = false
})
}
}

Related

How to fix performance issue while loading new animation in Lottie?

I am trying to make a simple game with Xcode 11.2 which contains an animated loop background and a view which shows and changes various animations from several Lottie JSON files in project.
When I click the "Next" button to change the view animation, background view which is looping gets stuck for a second until the next animation inside the view is loaded an everything in the app freezes at the same time just like the background animation.
CPU usage varies from 30% to 63%.
I don't like to complicate the question, so I am just showing the way I used Lottie.
#IBAction func SubmitButtonAction(_ sender: UIButton) {
showNextQuestion()
}
func showNextQuestion()->(){
myTimer.invalidate()
startCountdown(fromTime: 15)
Manager.generateQuestion()
lblLevel.text = String(Manager.questionNumber) + "/" + String(DataModel.Questions.count)
nIndex = 0
let animation = AnimationView(name: Manager.currentImage)
animation.loopMode = .loop
animation.play()
animation.backgroundColor = UIColor.clear
animation.frame = self.AnimView.bounds
animation.backgroundBehavior = .pauseAndRestore
if AnimView.subviews.isEmpty{
self.AnimView.addSubview(animation)
}
else {
for one in imgImageView.subviews{
one.removeFromSuperview()
}
self.AnimView.addSubview(animation)
}
AnswerCollectionView.reloadData()
RandomCollectionView.reloadData()
}
I am interested in suggestions as to what the problem is related to - could it be related to a threading issue?
In response to Jaseel.Dev, I created a function that returned a LottieView with the following:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
withAnimation(.spring()) {
LottieView(name: animation)
}
}

Keep particles in scene paused after app becomes active

Let me preface this question by saying I know it has been asked a few times, but the most upvoted post here was from back in 2014 and the top answer doesn't seem to work anymore.
Issue
In my game, I have all my 'moving' nodes as children of pauseNode. When the pauses button is pressed or when the player leaves the app, pauseScene() is called:
func pauseScene() {
self.node.speed = 0
self.physicsWorld.speed = 0
self.node.isPaused = true
}
This pauses all sprite nodes and emitter nodes. This keeps the sprite nodes paused if the app transitions from background to foreground state, but the particle emitters seem to resume animating.
Current solution
I solved this issue by triggering pauseScene() after a small delay when the app became active.
override func didMove(to view: SKView) {
NotificationCenter.default.addObserver(self, selector: #selector(GameScene.applicationDidBecomeActive(notification:)), name: NSNotification.Name.UIApplicationDidBecomeActive, object: app)
}
#objc func applicationDidBecomeActive(notification: NSNotification) {
NSDelay(0.01) {
pauseScene()
}
}
func NSDelay(_ delay:Double, closure:#escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
Goal
While this solution pauses the particles, it is not perfect because they are visible moving for the split second it takes to pause them again (this is the shortest delay that actually re-pauses them).
So, after reading the post I linked above, I tried to override applicationDidBecomeActive() in an SKView subclass as was suggested, but I couldn't get the method to be called. I want to prevent these particles from being unpaused in the first place, if possible. Thanks
So, it's been a little over a day and I've found a better solution. A comment in this post suggested having a variable that recorded the status of the scene, and updating it along with the pause/play functions:
var sceneIsPaused = false
func pauseScene() {
self.node.speed = 0
self.physicsWorld.speed = 0
self.node.isPaused = true
sceneIsPaused = true
}
func playScene() {
self.node.speed = 1
self.physicsWorld.speed = 1
self.node.isPaused = false
sceneIsPaused = false
}
Then, override SKView's update method and check the state of that variable. If the scene should be paused, pause it. This will mean that if the scene is automatically unpaused it will re-pause the next frame. This is much faster and cleaner than setting a delay:
override func update(_ currentTime: TimeInterval) {
if (sceneIsPaused == true) {
self.node.speed = 0
self.physicsWorld.speed = 0
self.node.isPaused = true
}
}

My UIImageView does not animate across the screen until it reaches the end

I am trying to bring the ball from one end of the screen to the other when the play button is clicked. My code does not redraw the ball until the ball reaches the right side of the screen.
You should enclose your code to move your ball inside a UIView.animate block.
UIView.animate(with: 1, animations: {
//Code which animates your view
})
And do not use sleep if you are meaning for the code to run after the 1 second animation. Use completion block inside UIView.animate.
UIView.animate(with: 1, animations: {
ball.center.x += balllVelocity
}, completion: (finished) in
if finished {
ball.center.x > view.frame.size.width {
gameRunning = false
}
}
}
There are lots of things you can do with UIView.animate, i suggest you go through the documentation to improve your animations.

UIViewPropertyAnimator AutoLayout Completion Issue

I'm using UIViewPropertyAnimator to run an array interactive animations, and one issue I'm having is that whenever the I reverse the animations I can't run the animations back forward again.
I'm using three functions to handle the animations in conjunction with a pan gesture recognizer.
private var runningAnimations = [UIViewPropertyAnimator]()
private func startInteractiveTransition(gestureRecognizer: UIPanGestureRecognizer, state: ForegroundState, duration: TimeInterval) {
if runningAnimations.isEmpty {
animateTransitionIfNeeded(gestureRecognizer: gestureRecognizer, state: state, duration: duration)
}
for animator in runningAnimations {
animator.pauseAnimation()
animationProgressWhenInterrupted = animator.fractionComplete
}
}
private func animateTransitionIfNeeded(gestureRecognizer: UIPanGestureRecognizer, state: ForegroundState, duration: TimeInterval) {
guard runningAnimations.isEmpty else {
return
}
let frameAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 1) {
switch state {
case .expanded:
// change frame
case .collapsed:
// change frame
}
}
frameAnimator.isReversed = false
frameAnimator.addCompletion { _ in
print("remove all animations")
self.runningAnimations.removeAll()
}
self.runningAnimations.append(frameAnimator)
for animator in runningAnimations {
animator.startAnimation()
}
}
private func updateInteractiveTransition(gestureRecognizer: UIPanGestureRecognizer, fractionComplete: CGFloat) {
if runningAnimations.isEmpty {
print("empty")
}
for animator in runningAnimations {
animator.fractionComplete = fractionComplete + animationProgressWhenInterrupted
}
}
What I've noticed is after I reverse the animations and then call animateTransitionIfNeeded, frameAnimator is appended to running animations however when I call updateInteractiveTransition immediately after and check runningAnimations, it's empty.
So I'm led to believe that this may have to do with how swift handles memory possibly or how UIViewAnimating completes animations.
Any suggestions?
I've come to realize the issue I was having the result of how UIViewPropertyAnimator handles layout constraints upon reversal.
I couldn't find much detail on it online or in the official documentation, but I did find this which helped a lot.
Animator just animates views into new frames. However, reversed or not, the new constraints still hold regardless of whether you reversed the animator or not. Therefore after the animator finishes, if later autolayout again lays out views, I would expect the views to go into places set by currently active constraints. Simply said: The animator animates frame changes, but not constraints themselves. That means reversing animator reverses frames, but it does not reverse constraints - as soon as autolayout does another layout cycle, they will be again applied.
Like normal you set your constraints and call view.layoutIfNeeded()
animator = UIViewPropertyAnimator(duration: duration, dampingRatio: 1) {
[unowned self] in
switch state {
case .expanded:
self.constraintA.isActive = false
self.constraintB.isActive = true
self.view.layoutIfNeeded()
case .collapsed:
self.constraintB.isActive = false
self.constraintA.isActive = true
self.view.layoutIfNeeded()
}
}
And now, since our animator has the ability to reverse, we add a completion handler to ensure that the correct constraints are active upon completion by using the finishing position.
animator.addCompletion { [weak self] (position) in
if position == .start {
switch state {
case .collapsed:
self?.constraintA.isActive = false
self?.constraintB.isActive = true
self?.view.layoutIfNeeded()
case .expanded:
self?.constraintA.isActive = false
self?.constraintB.isActive = true
self?.view.layoutIfNeeded()
}
}
}
The animator operates on animatable properties of views, such as the frame, center, alpha, and transform properties, creating the needed animations from the blocks you provide.
This is the crucial part of the documentation.
You can properly animate:
frame, center, alpha and transform, so you would not be able to animate properly NSConstraints.
You should modify frames of views inside of addAnimations block

Stopping an running SKAction - Sprite Kit

The following code will animate a rotation.
let something:SKSpriteNode = SKSpriteNode()
func start(){
let rotateAction = SKAction.rotateToAngle(CGFloat(M_PI), duration: 10.0)
something.runAction(SKAction.sequence([rotateAction]))
}
Now I want to stop the animation within the animating duration. Is there anything similar to the following? I want to stop the animation when the user touches the screen.
func stop(){
something.SKAction.stop()
}
You just have to use either:
something.paused = false // or true to pause actions on the node
something.removeAllActions() to definitely remove actions associated to the node
name your action when launching something.runAction(action,withKey:"action1") and then something.removeActionForKey("action1") to remove a given action
You may also change the speed if needed.
Firstly, run the action with a key so you can identify it later:
something.runAction(rotateAction, withKey: "rotate action")
Then you can stop it later by calling
something.removeActionForKey("rotate action")
Alternatively, you can remove all actions also
something.removeAllActions()