UIView animate doesn't finish before Label is hidden - ios

My label is disappearing before the animation runs.
The animation should run after a button is pressed and slide out a label and the Button. But when I press the button they are both hidden before the animation can slide them out.
func nextGame() {
UIView.animate(withDuration: 3, delay: 0.0, options: .allowAnimatedContent, animations: {
NSLog("Animation started")
self.labelWinningPlayer.center = CGPoint(x: self.labelWinningPlayer.center.x + 500, y: self.labelWinningPlayer.center.y)
self.buttonNextGameLabel.center = CGPoint(x: self.buttonNextGameLabel.center.x + 500, y: self.buttonNextGameLabel.center.y)
}, completion: { (finished: Bool) in
NSLog("Animation stopped")
self.labelWinningPlayer.isHidden = true
self.buttonNextGameLabel.isHidden = true
})
//sets buttons to start position
labelWinningPlayer.center = CGPoint(x: labelWinningPlayer.center.x - 1000, y: labelWinningPlayer.center.y)
buttonNextGameLabel.center = CGPoint(x: buttonNextGameLabel.center.x - 1000, y: buttonNextGameLabel.center.y)
//hides buttons that lay under the animated buttons
for i in 1...9 {
if let button = view.viewWithTag(i) as? UIButton {
button.setImage(nil, for: .normal)
}
}
activeGame = true
}
//animation should get started with this button
#IBAction func buttenNextGameAction(_ sender: Any) {
nextGame()
}
//slides buttons in
func slideNextGameButtons() {
labelWinningPlayer.isHidden = false
buttonNextGameLabel.isHidden = false
UIView.animate(withDuration: 0.5, animations: {
self.labelWinningPlayer.center = CGPoint(x: self.labelWinningPlayer.center.x + 500, y: self.labelWinningPlayer.center.y)
self.buttonNextGameLabel.center = CGPoint(x: self.buttonNextGameLabel.center.x + 500, y: self.buttonNextGameLabel.center.y)
})
}
Here is the NSLog. According to that it's running the full animation...
2017-11-15 23:26:17.321465 [2442:245232] Animation started
2017-11-15 23:26:20.325137 [2442:245232] Animation stopped
Thanks for your help in advance!

Your code works perfectly:
Therefore something that you have not told us about is causing whatever the problem is.
EDIT: And yes indeed, it was the code you didn't tell us about that causes the problem. You told us about this:
UIView.animate(withDuration: 3, delay: 0.0, options: .allowAnimatedContent, animations: {
self.labelWinningPlayer.center = CGPoint(x: self.labelWinningPlayer.center.x + 500, y: self.labelWinningPlayer.center.y)
self.buttonNextGameLabel.center = CGPoint(x: self.buttonNextGameLabel.center.x + 500, y: self.buttonNextGameLabel.center.y)
}, completion: { (finished: Bool) in
self.labelWinningPlayer.isHidden = true
self.buttonNextGameLabel.isHidden = true
})
But you didn't tell us about the next lines, which are this:
labelWinningPlayer.center = CGPoint(x: labelWinningPlayer.center.x - 1000, y: labelWinningPlayer.center.y)
buttonNextGameLabel.center = CGPoint(x: buttonNextGameLabel.center.x - 1000, y: buttonNextGameLabel.center.y)
Those lines cancel the animation!
It seems you have not understood what an animation is. You animate first, and anything you want done after the animation, you put into the completion function. But those lines are something you want done after the animation. So put them into the completion function! Like this:
UIView.animate(withDuration: 3, delay: 0.0, options: .allowAnimatedContent, animations: {
self.labelWinningPlayer.center = CGPoint(x: self.labelWinningPlayer.center.x + 500, y: self.labelWinningPlayer.center.y)
self.buttonNextGameLabel.center = CGPoint(x: self.buttonNextGameLabel.center.x + 500, y: self.buttonNextGameLabel.center.y)
}, completion: { (finished: Bool) in
self.labelWinningPlayer.isHidden = true
self.buttonNextGameLabel.isHidden = true
labelWinningPlayer.center = CGPoint(x: labelWinningPlayer.center.x - 1000, y: labelWinningPlayer.center.y)
buttonNextGameLabel.center = CGPoint(x: buttonNextGameLabel.center.x - 1000, y: buttonNextGameLabel.center.y)
// ... and everything else in the method goes here too
})
And everything else in the method needs to be moved in there too. Everything that is to happen after the completion of the animation goes into the completion function. That is what completion means!

set the label and the button's super view clipsToBounds property to true
UIView.animate(withDuration: 3, delay: 0.0, options: [], animations: {
NSLog("Animation started")
self.labelWinningPlayer.center = CGPoint(x: (self.labelWinningPlayer.center.x) * 3, y: self.labelWinningPlayer.center.y)
self.buttonNextGameLabel.center = CGPoint(x: (self.buttonNextGameLabel.center.x) * 3, y: self.buttonNextGameLabel.center.y)
}, completion: { (finished: Bool) in
NSLog("Animation stopped")
self.labelWinningPlayer.isHidden = true
self.buttonNextGameLabel.isHidden = true
})

Related

Have User be able to Swipe Down on temporary UIView

I wanted to be able to have the user swipe down to dismiss a temporary Notification that comes in from the bottom.
Here's what the code looks like:
func showAnimationToast(...) {
let toastView = UIView(frame: CGRect(x: 10, y: view.frame.size.height - view.safeAreaInsets.bottom, width: view.frame.size.width - 20, height: 60))
...
toastView.tag = 1474
let animationView = AnimationView(name: animationName)
...
toastView.addSubview(animationView)
let messageLabel = UILabel(frame: CGRect(x: toastView.frame.size.height, y: 5, width: toastView.frame.size.width - toastView.frame.size.height, height: 50))
...
toastView.addSubview(messageLabel)
toastView.isUserInteractionAvailable = true
I tried to add a UISwipeGestureRecognizer to toastView, but it never worked. I even tried the simple UITapGestureRecognizer and it STILL didn't work.
Here's what I tried:
//Let Swipe Down Dismiss. Does not work
let swipeDownGesture = UISwipeGestureRecognizer(target: self, action: #selector(dismissToast(_:)))
swipeDownGesture.direction = .down
toastView.addGestureRecognizer(swipeDownGesture)
//Show animation
UIView.animate(withDuration: 0.2, delay: 0, animations: {
toastView.frame.origin.y = self.view.frame.size.height - self.view.safeAreaInsets.bottom - 70
}, completion: {_ in
animationView.play()
})
//Remove after specified time
UIView.animate(withDuration: 0.2, delay: duration, animations: {
toastView.center.y = self.view.frame.size.height + 50
}, completion: {_ in
toastView.removeFromSuperview()
})
}
#objc func dismissToast(_ sender: UIGestureRecognizer) {
print("dismiss")
let toastView = view.subviews.filter { view in
if view.tag == 1474 /*toastView*/ tag{
return true
}
return false
}.last!
UIView.animate(withDuration: 0.2, delay: 0, animations: {
toastView.center.y = self.view.frame.size.height + 50
}, completion: {_ in
toastView.removeFromSuperview()
})
}
The issue seems to be that while a view is waiting for an animation to play (during the "delay" period), it can't receive user interactions.
One way to work around this is to not use the delay parameter, and instead use DispatchQueue.main.asyncAfter:
UIView.animate(withDuration: 0.2, delay: 0, options: [.allowUserInteraction], animations: {
toastView.frame.origin.y = self.view.frame.size.height - self.view.safeAreaInsets.bottom - 70
}, completion: {_ in
DispatchQueue.main.asyncAfter(deadline: .now() + duration + 0.2) {
UIView.animate(withDuration: 0.2, delay: 0, animations: {
toastView.center.y = self.view.frame.size.height + 50
}, completion: {_ in
toastView.removeFromSuperview()
})
}
})

How to remove delay on start and end of animateKeyframes animation - Swift UIKit

Is there a way to remove the delay on start and end of the animateKeyframes animation?
As you can see there is a slight delay before the animation starts; after tapping on the Animate button and also at the end of the animation. What I would like to be able to do is start the animation as soon as the Animate button is tapped since this is meant to provide feedback to the user.
Is this the normal behavior when using animateKeyframes animations with the calculationModeCubic? Is there a way to make the animation starts as soon as the button is tapped?
Sorry about the misspelling error (Aniamate).
Here is the code:
#IBAction func startAnimation(_ sender: Any) {
addMyView()
UIView.animateKeyframes(withDuration: 3.0, delay: 0.0, options: [.calculationModeCubic], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.2, animations: {
self.myView!.center = CGPoint(x: self.pointA.center.x, y: self.pointA.center.y)
})
UIView.addKeyframe(withRelativeStartTime: 0.2, relativeDuration: 0.2, animations: {
self.myView!.center = CGPoint(x: self.pointB.center.x + 55, y: self.pointB.center.y - 5 )
self.myView!.transform = CGAffineTransform(scaleX: 0.75, y: 0.75)
})
UIView.addKeyframe(withRelativeStartTime: 0.4, relativeDuration: 0.2, animations: {
self.myView!.center = CGPoint(x: self.pointB.center.x, y: self.pointB.center.y)
self.myView!.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
})
}, completion: { _ in
self.myView?.removeFromSuperview()
})
}
func addMyView(){
myView = UIView(frame: CGRect(x: pointA.center.x - 25, y: pointA.center.y - 25, width: 50, height: 50))
myView?.backgroundColor = .blue
myView?.layer.cornerRadius = myView!.frame.height / 2
view.addSubview(myView!)
}
The key is to keep tweaking the withRelativeStartTime: and the relativeDuration: parameters.
#IBAction func startAnimation(_ sender: Any) {
addMyView()
UIView.animateKeyframes(withDuration: 1.5, delay: 0.0, options: [.calculationModeCubic], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.2, animations: {
self.myView!.center = CGPoint(x: self.pointA.center.x, y: self.pointA.center.y)
})
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.9, animations: {
self.myView!.center = CGPoint(x: self.pointB.center.x + 75, y: self.pointB.center.y - 50 )
self.myView!.transform = CGAffineTransform(scaleX: 0.75, y: 0.75)
})
UIView.addKeyframe(withRelativeStartTime: 0.1, relativeDuration: 0.7, animations: {
self.myView!.center = CGPoint(x: self.pointB.center.x, y: self.pointB.center.y)
self.myView!.transform = CGAffineTransform(scaleX: 0.3, y: 0.3)
})
}, completion: { _ in
self.myView?.removeFromSuperview()
})
}

UIView bottom to top animation issue

This is a simple function I am using for animating a view from top to bottom and vice versa (if is top to bottom animation and else is bottom to top animation) :
#objc func openMenu(sender: UIButton) {
if sender.tag == 1 {
self.buttonView.tag = 2
self.moduleView = ModulesCollectionView(frame: CGRect(x: 0, y: self.frame.origin.y + self.frame.size.height + 20, width: UIScreen.main.bounds.size.width, height: 0), collectionViewLayout: UICollectionViewLayout())
self.window?.addSubview(self.moduleView)
UIView.animate(withDuration: 0.7, animations: {
self.moduleView.frame = CGRect(x: 0, y: self.frame.origin.y + self.frame.size.height + 20, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height - self.frame.size.height - 22)
}, completion: { _ in
})
} else {
self.buttonView.tag = 1
UIView.animate(withDuration: 3, animations: {
self.moduleView.frame = CGRect(x: 0, y: self.frame.origin.y + self.frame.size.height + 20, width: UIScreen.main.bounds.size.width, height: 0)
}, completion: { _ in
self.moduleView.removeFromSuperview()
})
}
}
Top animation works fine and the view is animated from top to bottom pleasantly in 0.7 seconds. However, bottom to top animation does not happen. The view is removed instantly. This is the result I am getting :
But I want the animation to be clean while going from bottom to top as well. Just like here.
Secondary : What I finally plan to achieve is PullUpController with the exact reverse animation. So if anyone knows a similar library (pull down drag) can share there inputs.
Update : The issue is coming only with UICollectionView. I replaced collectionView with a simple UIView and it worked perfect.
You should try to use layoutSubViews() method after at the end of animation block. Change the animation block like this.
For if block:
self.moduleView.frame = CGRect(x: 0, y: self.frame.origin.y + self.frame.size.height + 20, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height - self.frame.size.height - 22)
self.moduleView.layoutSubviews()
For else block:
self.moduleView.frame = CGRect(x: 0, y: self.frame.origin.y + self.frame.size.height + 20, width: UIScreen.main.bounds.size.width, height: 0)
self.moduleView.layoutSubviews()
Here is example code, hope it will help.
Show view:
self.containerView = ModulesCollectionView(frame: UIScreen.main.bounds)
self.containerView.center = CGPoint(x: self.view.center.x,
y: self.view.center.y + self.view.frame.size.height)
self.window?.addSubview(self.moduleView)
self.window?.bringSubview(toFront: self.moduleView)
self.window?.endEditing(true)
UIView.animate(withDuration: 0.3, delay: 0.0,
usingSpringWithDamping: 0.7, initialSpringVelocity: 3.0,
options: .allowAnimatedContent, animations: {
self.moduleView.center = self.view.center
}) { (isFinished) in
self.view.layoutIfNeeded()
}
hide view:
UIView.animate(withDuration: 0.7, delay: 0.0,
usingSpringWithDamping: 1, initialSpringVelocity: 1.0,
options: .allowAnimatedContent, animations: {
self.moduleView.center = CGPoint(x: self.view.center.x,
y: self.view.center.y + self.view.frame.size.height)
}) { (isFinished) in
self.moduleView.removeFromSuperview
}

Run two animations simultaneously

I'm trying to create an animation with two views and I've encountered some unexpected behaviors while performing them.
I want to animate both views position while doing a second animation which is transitionFlipFromBottom
Here's the code:
let initialFrame = CGRect(x: xpos, y: -310, width: 300, height: 300)
let firstView = UIView()
firstView.backgroundColor = .red
firstView.frame = initialFrame
let secondView = UIView()
secondView.backgroundColor = .white
secondView.frame = initialFrame
secondView.isHidden = false
self.view.addSubview(firstView)
self.view.addSubview(secondView)
// Here I try to move the views on screen while fliping them
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseOut, animations: {
secondView.center = self.view.center
firstView.center = self.view.center
self.flip(firstView: firstView, secondView: secondView)
}, completion: nil)
// This function flips the views vertically while it animates the transition from the first to the second view
fileprivate func flip(firstView: UIView, secondView: UIView) {
let transitionOptions: UIViewAnimationOptions = [.transitionFlipFromBottom, .showHideTransitionViews]
UIView.transition(with: firstView, duration: 0.5, options: transitionOptions, animations: {
firstView.isHidden = true
})
UIView.transition(with: secondView, duration: 0.5, options: transitionOptions, animations: {
secondView.isHidden = false
})
}
The code above fails to execute both animations at the same time.
It only works if I place the flip function call inside the completion block, after the first animation (moving frame) finishes, as the following:
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseOut, animations: {
secondView.center = self.view.center
firstView.center = self.view.center
}, completion: {(_) in
self.flip(firstView: dummyView, secondView: newGroupView)
})
I have even tried to use UIView.animateKeyframes but it still doesn't work.
Am I missing something here?
Thank you.
A couple of things:
In transition, specify .allowAnimatedContent option.
Defer the animation:
DispatchQueue.main.async {
UIView.animate(withDuration: 1, delay: 0, options: [.curveEaseOut], animations: {
secondView.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY)
firstView.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY)
self.flip(firstView: firstView, secondView: secondView)
}, completion: { _ in
})
}
Somewhat unrelated, you don't want:
secondView.center = self.view.center
Instead, do:
secondView.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY)
You want to set secondView.center in the coordinate space of the bounds of view, not in view's superview.

Move UIButtons back when I click on image in swift

I need to move UIButtons inside the view when I tap on image, and need to move UIButtons out of the screen when I tap on same image again.
When I tap on a UIImage, 4 UIButtons move inside screen with following code without a problem.
func imageTapped(img: AnyObject)
{UIView.animateWithDuration(0.5,
delay: 0,
options: [.CurveEaseInOut, .AllowUserInteraction],
animations: {
self.imageReceivedTrashCan.center = CGPoint(x: 30, y: 530)
self.imageReceivedRightArrow.center = CGPoint(x: 285, y: 530)
self.imageReceivedForward.center = CGPoint(x: 160, y: 530)
self.imageReceivedCross.center = CGPoint(x: 30, y: 30)
},
completion: { finished in
print("Objects moved!")
})
}
But, I couldn't find a way how can I move UIButtons back to original locations when I click on same image again. Appreciate your help.
Even if this seems really old-school, w/o any autolayout and I randomly guessed you don't change the center's elsewhere and all center's at once:
func imageTapped(img: AnyObject) {
if (self.imageReceivedTrashCan.center == CGPoint(x: 30, y: 530)) {
UIView.animateWithDuration(0.5,
delay: 0,
options: [.CurveEaseInOut, .AllowUserInteraction],
animations: {
self.imageReceivedTrashCan.center = CGPoint(x: -30, y: -530)
self.imageReceivedRightArrow.center = CGPoint(x: -285, y: -530)
self.imageReceivedForward.center = CGPoint(x: -160, y: -530)
self.imageReceivedCross.center = CGPoint(x: -30, y: -30)
},
completion: { finished in
print("Objects moved!")
})
} else {
UIView.animateWithDuration(0.5,
delay: 0,
options: [.CurveEaseInOut, .AllowUserInteraction],
animations: {
self.imageReceivedTrashCan.center = CGPoint(x: 30, y: 530)
self.imageReceivedRightArrow.center = CGPoint(x: 285, y: 530)
self.imageReceivedForward.center = CGPoint(x: 160, y: 530)
self.imageReceivedCross.center = CGPoint(x: 30, y: 30)
},
completion: { finished in
print("Objects moved!")
})
}
}
Firstly, to answer your question :
You can save the initial position for each button then on the second tap you call animateWithDuration:delay:options:animations:completion: and set the position to self.imageReceivedTrashCan.center = initialPosition
You also can try with translation methods from CoreGraphics.
UIView.animateWithDuration(0.5,
delay: 0,
options: [.CurveEaseInOut, .AllowUserInteraction],
animations: {
self.imageReceivedTrashCan.transform = CGAffineTransformMakeTranslation(300, 0)
....
},
completion: { finished in
print("Objects moved!")
})
}
Then on the second tap get back to the Identity transform
UIView.animateWithDuration(0.5,
delay: 0,
options: [.CurveEaseInOut, .AllowUserInteraction],
animations: {
self.imageReceivedTrashCan.transform = CGAffineTransformIdentity
....
},
completion: { finished in
print("Objects moved!")
})
}
But this method is hard coded, you should use autoLayout to make this kind of animations. Look at here
Hope this help you ;)
Select imageView, goto attribute inspector and Check user interaction enabled
UITapGestureRecognizer *TapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(moveMyView:)];
[imageview addGestureRecognizer: TapRecognizer];
- (void) moveMyView:(UITapGestureRecognizer*)sender {
[UIView animateWithDuration:1.0 delay:0.0 options:
UIViewAnimationOptionCurveEaseIn animations:^{
self.imageReceivedTrashCan.center = CGPoint(x: -30, y: -530)
self.imageReceivedRightArrow.center = CGPoint(x: -285, y: -530)
self.imageReceivedForward.center = CGPoint(x: -160, y: -530)
self.imageReceivedCross.center = CGPoint(x: -30, y: -30)
} completion:^ (BOOL completed) {}];
}];
}

Resources