Search code examples
iosswiftuikitcgaffinetransformcgaffinetransformscale

CGAffineTransform.identity doesn't reset transform correctly after device rotation


I am doing a custom transition and if after present animation, device will be rotated and then destinationVC will be dismissed, originVC transform is not correct (not fulfil screen). If there is no device rotation, everything works perfectly fine. Does any one can help me?

Here is my code for present and dismiss animation:

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let originViewController = transitionContext.viewController(forKey: .from),
            let destinationViewController = transitionContext.viewController(forKey: .to) else { return }

        destinationViewController.view.transform = CGAffineTransform(translationX: 0, y: destinationViewController.view.frame.height)
        let duration = transitionDuration(using: transitionContext)

        UIView.animate(withDuration: duration, animations: {
            destinationViewController.view.transform = CGAffineTransform(translationX: 0, y: 0)
            originViewController.view.transform = originViewController.view.transform.scaledBy(x: 0.95, y: 0.95)
            originViewController.view.layer.cornerRadius = 8.0
        }, completion: { completed in            
            transitionContext.completeTransition(completed)
        })
    }

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let originViewController = transitionContext.viewController(forKey: .from),
            let destinationViewController = transitionContext.viewController(forKey: .to) else { return }

        let duration = transitionDuration(using: transitionContext)

        UIView.animate(withDuration: duration, animations: {
            originViewController.view.transform = CGAffineTransform(translationX: 0, y: destinationViewController.view.frame.height)
            destinationViewController.view.transform = CGAffineTransform.identity
            destinationViewController.view.layer.cornerRadius = 0.0
        }, completion: { completed in
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        })
    }

Screens: Before present animation Before present animation After present animation After present animation After device rotation After device rotation After dismiss animation After dismiss animation

EDIT: when I add destinationViewController.view.frame = transitionContext.finalFrame(for: destinationViewController) to dismiss animation everything seems works fine but I don't know if this is right way


Solution

  • Add a subView in ViewC1 with leading, top, bottom, trailing constraints. Working full code

    class ViewC1: UIViewController {
    
        @IBAction func presentNow(_ sender: Any) {
            let viewc = storyboard!.instantiateViewController(withIdentifier: "ViewC2") as! ViewC2
            viewc.transitioningDelegate = viewc
            viewc.modalPresentationStyle = .overCurrentContext
            present(viewc, animated: true, completion: nil)
        }
    
    }
    
    class ViewC2: UIViewController {
    
        let pres = PresentAnimator()
        let diss = DismissAnimator()
    
        @IBAction func dissmisNow(_ sender: Any) {
            dismiss(animated: true, completion: nil)
        }
    }
    
    extension ViewC2: UIViewControllerTransitioningDelegate {
        func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            return pres
        }
    
        func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            return diss
        }
    }
    
    class PresentAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    
        func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
            return 1.0
        }
    
        func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
            guard let originViewController = transitionContext.viewController(forKey: .from),
                let destinationViewController = transitionContext.viewController(forKey: .to) else { return }
    
            transitionContext.containerView.addSubview(destinationViewController.view)
            destinationViewController.view.frame = transitionContext.containerView.bounds
    
            let originView = originViewController.view.subviews.first
    
            destinationViewController.view.transform = CGAffineTransform(translationX: 0, y: destinationViewController.view.frame.height)
            let duration = transitionDuration(using: transitionContext)
    
            UIView.animate(withDuration: duration, animations: {
                destinationViewController.view.transform = CGAffineTransform(translationX: 0, y: 0)
                originView?.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
                originView?.layer.cornerRadius = 8.0
            }, completion: { completed in
                transitionContext.completeTransition(completed)
            })
        }
    }
    
    class DismissAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    
        func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
            return 1.0
        }
    
        func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
            guard let originViewController = transitionContext.viewController(forKey: .from),
                let destinationViewController = transitionContext.viewController(forKey: .to) else { return }
    
            let originView = destinationViewController.view.subviews.first
            let duration = transitionDuration(using: transitionContext)
    
            UIView.animate(withDuration: duration, animations: {
                originViewController.view.transform = CGAffineTransform(translationX: 0, y: destinationViewController.view.frame.height)
                originView?.transform = CGAffineTransform.identity
                originView?.layer.cornerRadius = 0.0
            }, completion: { completed in
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            })
        }
    }
    

    enter image description here

    Update: or you can override willRotate and didRotate if you dont want to use view.subviews.first

    var prevTrans: CGAffineTransform?
    override func willRotate(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
        prevTrans = view.transform
        view.transform = CGAffineTransform.identity
    }
    
    override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
        if let prevTrans = prevTrans {
            view.transform = prevTrans
        }
    }