0

I'm trying to implement CardViews similar to the ones used in the iOS 11 App Store. In order to do so, I'm using a GitHub project (https://github.com/PaoloCuscela/Cards) and tweaked it a bit.

The problem is that when transitioning back from the presented Detail View to the initial view (which is placed inside a TabBarController) the card is drawn in front of the TabBar (see video https://youtu.be/qDb3JoISTdw) which gives the whole transition a kind of 'glitchy' look.

This is the Code of the transitioning class I use:

import UIKit

class Animator: NSObject, UIViewControllerAnimatedTransitioning {


fileprivate var presenting: Bool
fileprivate var velocity = 0.6
var bounceIntensity: CGFloat = 0.07
var card: Card

init(presenting: Bool, from card: Card) {
    self.presenting = presenting
    self.card = card
    super.init()
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

    // Animation Context Setup
    let container = transitionContext.containerView
    let to = transitionContext.viewController(forKey: .to)!
    let from = transitionContext.viewController(forKey: .from)!
    container.addSubview(to.view)
    container.addSubview(from.view)


    guard presenting else {

        // Detail View Controller Dismiss Animations
        card.isPresenting = false

        let detailVC = from as! DetailViewController
        let cardBackgroundFrame = detailVC.scrollView.convert(card.backgroundIV.frame, to: nil)
        let bounce = self.bounceTransform(cardBackgroundFrame, to: card.originalFrame)

        // Blur and fade with completion
        UIView.animate(withDuration: velocity/2, delay: 0, options: .curveEaseOut, animations: {
            detailVC.blurView.alpha = 0

        }, completion: nil)
        UIView.animate(withDuration: velocity, delay: 0, options: .curveEaseOut, animations: {
            detailVC.snap.alpha = 0
            self.card.backgroundIV.layer.cornerRadius = self.card.cardRadius

        }, completion: { _ in

            detailVC.layout(self.card.originalFrame, isPresenting: false, isAnimating: false)
            self.card.addSubview(detailVC.card.backgroundIV)
            transitionContext.completeTransition(true)
        })

        // Layout with bounce effect
        UIView.animate(withDuration: velocity/2, delay: 0, options: .curveEaseOut, animations: {

            detailVC.layout(self.card.originalFrame, isPresenting: false, transform: bounce)
            self.card.delegate?.cardIsHidingDetail?(card: self.card)

        }) { _ in UIView.animate(withDuration: self.velocity/2, delay: 0, options: .curveEaseOut, animations: {

            detailVC.layout(self.card.originalFrame, isPresenting: false)
            self.card.delegate?.cardIsHidingDetail?(card: self.card)
            })
        }
        return

    }

    // Detail View Controller Present Animations
    card.isPresenting = true

    let detailVC = to as! DetailViewController
    let bounce = self.bounceTransform(card.originalFrame, to: card.backgroundIV.frame)

    container.bringSubview(toFront: detailVC.view)
    detailVC.card = card
    detailVC.layout(card.originalFrame, isPresenting: false)

    // Blur and fade with completion
    UIView.animate(withDuration: velocity/2, delay: 0, options: .curveEaseOut, animations: {
        detailVC.blurView.alpha = 1

    }, completion: nil)

    UIView.animate(withDuration: velocity, delay: 0, options: .curveEaseOut, animations: {

        self.card.transform = CGAffineTransform.identity    // Reset card identity after push back on tap
        detailVC.snap.alpha = 1
        self.card.backgroundIV.layer.cornerRadius = 0

    }, completion: { _ in

        detailVC.layout(self.card.originalFrame, isPresenting: true, isAnimating: false, transform: .identity)
        transitionContext.completeTransition(true)
    })

    // Layout with bounce effect
    UIView.animate(withDuration: velocity/2, delay: 0, options: .curveEaseOut, animations: {

        detailVC.layout(detailVC.view.frame, isPresenting: true, transform: bounce)
        self.card.delegate?.cardIsShowingDetail?(card: self.card)

    }) { _ in UIView.animate(withDuration: self.velocity/2, delay: 0, options: .curveEaseOut, animations: {

        detailVC.layout(detailVC.view.frame, isPresenting: true)
        self.card.delegate?.cardIsShowingDetail?(card: self.card)

        })
    }

}

private func bounceTransform(_ from: CGRect, to: CGRect ) -> CGAffineTransform {

    let old = from.center
    let new = to.center

    let xDistance = old.x - new.x
    let yDistance = old.y - new.y

    let xMove = -( xDistance * bounceIntensity )
    let yMove = -( yDistance * bounceIntensity )

    return CGAffineTransform(translationX: xMove, y: yMove)
}


func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return velocity
}

}

I haven't worked with transitioning in iOS and hope someone can tell me how to achieve what I want here.

Medwe
  • 318
  • 1
  • 12

1 Answers1

2

UITabBarController does all of its layout using autoresizing masks. That being the case you can grab the tabBar add it to the container view, perform the animation then add it back to it's root view. For example using the Cards animation you can change the animateTransition(using transitionContext: UIViewControllerContextTransitioning) to:

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

    // Animation Context Setup
    let container = transitionContext.containerView
    let to = transitionContext.viewController(forKey: .to)!
    let from = transitionContext.viewController(forKey: .from)!
    container.addSubview(to.view)
    container.addSubview(from.view)

    // If going to tab bar controller
    // Add tab bar above view controllers
    // Turn off interactions
    if !presenting, let tabController = to as? UITabBarController {
        tabController.tabBar.isUserInteractionEnabled = false
        container.addSubview(tabController.tabBar)
    }


    guard presenting else {

        // Detail View Controller Dismiss Animations
        card.isPresenting = false

        let detailVC = from as! DetailViewController
        let cardBackgroundFrame = detailVC.scrollView.convert(card.backgroundIV.frame, to: nil)
        let bounce = self.bounceTransform(cardBackgroundFrame, to: card.originalFrame)

        // Blur and fade with completion
        UIView.animate(withDuration: velocity, delay: 0, options: .curveEaseOut, animations: {

            detailVC.blurView.alpha = 0
            detailVC.snap.alpha = 0
            self.card.backgroundIV.layer.cornerRadius = self.card.cardRadius

        }, completion: { _ in

            detailVC.layout(self.card.originalFrame, isPresenting: false, isAnimating: false)
            self.card.addSubview(detailVC.card.backgroundIV)

            // Add tab bar back to tab bar controller's root view
            if let tabController = to as? UITabBarController {
                tabController.tabBar.isUserInteractionEnabled = true
                tabController.view.addSubview(tabController.tabBar)
            }
            transitionContext.completeTransition(true)
        })

        // Layout with bounce effect
        UIView.animate(withDuration: velocity/2, delay: 0, options: .curveEaseOut, animations: {

            detailVC.layout(self.card.originalFrame, isPresenting: false, transform: bounce)
            self.card.delegate?.cardIsHidingDetail?(card: self.card)

        }) { _ in UIView.animate(withDuration: self.velocity/2, delay: 0, options: .curveEaseOut, animations: {

            detailVC.layout(self.card.originalFrame, isPresenting: false)
            self.card.delegate?.cardIsHidingDetail?(card: self.card)
            })
        }
        return

    }

    // Detail View Controller Present Animations
    card.isPresenting = true

    let detailVC = to as! DetailViewController
    let bounce = self.bounceTransform(card.originalFrame, to: card.backgroundIV.frame)

    container.bringSubview(toFront: detailVC.view)
    detailVC.card = card
    detailVC.layout(card.originalFrame, isPresenting: false)

    // Blur and fade with completion
    UIView.animate(withDuration: velocity, delay: 0, options: .curveEaseOut, animations: {

        self.card.transform = CGAffineTransform.identity    // Reset card identity after push back on tap
        detailVC.blurView.alpha = 1
        detailVC.snap.alpha = 1
        self.card.backgroundIV.layer.cornerRadius = 0

    }, completion: { _ in

        detailVC.layout(self.card.originalFrame, isPresenting: true, isAnimating: false, transform: .identity)
        transitionContext.completeTransition(true)
    })

    // Layout with bounce effect
    UIView.animate(withDuration: velocity/2, delay: 0, options: .curveEaseOut, animations: {

        detailVC.layout(detailVC.view.frame, isPresenting: true, transform: bounce)
        self.card.delegate?.cardIsShowingDetail?(card: self.card)

    }) { _ in UIView.animate(withDuration: self.velocity/2, delay: 0, options: .curveEaseOut, animations: {

        detailVC.layout(detailVC.view.frame, isPresenting: true)
        self.card.delegate?.cardIsShowingDetail?(card: self.card)

        })
    }

}

Which produces an animation like:

enter image description here

beyowulf
  • 15,101
  • 2
  • 34
  • 40
  • Thank you very much, worked like a charm on the physical device! Somehow didn't change anything on the simulator though, I wonder how that comes? – Medwe Jun 22 '18 at 13:40