How to stop/cancel a UIProgressView progress animation? - ios

I have a UIProgressView which must be be updated while the user holds a button. For a smooth animation I opted to use UIView.animateWithDuration instead of a Timer to update the progress.
What I expect to achieve is: the progress increases while the user holds a button, reaching 100% in 30 seconds (and then stops). If the user releases the button before the time ends, the progress bar just stops in the current progress. If the user then holds the button again, the progress resets to zero and the same process stars all over again.
Problem is I can't cancel the animation once the user releases the button. I tried to use progressView.layer.removeAllAnimation() but didn't work too.
Bellow is my code:
#IBAction func holdValueChanged(_ sender: CustomButton) {
if sender.isPressed {
progressView.progress = 1.0
UIView.animate(withDuration: 30.0, delay: 0.0, options: UIViewAnimationOptions.curveLinear, animations: {
self.progressView.layoutIfNeeded()
}, completion: { (finished) in
print("Ended progress animation")
})
}
else {
// stop progress animation
progressView.layer.removeAllAnimations()
}
}

Actually this is because there is no guarantee that the layer, that is being animated is progressView.layer. That's why progressView.layer.removeAllAnimations() will not work. Try this approach:
for (CALayer *layer in self.progressBar.layer.sublayers) {
[layer removeAllAnimations];
}

Try creating a second (short) animation from the current state to the desired stop point, e.g., something like this (untested):
UIView.animate(withDuration: 0.1, delay: 0,
options: [ .beginFromCurrentState, .allowUserInteraction ],
animations: {
self.progressView.progress = 1.0; // or whatever state it stopped at
}, completion:nil);
Starting a new animation should cancel any existing ones, and .beginFromCurrentState will prevent it from jumping to the end (in case you want to end at some other point). As a bonus, the visual effect of a short animation may also be nicer than just cancelling.

Related

How to do interactive transition + ending animation?

Normally, we can do an interactive transitioning with animateTransition of UIViewControllerAnimatedTransitioning and updating progress via UIPercentDrivenInteractiveTransition.
Question:
How to have the interactive transitioning at first, then as we pass a certain threshold, perform a different ending animation?
What I want to achieve here is something like dismissing App store's Today card (https://gph.is/2qgcGHd). We can interactively shrink the card by panning a left edge of the screen. Then when it reaches the point, the card animates back to home page without any interactivity. It seems like a combination of interactive + animate transition to me.
What I've tried:
I tried doing this in UIView.animateWithKeyFrames by dividing into two parts of animation with 0.5 relative times for each. Then as the progress reach 0.5, I call finish() (of UIPercentDrivenInteractiveTransition) to have the second animation performing. It has some glitches there and it's like a hack. Want to know if there's a better way to do this.
In the end, I use UIView.animateKeyFramesand dividing the interactive transition into two-part animation (as explained in the question):
let progressUntilDismissing = 0.4
UIView.animateKeyframes(withDuration: 0.5, delay: 0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0,
relativeDuration: progressUntilDismissing,
animations: {
// interactive dismissing animation...
})
UIView.addKeyframe(withRelativeStartTime: progressUntilDismissing,
relativeDuration: (1 - progressUntilDismissing),
animations: {
// closing dismissing animation...
})
}) { (finished) in
//...
}
Then in the pan gesture recognizer, I calculate the pan progress and determine if it passes progressUntilDismissing or not.
If yes, call finish() on UIPercentDrivenInteractiveTransition subclass, it will animate the closing dismissing animation automatically.
In case anyone is curious, this is what I'm playing with:
AppStoreTodayInteractiveTransition

Animate WKInterfaceLabel with text change apple watch swift

I am working on an apple watch application. In the application, I have a view where the user may swipe left and right between 3 given results. I am using WKInterfaceLabel to show result information. On each swipe, labels are updated with new text.
View screenshot:
I want to animate the change of text on swipe. How can I do this?
Any help provided will be appreciated.
Thanks!
This is not very elegant, but it should work:
You can fade out the contents of a WKInterfaceLabel, and fade in another label in its place. So, place 2 WKInterfaceLabel objects at the same place. One of them is visible (alpha = 1.0) and the other invisible (alpha = 0.0).
When you swipe, determine the new value that should be shown, and set it to the invisible label.
Then, animate the transition using the function animate(withDuration:animations:) of the WKInterfaceController. In the animation block, change the alpha values as required, something like
animateWithDuration(1.0) {
self.visibleLabel.setAlpha(0.0)
self.invisibleLabel.setAlpha(1.0)
}
Hope this helps!
try:-
func labelimage(img: UIImageView) {
print(labelrate.hidden)
if (labelrate.hidden) {
UIView.animateWithDuration(0.5, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: {
self.labelrate.alpha = 1
}, completion: nil)
}
else {
UIView.animateWithDuration(0.5, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: {
self.labelrate.alpha = 0
}, completion: nil)
}
self.labelrate.hidden = !self.labelrate.hidden
}

UIview animation misbehaves when image of an unrelated button is changed

On my viewcontroller, I have a button to enable/disable audio using the following action:
func audioControl() {
playMusic = playMusic ? false : true // toggle
if (playMusic) {
startMusic()
audioBtn.setImage(UIImage(named: "musicOn"),for: .normal)
}
else {
stopMusic()
audioBtn.setImage(UIImage(named: "musicOff"),for: .normal)
}
}
The button toggles its image, as expected.
There is also an unrelated UIImageView object on the same VC which is being translated as follows:
UIView.animate(withDuration: 10.0, delay: 0.0, options: [ .curveLinear, .repeat ], animations: {
self.movingImg.center = CGPoint(x: screenSize.minX, y: screenSize.maxY)
})
This simple animation works fine by itself. However, as soon as the button is tapped, the movingImg object goes outside the boundary of screen and it is no longer visible.
I am not sure why a change of image on a button causes another object animation to misbehave. Any thoughts? I am using Swift 3.
Thank you.
After reading through discussions in a number of SO threads, I found that with the image change (along with any other layout changes), the viewcontroller's viewDidLayoutSubviews() gets called. As such, the followings are two things I did to resolve this issue:
Within the audioControl() method, I removed the animation of movingImg (i.e. movingImg.layer.removeAllAnimations()) before updating the button image.
I added viewDidLayoutSubviews() to my viewcontroller and started the animation of movingImg there.
Hopefully, this approach helps others too.

Can I force an element to finish it's animation immediately?

Here I have some code to show a UIView with a label as a notification.
self.not1cons.constant = 0
self.notificationLbl1.text = self.notification1
UIView.animate(withDuration: 2.5, delay: 0.3, options: .allowAnimatedContent, animations: {
self.view.layoutIfNeeded()
}, completion: { finsihed in
self.not1cons.constant = -100
UIView.animate(withDuration: 2.5, delay: 2.0, options: .allowAnimatedContent, animations: {
self.view.layoutIfNeeded()
}, completion: { finshed in
})
})
It start off-screen and descends in to view. It stays in place for a few seconds and returns to its original position off-screen. I need some code to make these chained animations happen instantly. Is this possible?
You could probably accomplish this by manipulating the CAAnimations the system generates behind the scenes, but that is fairly tricky business, and not a great idea since it relies on undocumented details of the current implemention, which is risky.
Instead I'd suggest reworking your animations to use the new-to-iOS 10
UIViewPropertyAnimator, which has support for pausing and resuming animations, as well as scrubbing them back and forth to arbitrary points.
I have a demo project on Gitub called UIViewPropertyAnimator-test that lets you scrub an animation back and forth using a slider. It is more complex than your need, but it should give you the idea.

Adding and removing animation on a UILabel

I am creating a game for english typing. The words fall from the top to the edge of keyboard. For animating the fall of a label I have used:
UIView.animateWithDuration(25, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: {
label.frame = CGRectMake(self.view.frame.width - (label.frame.origin.x+label.frame.width), self.view.frame.height-self.keyboardYPosition!, label.frame.width, width)
}){(success) in
label.removeFromSuperview()
for label in self.currentLabelsArray {
label.layer.removeAllAnimations()
label.removeFromSuperview()
}
self.gameOver()
}
Everything goes fine. The problem I am facing is : I am calling this animation block on creation of every single label. Actually this animation block is in a function called createLabels(). And the createLabel function is called every 5 seconds using NSTimer. Once the game is over I show a view above with restart button. Now here comes the problem:
Before the game ends we might have 3-4 labels already created that are pushed into the animation block. But the very first label might end the game. I get a gameover view above it with a restart button. Once I tap restart, my game again ends because the labels created earlier are still calling the completion block of UIAnimation. I cannot restart the game unless all my labels have completed the animation.
Is there any way to remove animation once the game if completed so that the already created labels no more come into the completion block?
I have used the following code to remove the label from view and remove its animation:
for label in self.currentLabelsArray {
label.layer.removeAllAnimations()
label.removeFromSuperview()
}
A quick guess is that, perhaps you missed to invalidate the timer object.
for label in self.currentLabelsArray {
label.layer.removeAllAnimations()
label.removeFromSuperview()
}
myTimer.invalidate();
So the timer will not call createLabels after the 5 sec. Once the game restarts schedule the time again.
Hope that helps!
EDIT:
Another pointer could be to clear the labels array.
self.currentLabelsArray.removeAllObjects();
and call self.gameOver() iff, there is no request of game over in queue.
EDIT:
UIView.animateWithDuration(25, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: {
label.frame = CGRectMake(self.view.frame.width - (label.frame.origin.x+label.frame.width), self.view.frame.height-self.keyboardYPosition!, label.frame.width, width)
}){(success) in
//label.removeFromSuperview()
for label in self.currentLabelsArray {
label.layer.removeAllAnimations()
label.removeFromSuperview()
}
if(self.currentLabelsArray.count > 0) {
self.currentLabelsArray.removeAllObjects()
self.gameOver()
}
}

Resources