swiftcore-animationuiviewanimationcgaffinetransformuiviewanimationtransition

CGAffineTransform messing up ViewFrame


I have an UIViewControllerAnimatedTransitioning where one of the animations is an scale using CGAffineTransform

toView.frame.origin.y = containerView.frame.size.height - toView.frame.height
fromView.transform = CGAffineTransform(scaleX: 0.96, y: 0.94)
fromView.frame = fromView.frame.offsetBy(dx: 0, dy: self.topOffset)

This get my frame from (0.0, 0.0, 375.0, 667.0) to (7.5, 20.00999999999999, 360.0, 626.98), and on the way back:

toView.transform = CGAffineTransform.identity
toView.frame = toView.frame.offsetBy(dx: 0, dy: -self.topOffset)
fromView.frame.origin.y = containerView.frame.size.height

The problem is: instead of the frame come back to the original, it messes up and now I get: 0.0, -5.684341886080802e-14, 375.0, 667.0000000000001), the main problem of course is the y positioning.

Mind that the value self.topOffset is 0, and I've tried to remove this line, without any result.

And the fromView from the first snippet is the toView from the second.

If you want to check the full code of that function:

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let fromViewController = transitionContext.viewController(forKey: .from),
            let toViewController = transitionContext.viewController(forKey: .to),
            let toView = toViewController.view,
            let fromView = fromViewController.view,
            let containerView = containerView else { return }


        let isPresenting = fromViewController == presentingViewController
        if isPresenting {
            toView.frame.origin.y = fromView.frame.size.height
        }

        UIView.animate(withDuration: transitionDuration(using: transitionContext),
                       delay: 0,
                       usingSpringWithDamping: 1,
                       initialSpringVelocity: 0,
                       options: .curveEaseInOut,
                       animations: {
            if isPresenting {
                toView.frame.origin.y = containerView.frame.size.height - toView.frame.height
                fromView.transform = CGAffineTransform(scaleX: 0.96, y: 0.94)
                fromView.frame = fromView.frame.offsetBy(dx: 0, dy: self.topOffset)
            }
            else {
                toView.transform = CGAffineTransform.identity
                toView.frame = toView.frame.offsetBy(dx: 0, dy: -self.topOffset)
                fromView.frame.origin.y = containerView.frame.size.height
            }
        }) { (finished) in
            if finished {
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            }
        }
    }

By the way, keeping the initial value on a variable and setting it after, solves the problem, but this seems like a workaround not a solution. I want to understand why it is happening and if there's a better way to do it.


Solution

  • Here is what I think might be wrong. From Docs on UIView frame,

    If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.

    you should not use frame property after setting transform. Instead as stated in doc,

    Changes to this property can be animated. However, if the transform property contains a non-identity transform, the value of the frame property is undefined and should not be modified. In that case, reposition the view using the center property and adjust the size using the bounds property instead.

    So

    fromView.frame = fromView.frame.offsetBy(dx: 0, dy: self.topOffset)
    

    need to be modified as

    fromView.center = fromView.frame.center + self.topOffset
    fromView.frame = fromView.frame.bounds
    

    But I don't think we need to modify toView code because we are setting transform to Identity before setting the frame.


    Based on what is said above what solves the problem is modifying the first block from:

    toView.frame.origin.y = containerView.frame.size.height - toView.frame.height
    fromView.transform = CGAffineTransform(scaleX: 0.96, y: 0.94)
    fromView.frame = fromView.frame.offsetBy(dx: 0, dy: self.topOffset)
    

    To

    toView.frame.origin.y = containerView.frame.size.height - toView.frame.height
    fromView.frame = fromView.frame.offsetBy(dx: 0, dy: self.topOffset)
    fromView.transform = CGAffineTransform(scaleX: 0.96, y: 0.94)
    

    Basically doing the frame reposition BEFORE applying the transform.