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

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

Related

Animation with rotating image

I want to create animation with a rotating image. I have two variants of code.
First
I want to create animation like on this video:
https://yadi.sk/i/ek-3Pydc3ZfZFW - this animation fits me.
I use this code to create animation:
func animation() {
self.book1ImageView = UIImageView(frame: CGRect(x: self.view.frame.size.width / 2 - 120, 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))
self.book2ImageView = UIImageView(frame: CGRect(x: self.view.frame.size.width / 2 - 120, 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))
book1ImageView.image = UIImage(named:"attachment_83090027.jpg")
book2ImageView.image = UIImage(named:"2.jpg")
book1ImageView?.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
book2ImageView?.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
var transform = CATransform3DIdentity
transform.m34 = 1.0 / -2000.0
book1ImageView?.layer.transform = transform
UIView.animate(withDuration: 3, delay: 0.0,
options: [], animations: {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
self.book1ImageView.image = UIImage(named:"2.jpg")
}
self.book1ImageView.layer.transform = CATransform3DRotate(transform, .pi, 0, 1, 0)
self.view.layoutIfNeeded()
}, completion: {_ in
})
self.view.addSubview(self.book2ImageView)
self.view.addSubview(self.book1ImageView)
}
My image should changes in 90 degrees like in firs video. But sometimes my image changes before or after 90 degrees. like in this video: https://yadi.sk/i/MACrcIhM3ZfaKH
Also I have second variant of code:
Second
But this code not fits me because image rotating with delay on 90 degrees.
video: https://yadi.sk/i/_gNG8FBA3ZfaUe
code:
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
}
How to solve the problem?
The second one is the right idea, because you divide it into two stages: you open to 90 degrees, you change the image, and then you open it the rest of the way. So that is the one I would suggest fixing.
The reason for the delay is merely that you have not changed the timing function, so you get the default: an EaseInOut to 90 degrees and then another EaseInOut from 90 degrees to 180 degrees. Thus we slow down to zero and start up again at 90 degrees.
Just give the first animation an EaseIn timing function and give the second animation an EaseOut timing function and you should be all set.

Swift: change UIImageView image midway during animation

I have this code to create animation imageView:
imageView.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
var transform = CATransform3DIdentity
transform.m34 = -0.001
imageView.layer.transform = transform
UIView.animate(withDuration: 3) {
self.imageView.layer.transform = CATransform3DRotate(transform, .pi, 0, 1, 0)
self.imageViewCenterConstraintX.constant = 0
self.view.layoutIfNeeded()
}
In this code imageView rotate with 180 degrees. I want to change image after imageView rotate with 90 degrees.
You should chain the two animations, each rotating with pi/2.
imageView.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
var transform = CATransform3DIdentity
transform.m34 = -0.001
imageView.layer.transform = transform
UIView.animate(withDuration: 1.0, animations:
{
self.imageView.layer.transform = CATransform3DRotate(transform, .pi/2, 0, 1, 0)
self.imageViewCenterConstraintX.constant = 0
})
{
(bCompleted) in
if (bCompleted)
{
self.imageView?.layer.transform = CATransform3DRotate(transform, .pi/2, 0, 1, 0)
}
UIView.animate(withDuration: 1.0, animations:
{
self.imageView.layer.transform = CATransform3DRotate(transform, .pi*0.999, 0, 1, 0)
self.imageView.image = UIImage.init(named:"anotherImage")
},
completion:
{
(bFinished) in
//Whatever
})
}
Use the one below. Use code to change image in the completion handler field.
UIView Animation with completion
You could use DispatchQueue.main.asyncAfter.
UIView.animate(withDuration: 3) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
// Change your image here
}
self.imageView.layer.transform = CATransform3DRotate(transform, .pi, 0, 1, 0)
self.imageViewCenterConstraintX.constant = 0
self.view.layoutIfNeeded()
}

Weird behaviour CGAffineTransform

I try to animate an UILabel:
let label: UILabel = UILabel()
var transform = CGAffineTransform.identity
UIView.animate(withDuration: 2, animations: {
self.transform = self.transform.translatedBy(x: 0, y: -150)
self.transform = self.transform.scaledBy(x: 2, y: 2)
self.label.transform = self.transform
})
It works well. I have a button and I added a selector so that when it is pressed, the label animates again:
func performSearch() {
UIView.animate(withDuration: 2, animations: {
self.transform = self.transform.translatedBy(x: 0, y: -300)
self.label.transform = self.transform
})
}
But what it actually does is to scale down the label as in the original state, move it down to the bottom of the screen, then animate, although all I want it to do is to move up. Why?
Transforms can be a little confusing... Because you have scaled the transform, further transforms seem to be "auto-adjusted" by the scaling, but not in a really intuitive or obvious way.
This may help you get further:
// initial animation
UIView.animate(withDuration: 2, animations: {
self.transform = self.transform.translatedBy(x: 0, y: -150)
self.transform = self.transform.scaledBy(x: 2, y: 2)
self.label.transform = self.transform
})
// "resuming" animation
func performSearch() {
let distance: CGFloat = -300 / 2
self.transform = self.transform.translatedBy(x: 0, y: distance)
self.label.transform = self.transform
UIView.animate(withDuration: 2, animations: {
self.transform = self.transform.translatedBy(x: 0, y: distance)
self.label.transform = self.transform
})
}
You can flip the coordinate system to behave like iOS like this...
layer.sublayerTransform = CATransform3DMakeScale(1.0f, -1.0f, 1.0f);
If you want to move it up in performSearch() you need to use a positive y value:
self.transform = self.transform.translatedBy(x: 0, y: 300)

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

Can't animate UILabels with Swift

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.

Resources