Can't animate UILabels with Swift - ios

I'm trying to animate labels on view. I want to show label 1, then label 2, then remove label2. But label2 is always removed. If I delete the last keyframe block (which "removes" label2), it shows properly).
Here's the code:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let pos1 = self.label1.frame
let pos2 = self.label2.frame
self.label1.frame = CGRect(x: self.view.center.x, y: -10, width: 0, height: 0)
self.label2.frame = CGRect(x: pos2.origin.x, y: self.view.frame.height+10, width: 0, height: 0)
UIView.animateKeyframesWithDuration(1.5, delay: 0.0, options: nil, animations: {
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.4, animations: {
self.label1.frame = pos1
})
UIView.addKeyframeWithRelativeStartTime(0.4, relativeDuration: 0.4, animations: {
self.label2.frame = pos2
})
UIView.addKeyframeWithRelativeStartTime(1.0, relativeDuration: 0.5, animations: {
self.label1.frame = CGRect(x: pos2.origin.x, y: self.view.frame.height + 10, width: 0, height: 0)
})
}, completion: nil)
}

From the reference, the timing parameters in addKeyframeWithRelativeStartTime... are values in the range of 0..1 which are interpreted relative to the entire duration of the animation and NOT a value in seconds. So specifying a keyframe with a start time of 1.0 says to start the animation at the END of the entire animation. Change the start times to 0.0 / 1.5, 0.5 / 1.5, 1.0 / 1.5 and the durations to 0.4 / 1.5 and 0.5 / 1.5.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let pos1 = self.label1.frame
let pos2 = self.label2.frame
self.label1.frame = CGRect(x: self.view.center.x, y: -10, width: 0, height: 0)
self.label2.frame = CGRect(x: pos2.origin.x, y: self.view.frame.height + 10, width: 0, height: 0)
UIView.animateKeyframes(withDuration: 1.5, delay: 0.0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0 / 1.5, relativeDuration: 0.4 / 1.5, animations: {
self.label1.frame = pos1
})
UIView.addKeyframe(withRelativeStartTime: 0.4 / 1.5, relativeDuration: 0.4 / 1.5, animations: {
self.label2.frame = pos2
})
UIView.addKeyframe(withRelativeStartTime: 1.0 / 1.5, relativeDuration: 0.5 / 1.5, animations: {
self.label1.frame = CGRect(x: pos2.origin.x, y: self.view.frame.height + 10, width: pos1.size.width, height: pos1.size.height)
})
}, completion: nil)
}
It seems there are also issues animating the size of views. Only the final size is used (note that in this case you should see the labels start from 0x0 and grow to their final size, but they don't) So setting the size in the final frame to 0x0 makes label1 have that size through the whole animation. Change the code to maintain the size and it seems to work fine. If you really want to animate the size to zero you'll probably need to animate the transform property through scaling.

Related

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()
})
}

iOS swift: change imageview image in the middle of rotation animation

I have an opening book animation: https://streamable.com/n1c0n
And I want to on 90 degrees my image has changed.
I use this code:
var book1ImageViewI : UIImageView
let book2ImageViewI : UIImageView
book1ImageViewI = UIImageView(frame: CGRect( x: self.view.frame.size.width / 2 - 140, y: (self.view.frame.size.height / 2) - ( (self.view.frame.width / 2) / 2 ), width: ( (self.view.frame.width / 2) / 8) * 7, height: self.view.frame.width / 2))
book2ImageViewI = UIImageView(frame: CGRect(x: self.view.frame.size.width / 2 - 140, y: (self.view.frame.size.height / 2) - ( (self.view.frame.width / 2) / 2 ), width: ( (self.view.frame.width / 2) / 8) * 7, height: self.view.frame.width / 2))
book1ImageViewI.image = UIImage(named:"attachment_83090027.jpg")
book2ImageViewI.image = UIImage(named:"0a6752b7cd35fc441c152238ee5078384d--antique-books-rabbit-hole.jpg")
book1ImageViewI.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
book2ImageViewI.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
var transform = CATransform3DIdentity
transform.m34 = 1.0 / -2000.0
book1ImageViewI.layer.transform = transform
UIView.animate(withDuration: 1.5, animations: {
book1ImageViewI.layer.transform = CATransform3DRotate(transform, -.pi/2, 0, 1, 0)
})
{
(bCompleted) in
if (bCompleted) {
book1ImageViewI.layer.transform = CATransform3DRotate(transform, -.pi/2, 0, 1, 0)
}
UIView.animate(withDuration: 1.5, animations: {
book1ImageViewI.layer.transform = CATransform3DRotate(transform, .pi*0.999, 0, 1, 0)
book1ImageViewI.image = UIImage(named:"0a6752b7cd35fc441c1528ee5078384d--antique-books-rabbit-holer.png")
}, completion: {
(bFinished) in
//Whatever
})
}
self.view.addSubview(book2ImageViewI)
self.view.addSubview(book1ImageViewI)
Everything works fine. But on 90 degrees animation slightly delayed.
I want to get animation like this without delay: https://streamable.com/q2bf6
How to do it?
P.S. In second animation use this code:
UIView.animate(withDuration: 3, delay: 0.0,
options: [], animations: {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
book1ImageViewI.image = UIImage(named:"0a6752b7cd35fc441c1528ee5078384d--antique-books-rabbit-holer.png")
}
book1ImageViewI.layer.transform = CATransform3DRotate(transform, .pi, 0, 1, 0)
self.view.layoutIfNeeded()
}, completion: {_ in
})
But this code does not fit. Because images changes not on 90 degrees. Sometimes earlier. Sometimes later.
Apparently, the anchor-point change was quite crucial wherever rotation transform is involved, and it is not straightforward. Every time you change anchorpoint, it alters the UIView position, and we must compensate for this change somehow.
Here is the final version using imageview which works without interruption for me. Apparently, it works with addKeyFrame API as well but for simplicity's sake I kept UIView.animate().
The trick I used is to have smaller duration for first animation and larger one for the second. It almost look like a page flip. You can also play with UIView.animate overrides that provide for UIViewAnimationOptions - such as curveEaseIn and curveEaseOut to make it look like page flip of a book.
It seems to work for me, the only thing is that you can't say it is rotating from the front or back. If your imageview is slant (like the slant book page you show in video), it should be visible and you can simply correct by changing the - to + sign within CATransform3DRotate arguments, or vice versa in animation block.
NOTE:
If it still plays hide and seek, try on real device instead of simulator just in case. There is apparent difference due to GPU execution when it comes to animations.
func pageFlipAnimation()
{
self.myImageView.image = UIImage.init(named: "firstImage")
var transform = CATransform3DIdentity
let transform1 = CATransform3DRotate(transform, -CGFloat(Double.pi)*0.5, 0, 1, 0)
let transform2 = CATransform3DRotate(transform, -CGFloat(Double.pi), 0, 1, 0)
transform.m34 = 1.0 / -5000.0
self.setAnchorPoint(anchorPoint: CGPoint(x: 0, y: 0.5), forView: self.myImageView)
UIView.animate(withDuration: 0.5, animations:
{
self.myImageView.layer.transform = transform1
})
{
(bFinished) in
self.myImageView.image = UIImage.init(named: "secondImage")
UIView.animate(withDuration: 0.75, animations:
{
self.myImageView.layer.transform = transform2
})
}
}
//This should ideally be a UIView Extension
func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView)
{
var newPoint = CGPoint(x:view.bounds.size.width * anchorPoint.x, y:view.bounds.size.height * anchorPoint.y)
var oldPoint = CGPoint(x:view.bounds.size.width * view.layer.anchorPoint.x, y:view.bounds.size.height * view.layer.anchorPoint.y)
newPoint = newPoint.applying(view.transform)
oldPoint = oldPoint.applying(view.transform)
var position = view.layer.position
position.x -= oldPoint.x
position.x += newPoint.x
position.y -= oldPoint.y
position.y += newPoint.y
view.layer.position = position
view.layer.anchorPoint = anchorPoint
}
This part of your code conflicts:
(bCompleted) in
if (bCompleted) {
book1ImageViewI.layer.transform = CATransform3DRotate(transform, -.pi/2, 0, 1, 0)
}
UIView.animate(withDuration: 1.5, animations: {
book1ImageViewI.layer.transform = CATransform3DRotate(transform, .pi*0.999, 0, 1, 0)
book1ImageViewI.image = UIImage(named:"0a6752b7cd35fc441c1528ee5078384d--antique-books-rabbit-holer.png")
}, completion: {
(bFinished) in
//Whatever
})
You set book1ImageViewI.layer.transform = CATransform3DRotate(transform, -.pi/2, 0, 1, 0) at the same time as book1ImageViewI.layer.transform = CATransform3DRotate(transform, .pi*0.999, 0, 1, 0)
Try this instead:
(bCompleted) in
if (bCompleted) {
UIView.animate(withDuration: 1.5, animations: {
book1ImageViewI.layer.transform = CATransform3DRotate(transform, -.pi/2, 0, 1, 0)
book1ImageViewI.image = UIImage(named:"0a6752b7cd35fc441c1528ee5078384d--antique-books-rabbit-holer.png")
}, completion: {
(bFinished) in
//Whatever
})
}

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
}

Combine translation, alpha and scale animations in swift3

I'm totally newbies with iOS Swift developement and i try to combine three parameters in a single animations but i don't succeed.
I think the solution is here -Apple Dev Core Animation Programming Guide by grouping the animations but being a beginner and after a lot of Internet research i can't find what i'm looking for.
What do you think of my code and what is for you the best solution to combine performance and stability.
I want to point out that the purpose of this animation is to create an animated Splashscreen. There are other elements (UIImage) that will be to animates.
Here is my code:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
logoImg.alpha = 0
logoImg.transform = CGAffineTransform(translationX: 0, y: -200)
logoImg.transform = CGAffineTransform(scaleX: 0, y: 0)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 1.0, delay: 1.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 10, options: [.curveEaseOut], animations: {
self.logoImg.transform = CGAffineTransform(translationX: 0, y: 0)
self.logoImg.transform = CGAffineTransform(scaleX: 1, y: 1)
self.logoImg.alpha = 1
}, completion: nil)
}
Based on what I am seeing you are wanting to preset the animation and translate it back. In that case I would do this.
self.logoImg.transform = CGAffineTransform(translationX: 0, y: -200).concatenating(CGAffineTransform(scaleX: 0, y: 0))
self.logoImg.alpha = 0
UIView.animate(withDuration: 1.0, delay: 1.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 10, options: [.curveEaseOut], animations: {
self.logoImg.transform = .identity
self.logoImg.alpha = 1
}, completion: nil)
I think you may not be seeing all the animation so try to start the scale at 0.5
self.logoImg.transform = CGAffineTransform(translationX: 0, y: -200).concatenating(CGAffineTransform(scaleX: 0.5, y: 0.5))
self.logoImg.alpha = 0
UIView.animate(withDuration: 1.0, delay: 1.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 1, options: [.curveEaseOut], animations: {
self.logoImg.transform = .identity
self.logoImg.alpha = 1
}, completion: nil)
The key here is that the animation is animating back the original identity. Hope this helps
You can use concatenating method to combining two existing affine transforms.
UIView.animate(withDuration: 1.0, delay: 1.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 10, options: [.curveEaseOut], animations: {
let translation = CGAffineTransform(translationX: 0, y: 0)
let scale = CGAffineTransform(scaleX: 1, y: 1)
self.logoImg.transform = translation.concatenating(scale)
self.logoImg.alpha = 1
}, completion: nil)
Look at Apple Document for more info. Hope it help. :)
Rotate and Translate and make it like parallax effect in tableview header:
func setupTableHeaderView() {
self.customHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: 200))
self.customHeaderView?.backgroundColor = .white
self.customImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: 200))
self.customImageView?.image = ImageNamed(name: "camera")
self.customImageView?.contentMode = .center
self.customHeaderView?.addSubview(self.customImageView!)
self.tableHeaderView = self.customHeaderView
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let yPos = scrollView.contentOffset.y
if yPos < 0 {
let scaleX = ((yPos * -1) / 200) + 1
let translateY = yPos / 2
var commonTransform = CGAffineTransform.identity
commonTransform = commonTransform.translatedBy(x: 0, y: translateY)
commonTransform = commonTransform.scaledBy(x: scaleX, y: scaleX)
self.customImageView?.transform = commonTransform
}else{
self.customImageView?.transform = CGAffineTransform.identity
}
}
NOTE : Make sure the View you apply transform to is SUBVIEW of the header view, not header view itself. In above example customImageView is subview of main headerview.
You can use this also with swift 3.0.1:
UIView.transition(with: self.imageView,
duration:0.5,
options: .transitionCrossDissolve,
animations: { self.imageView.image = newImage },
completion: nil)
Reference: https://gist.github.com/licvido/bc22343cacfa3a8ccf88

swift UIView animateWithDuration with repeat and autoreverse

I am new to swift and this is my first question ever ....
I would like to shrink a ball with duration 2 seconds, and then grow it for a duration of 5 seconds.
My problem is that the second duration is ignored (ball shrinks for 2 seconds and grows for 2 seconds).
I hope someone can help me.
This is my attempt:
let ball = UIView()
ball.frame = CGRectMake(50, 50, 50, 50)
ball.backgroundColor = UIColor.blueColor()
ball.layer.cornerRadius=25
relaxContainer.addSubview(ball)
UIView.animateWithDuration(2.0, delay:0, options: [.Repeat, .Autoreverse], animations: {
ball.frame = CGRectMake(50, 50, 20, 20)
}, completion: { finished in
UIView.animateWithDuration(5.0, animations: {
ball.frame = CGRectMake(50, 50, 50, 50)
})
})
My Answer thanks to help by Matt (duration times, variables from original question were changed):
Swift 2
let duration = 6.0
let delay = 0.0
UIView.animateKeyframesWithDuration(duration, delay: delay, options: [.Repeat], animations: {
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 1/3, animations: {
ball.frame = CGRectMake(screenWidth/8*3, screenHeight/8*3, screenWidth/4, screenWidth/4)
})
UIView.addKeyframeWithRelativeStartTime(1/3, relativeDuration: 2/3, animations: {
ball.frame = CGRectMake(screenWidth/4, screenHeight/4, screenWidth/2, screenWidth/2)
})
}, completion: nil
)
Swift 3, 4, 5
let duration = 6.0
let delay = 0.0
UIView.animateKeyframes(withDuration: duration, delay: delay, options: [.repeat], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1/3, animations: {
ball.frame = CGRect(x: screenWidth/8*3, y: screenHeight/8*3, width: screenWidth/4, height: screenWidth/4)
})
UIView.addKeyframe(withRelativeStartTime: 1/3, relativeDuration: 2/3, animations: {
ball.frame = CGRect(x: screenWidth/4, y: screenHeight/4, width: screenWidth/2, height: screenWidth/2)
})
}, completion: nil
)

Resources