0

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.

Nirav Bhatt
  • 6,940
  • 5
  • 45
  • 89

2 Answers2

0

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
         })
    }
Starsky
  • 1,829
  • 18
  • 25
  • I get result like in my first video. Also If I change `(transform, -.pi/2, 0, 1, 0)` on `(transform, .pi, 0, 1, 0)`. –  Jul 16 '18 at 10:19
0

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
}
Nirav Bhatt
  • 6,940
  • 5
  • 45
  • 89
  • If I paste image that I want change in this line `//Put Image Change code here` the image will change immediately when the animation starts, and not at 90 degrees –  Jul 16 '18 at 13:12
  • Can you try this: Instead of setting as view.image, try doing imageview.layer.contents = yourUIImage.cgImage – Nirav Bhatt Jul 16 '18 at 13:15
  • I get same result if using this code `let image = UIImage(named:"0a6752b7cd35fc441c152238ee5078384d--antique-books-rabbit-hole.jpg")` `book1ImageViewI.layer.contents = image?.cgImage` –  Jul 16 '18 at 13:28
  • Updated. Check that I have moved image change to completion block now. – Nirav Bhatt Jul 16 '18 at 13:46
  • It is works. But now animation look like in my first video with delay. In previous your code animation works fine. –  Jul 16 '18 at 13:54
  • Try now: I added beginFromCurrentState option to second animation. – Nirav Bhatt Jul 16 '18 at 14:06