5 days of learning from a variety of English language tutorials, the transition animation is an unforgettable exploration experience

Better refer to the article customizing UIViewController transition introduction, animation introduction.

Ferry way

First, let’s understand how iOS transitions work:

  • UINavigationController push/pop UIViewController transitions to the navigation bar
  • UITabBarController Switch Tab transitions
  • Present /dismiss mode transition

These are the three basic transition modes provided by iOS. The default transition mode has limited transition styles. For example, in modal transitions, transitions are still stuck at the bottom, despite modalPresentationStyle and modalTransitionStyle Settings for presentation and transition styles. This does not meet our needs in software development, and the animation effects of the sidebar and the top bar are not well realized. After iOS 7.0, Apple provided a complete set of custom transition animation API, for a variety of transition animation implementation with unlimited possibilities.

This article mainly introduces the custom animation of modal transition. Animation for the top bar:

The source code of SlideMune

Modal transitions

Modal transitions are divided into non-interactive transitions and interactive transitions. A non-interactive transition is a normal transition. The animation of a transition cannot interact and the transition cannot be terminated during the animation. Interactive transitions can be transitioned and terminated by swiping through the screen with a finger touch.

Transition animation API

We define: if view controller Apresent is displayed after view controller B. Later, the source view controller is fromVC and the target view controller is toVC.

state View Controller A View Controller B
Present Source View Controller Target view Controller
Dissmiss Target view Controller Source View Controller

TransitioningDelegate Indicates the transition delegate

Each view controller have a UIViewController transitioningDelegate attribute, the agent shall follow UIViewControllerTransitioningDelegate agreement, provide relevant animation controller.

Whenever you display or close a view controller, UIKit requires its transition agent to use an animation controller. To replace the default animation with your own custom animation, you must implement the transition agent and return it to the appropriate animation controller.

AnimationController AnimationController

The transition agent returns the corresponding animation controller at present/dismiss. Animation controller is the core of transition animation. It does the “heavy lifting” of the animation transition.

TransitioningContext Transition context

The transitional context is implemented and plays a crucial role in the transition process: it encapsulates information about the views and view controllers involved in the transition. The transition context assists the animation controller to animate. As you can see from the figure, I did not implement this protocol. UIKit creates and configures the transition context for you and passes it to the animation controller each time a transition occurs.

The process of non-interactive transition animation

Take the present transition animation for example:

  1. Through code orStoryBoard segueTrigger the modal viewpresentProcess.
  2. UIKit totoVC(Target view controller) requests its transitional broker. If not, UIKIt will use standard built-in transitions.
  3. UIKit then requests the animation controller from the transition agentanimationController(forPresented:presenting:source:). If the returnnil, the transition will use the default animation.
  4. UIKit constructs transitional contexts. UIKit calls the animation controller to ask for the duration of the animationtransitionDuration(using:). UIKitanimateTransition(using:)Upregulation in the animation controller to perform the transition animation.
  5. Finally, the animation controller callscompleteTransition(_:)Transition the context to indicate that the animation is complete.

The steps for the dimiss transition animation are almost the same. In this case, UIKit asks the fromVC view controller (the view controller that is closing) for its transition agent, asking for the animationController(forpresenting :).

Non-interactive transition animations need to provide conditions

  1. Set up the transition animation agent. Set (target view controller)transitioningDelegateProperty that sets the transition animation proxy object that the proxy object followsUIViewControllerTransitioningDelegateProtocol, implementationforPresentedandforDismissedTwo methods, providing view controller instances of present and dismiss, respectively.
  2. Create the animation controller. Create present and Dismiss animation controllers that implement the animation duration and construct the animation method.

Here we complete a simple transition animation from left to right.

1. Construct the Present/Dimiss scenario

The build process is not covered here, but both stroyBoard and pure code are pretty well done. I’m going to do this in storyboard for convenience. The LeftViewController is ViewController. The right ViewController is LeftViewController

2. Create an Animation Controller

The Animation Controller is the core object for custom transition animations. AnimationControlle inheritance to NSObject, follow UIViewControllerAnimatedTransitioning protocol, implement two methods required.

class AnimationController: NSObject.UIViewControllerAnimatedTransitioning {

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        // Requires the animator object's animation duration property to be provided
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        // The implementation effect of the transition animation is also the core method of customizing the transition animation, which needs to build the animation implementation.}}Copy the code

The code for the AnimationController in the Present state is posted here, and the Dismiss state is similar (the animation process executes the reverse process).

import UIKit

class PresentAnimationController: NSObject.UIViewControllerAnimatedTransitioning {
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.6
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        / / 1
        guard let _ = transitionContext.viewController(forKey: .from),
            let toVC = transitionContext.viewController(forKey: .to) else {
                return
        }
        
        / / 2
        let containerView = transitionContext.containerView
        containerView.addSubview(toVC.view)
        let duration = transitionDuration(using: transitionContext)
        toVC.view.frame = CGRect(x: -ScreenWidth, y: 0, width: ScreenWidth, height: ScrennHeight)
        
        / / 3
        UIView.animate(withDuration: duration, animations: {
            toVC.view.frame = CGRect(x: 0, y: 0, width: ScreenWidth, height: ScrennHeight) {}) (_) intransitionContext.completeTransition(! transitionContext.transitionWasCancelled) } } }Copy the code

Set the duration of the animation in the first transitionDuration(using:) method.

Build the animation in the second transitionDuration(using:) method.

  1. Get the view controller and snapshot required for the transition animation. From the transitional contexttransitionContext.viewControllerWe can getSource view controller fromVCandTarget view controller toVCThe transitional context encapsulates the information of the designed view controller and greatly helps us deal with the animation of the view controller. You can also get fromVC and toVCSnapshot screenTo construct more complex and excellent animations.
  2. Container view for managing transitional contextscontainerViewAnd view animation position initialization. UIKit encapsulates the entire transition in a container view to simplify the management of view hierarchies and animations, which are managed by the container viewfromVC.viewandtoVC.view. Container views created by UIKit only containfromVCThe view. You must add any other views that will participate in the transition.

AddSubview (_:) puts the new view before all the other views in the view hierarchy, so the order in which the subviews are added is important.

  1. Set the animation effect. Animation has two ways to achieve, one is basic animationanimateThe other is keyframe animationanimateKeyframes. I’m just going to simply putfromVC.viewMove from the left edge of the screen to the screen. Note: this is called after the animation is completecompleteTransition(_:)Notify UIKit that the animation is complete. This ensures that the final state is consistent.

3. Set the transition animation agent

Set destinationVC’s modalPresentationStyle to the enumerated property Custom and the transition animation’s proxy to self, the ViewController.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let destinationVC = segue.destination as? LeftViewController {
            destinationVC.modalPresentationStyle = .custom
            destinationVC.transitioningDelegate = self}}Copy the code

Then in (the source view controller) add extensions, follow UIViewControllerTransitioningDelegate agreement, achieve forPresented and forDismissed method.

extension ViewController: UIViewControllerTransitioningDelegate {
    
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        // The target VC is presented, while the source VC is presented
        guard let _ = presented as? LeftViewController.let _ = presenting as? ViewController else {
            return nil
        }
        return PresentAnimationController()}func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        guard let _ = dismissed as? LeftViewController else {
            return nil
        }
        return DismissAnimationController()}}Copy the code

This builds a simple, basic custom transition animation.

Interactive transition animation

Interactive animation makes the user’s animation experience more natural and comfortable without being sharp, leaving room for control of the animation process. There are examples of interactive animations in the iOS native APP Settings.

The progress of the transition animation follows the slide of the finger to start/stop the transition animation, which makes for a good user interaction experience.

How the interactive controller works

Interactive controllers respond to touch events or programming inputs by speeding up, slowing down, or even reversing the transition process. To enable interactive transformations, the transformation agent must provide an interaction controller. You have animated the transitions. Based on transition animations, Apple encapsulates interactive animations as interactive controllers that respond to gestures to manage the animation, rather than allowing it to play back like a video. Apple provides a ready-made UIPercentDrivenInteractiveTransition class, through inherit this class to create their own interactive controller.

1. Create an interactive controller

To build an interactive transition animation from the previous transition animation, we first need to create an interactive controller. Posted here shows an interactive controller PresentInteractionController code, packaging good UIPercentDrivenInteractiveTransition class inheritance.

class PresentInteractionController: UIPercentDrivenInteractiveTransition {
    var interactionInProgress = false
    private var shouldCompleteTrantision = false
    private weak var viewController: UIViewController!
    private weak var toViewController: UIViewController!
    
    init(viewController: UIViewController, toViewController: UIViewController) {
        super.init(a)self.viewController = viewController
        self.toViewController = toViewController
        prepareGestureRecognizer(in: self.viewController.view)
    }
    
    private func prepareGestureRecognizer(in view: UIView) {
        let gesture = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleGsture(_:)))
        gesture.edges = .left
        view.addGestureRecognizer(gesture)
    }
    
    @objc func handleGsture(_ gestureRecognizer: UIScreenEdgePanGestureRecognizer) {
        let translation = gestureRecognizer.translation(in: gestureRecognizer.view? .superview)var progress = translation.x / 200
        progress = CGFloat(fminf(fmaxf(Float(progress), 0.0), 1.0))
        
        switch gestureRecognizer.state {
        case .began:
            interactionInProgress = true
            viewController.present(toViewController, animated: true, completion: nil)
        case .changed:
            shouldCompleteTrantision = progress > 0.5
            update(progress)
        case .cancelled:
            interactionInProgress = false
            cancel()
        case .ended:
            interactionInProgress = false
            if shouldCompleteTrantision {
                finish()
            } else {
                cancel()
            }
        default:
            break}}}Copy the code
  • interactionInProgressBool property indicating whether an interactive scene is occurring.
  • shouldCompleteTrantisionBool property indicating whether the transition animation should be terminated. For internal management transition.
  • viewControllerandtoViewControllerTo obtain the reference of the source view controller and the target view controller, which is used to manage the transition of a state present view controller, and achieve the function of connecting the interactive controller and the animation controller.
  • prepareGestureRecognizer(in:)Add screen gesture method for source view, here interactive animation is presented from left to right VC, so add screen for source view corresponding in.leftThe gesture.
  • handleGsture(_:)Method of changing transition animation state for corresponding gesture changes. Track slide progress by declaring local variablestranslation, according to thetranslationGets and calculates the transition progress in the viewprogress. Gesture starts when you will setinteractionInProgressfortrueAnd triggerprsentView controller. Gesture is called when movingupdate(_:)Update the transition progress. This is a basisUIPercentDrivenInteractiveTransitionYou pass in the percentage move transition method. If the gesture is cancelled, it is updatedinteractionInProgressAnd cancel the transition. Once the action has finished, you use the transition schedule to determinecancel()orfinish().

2. Controller contact

The (source) view controller is associated with the interaction controller

Add the following properties to the viewController:

var presentInteractionController: PresentInteractionController?
Copy the code

And initialize the property in the viewDidLoad() method:

presentInteractionController = PresentInteractionController(viewController: self, toViewController: leftViewController)
Copy the code

The interaction controller is associated with the animation controller

In PresentAnimationController add the following attributes:

let interactionController: PresentInteractionController?
Copy the code

And add init method:

init(interactionController: PresentInteractionController?). {self.interactionController = interactionController
}
Copy the code

3. Implement the transitional agent protocol

Animation controller forPresented method of modified PresentAnimationController object creation.

func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        // The PRESENTING VC is presented while the presenting VC is presented
        guard let _ = presented as? LeftViewController.let fromVC = presenting as? ViewController else {
            return nil
        }
        return PresentAnimationController(interactionController: fromVC.presentInteractionController)
    }
Copy the code

Add the following methods:

func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        guard let animator = animator as? PresentAnimationController.let interactionController = animator.interactionController,
            interactionController.interactionInProgress else {
                return nil
        }
        return interactionController
    }
Copy the code

The first check for PresentAnimationController involved in the animation controller. If so, it gets a reference to the associated interaction controller and verifies that the user interaction is in progress. If any of these conditions are not met, it returns nil so that the conversion will continue without interaction. Otherwise, it returns the interaction controller to UIKit so that it can manage the transition.

The method of the interactive controller of the dismiss state is similar, and the final result is as follows:

Download the source code