The story of one controller view that wanted to show off beautifully

Once upon a time there was a modest view controller VCYellow . And he had no pictures, no text, not even a tiny business logic. He lived a normal view-controller life.


His fellow controller view VCMain sometimes presented it to the world:


classVCMain: UIViewController{
...
@IBActionfunconBtnTapMeTapped(_ sender: Any) {
    let vcYellow = self.storyboard!.instantiateViewController(withIdentifier: "VCYellow") as! VCYellowself.present(vcYellow, animated: true, completion: nil)
}

And VCYellow, in turn, was hiding with a single “X” button, which, by the way, he was very proud of:


classVCYellow: UIViewController{
...
@IBActionfunconBtnCloseTapped(_ sender: Any) {
    self.dismiss(animated: true, completion: nil)
}

And it looked not so bad, but boring and mundane:



But our hero had a dream to learn how to show and hide in beauty. Yes, so that you can change this beauty later on holidays or simply in honor of a good mood.



It has been a year ... and it would have remained a dream, if it had not learned VCYellow about magic called:


UIViewControllerTransitioningDelegate

And the strength of this magic is that it gives the opportunity to slip the appropriate animator to show and hide the view controller. Just what our controller dreamed of.
He read in the ancient scrolls how to use the spell and began to prepare.
I wrote myself a cheat sheet with the spell itself, so as not to forget:


extensionVCYellow: UIViewControllerTransitioningDelegate{
    funcanimationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        returnAnimatorPresent(startFrame: self.startFrame)
    }
    funcanimationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        returnAnimatorDismiss(endFrame: self.startFrame)
    }
}

In it, he carefully wrote that to show you need to use the animator AnimatorPresent , and when closing AnimatorDismiss .
Well, as an aid to both animators, it was decided to transfer the frame of the main button from VCMain


And then he morally tuned. Because without the right mood, as you know, no magic works:


overridefuncviewDidLoad() {
    super.viewDidLoad()
    self.modalPresentationStyle = .custom
    self.transitioningDelegate = self
}

He asked his friend VCMain to present himself to check how the magic worked and ... it worked in any way ...
It turned out that AnimatorPresent and AnimatorDismiss do not appear by themselves.


It was too late to stop and our hero decided to create the necessary animators. I dig into the necessary section of the ancient scrolls and found out that two things are enough to create an animator.


First, you need to set the time allotted for the animation:


functransitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return0.3
}

and second, mark the animation itself:


funcanimateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    //1guardlet vcTo = transitionContext.viewController(forKey: .to),
        let snapshot = vcTo.view.snapshotView(afterScreenUpdates: true) else {
        return
    }
    //2let vContainer = transitionContext.containerView
    //3
    vcTo.view.isHidden = true
    vContainer.addSubview(vcTo.view)
    //4
    snapshot.frame = self.startFrame
    vContainer.addSubview(snapshot)
    UIView.animate(withDuration: 0.3, animations: {
        //5
        snapshot.frame = (transitionContext.finalFrame(for: vcTo))
    }, completion: { success in//6
        vcTo.view.isHidden = false
        snapshot.removeFromSuperview()
        transitionContext.completeTransition(true)
    })
}

  1. Pull out the presented view controller (in our case VCYellow) and take it. The photo is needed to simplify the animation.
  2. Get the view where the animated witch will be. Let's call its context.
  3. To fasten a view of the final controller on a context and to hide it. Show
  4. it was decided after the animation ended.
  5. Prepare a photo for animation. Reduce to initial size and throw on the context.
  6. Split pictures into full screen, thereby animating the presentation process.
  7. After the animation ends, show the real view of the final controller,
  8. get rid of the pictures and report that the action is over.

As a result, this animator came out to show:


import UIKit
classAnimatorPresent: NSObject, UIViewControllerAnimatedTransitioning{
    let startFrame: CGRectinit(startFrame: CGRect) {
        self.startFrame = startFrame
    }
    functransitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return0.3
    }
    funcanimateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guardlet vcTo = transitionContext.viewController(forKey: .to),
        let snapshot = vcTo.view.snapshotView(afterScreenUpdates: true) else {
            return
        }
        let vContainer = transitionContext.containerView
        vcTo.view.isHidden = true
        vContainer.addSubview(vcTo.view)
        snapshot.frame = self.startFrame
        vContainer.addSubview(snapshot)
        UIView.animate(withDuration: 0.3, animations: {
            snapshot.frame = (transitionContext.finalFrame(for: vcTo))
        }, completion: { success in
            vcTo.view.isHidden = false
            snapshot.removeFromSuperview()
            transitionContext.completeTransition(true)
        })
    }
}

And after that, it was easy to write an animator for hiding, which does roughly the same thing, but vice versa:


import UIKit
classAnimatorDismiss: NSObject, UIViewControllerAnimatedTransitioning{
    let endFrame: CGRectinit(endFrame: CGRect) {
        self.endFrame = endFrame
    }
    functransitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return0.3
    }
    funcanimateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guardlet vcTo = transitionContext.viewController(forKey: .to),
        let vcFrom = transitionContext.viewController(forKey: .from),
        let snapshot = vcFrom.view.snapshotView(afterScreenUpdates: true) else {
            return
        }
        let vContainer = transitionContext.containerView
        vContainer.addSubview(vcTo.view)
        vContainer.addSubview(snapshot)
        vcFrom.view.isHidden = trueUIView.animate(withDuration: 0.3, animations: {
            snapshot.frame = self.endFrame
        }, completion: { success in
            transitionContext.completeTransition(true)
        })
    }
}

Having finished all the finishing touches , VCYellow again asked his friend VCMain to present himself and about a miracle!



The magic worked! VCYellow's dream came true! Now he can show and hide as he pleases and nothing will limit his imagination!


An example project can be downloaded here.


The article I used for inspiration is here.


Also popular now: