Skip to content

Latest commit

 

History

History
184 lines (117 loc) · 9.31 KB

custom_interaction.md

File metadata and controls

184 lines (117 loc) · 9.31 KB

Adding Custom Interaction

Now you've seen how easy it is to build a custom view controller transition animation, let's dive in further and add some custom interaction.

Transition offers you two approaches:

  1. DIY: implement an object that conforms to the TransitionInteractionController protocol
  2. Drop-in: use Transition's built-in PanInteractionController for basic pan-gesture interaction

Want to take it easy? just skip to 2.

Either way, with custom interaction comes the requirement for an InteractiveTransitionOperationDelegate.


1. TransitionInteractionController

The requirements are quite straightforward:

  1. What gesture should drive the interaction?
  2. What kind of operation (navigation push / pop, modal present / dismiss, tabbar index change) should initiate when the gesture is recognized?
  3. What is the progress of the transition based on the change of the recognized gesture?
  4. When the gesture ends, should the transition complete or cancel?

1. What gesture should drive the interaction?

We start by creating an object that conforms to TransitionInteractionController. You'll be required to implement several functions, but everything starts with the gestureRecognizer. We will use a UIPanGestureRecognizer, which you can easily expose as follows:

private let panGestureRecognizer = UIPanGestureRecognizer()
public var gestureRecognizer: UIGestureRecognizer { return panGestureRecognizer }

The first line instantiates our panGestureRecognizer for internal use in our object, the second one is in accordance with the TransitionInteractionController protocol.

2. What kind of operation should initiate when the gesture is recognized?

The TransitionController will register as target of your gestureRecognizer. When the gesture is recognized, it'll ask your TransitionInteractionController which TransitionOperation should be initiated based on the gesture (this can be no action at all too).

You answer by implementing:

func operationForInteractiveTransition() -> TransitionOperation

Our pan gesture has a translation (in: view...) that can be used to determine in which direction the gesture was made. You convert this to whatever TransitionOperation you prefer, for example .navigation(.push), or .none if the gesture was in the wrong direction.

3. What is the progress of the transition based on the change of the recognized gesture?

The gesture recognizer will frequently call its targets during interaction, causing your TransitionInteractionController to be asked to translate the current position or movement of the gesture into the progress of the transition. You do this in:

func progress(in transitionOperationContext: TransitionOperationContext) -> TransitionProgress

Your answer can either represent a small step relative to the last update that will be added to the current fractionComplete of the transition, or an overall fractionComplete that will be used as the new corresponding value for the transition.

4. When the gesture ends, should the transition complete or cancel?

When the gesture ends, you want the transition to nicely animate towards the end, completing the transition.

However, it might be that the gesture movement was insufficient to decently complete the transition operation, or the movement went backwards halfway the transition. In such cases you might wish to reverse the transition, effectively rolling it back to the start, cancelling the transition operation.

Answer accordingly by implementing:

func completionPosition(in transitionOperationContext: TransitionOperationContext, 
                                     fractionComplete: AnimationFraction) -> UIViewAnimatingPosition

2. PanInteractionController

For your pleasure we have added an easy to use TransitionInteractionController for basic pan gestures, called the PanInteractionController. It can be used for any kind of transition (navigation, modal, tabBar).

For navigation and modal transitions, you initialize it with

PanInteractionController(forNavigationTransitionsAtEdge: TransitionScreenEdge)

or

PanInteractionController(forModalTransitionsAtEdge: TransitionScreenEdge)

respectively. The TransitionScreenEdge (.top, .right, .bottom or .left) designates the edge of the screen from which new viewControllers will be presented or towards which they will be dismissed. To mimic native navigationController transitions, you would set this to .right (at least for left-to-right languages), and for modal transitions this would be .bottom. However you can now set this to whatever you like!

For tabBar transitions a different initializer is available that corresponds to the direction of items on the tabBar. To initialize one, call:

PanInteractionController.forTabBarTransitions(rightToLeft: Bool = false)

By default this is configured for left-to-right language apps where the tabBar item indices increase from left to right.

CompletionThreshold

The PanInteractionController has a built-in completionThreshold (default: 1/3 of the transition progress) above which the transition will complete, but below which the transition will rewind to the start and cancel the transition operation. You can set this to anything between 0 and 1.

Flick

Another default feature is the "flick", a swift movement at the end of the gesture that has enough velocity to complete the transition in the direction of the flick (this might be towards the end but also towards the start). You can adjust the minimum velocity (expressed in points per second) for recognizing an ended pan gesture as a flick. You can also turn off this feature completely.

Updating Progress

Depending on how you implement the interaction part of your Transition (which describes how the "Shared Element" should animate and move – more info here) you might want the TransitionInteractionController to update the progress either as small step relative to the last update, or as an overall fraction complete. By default, the PanInteractionController updates the progress as fraction complete, but you can switch this to progress step by setting updateProgressAsStep to true.


The InteractiveTransitionOperationDelegate

The operation resulting from your recognized gesture can be any of the following:

  • Navigation push
  • Navigation pop
  • Modal present
  • Modal dismiss
  • Increase TabBar index
  • Decrease TabBar index

The TransitionController leaves it up to you to appropriately respond to whatever applies for your configuration (you can only use a TransitionController and associates for a single type – navigation, modal or tabBar – at a time).

The main reason for this is that Transition doesn't know which ViewController to instantiate when pushing or presenting. The delegate comes in three flavors:

InteractiveNavigationTransitionOperationDelegate

      func performOperation(operation: UINavigationControllerOperation,
forInteractiveTransitionIn controller: UINavigationController, 
                    gestureRecognizer: UIGestureRecognizer)
}

InteractiveTabBarTransitionOperationDelegate

      func performOperation(operation: UITabBarControllerOperation,
forInteractiveTransitionIn controller: UITabBarController, 
                    gestureRecognizer: UIGestureRecognizer)
}

InteractiveModalTransitionOperationDelegate

func viewControllerForInteractiveModalPresentation(by sourceViewController: UIViewController, 
                                                         gestureRecognizer: UIGestureRecognizer) -> UIViewController
}

🤔 Hey, that last one looks different!

Yes. For navigation and tabBar, leaving the operation up to you suffices, but for modal transitions, the correct transitioningDelegate and modalPresentationStyle must be set on the correct ViewController, at the correct moment. Therefore the operationDelegate is simply asked for the ViewController that needs to be presented, and the TransitionController ensures it is presented correctly, with your custom transition.


Adjusting the TransitionController

You'll have to pass your new-found interactionController and operationDelegate to the TransitionController. For our simple custom animation we used the initializer:

TransitionController(forTransitionsIn: navigationController, 
                    transitionsSource: transitionsSource)

To make it interactive, change to this initializer:

TransitionController(
  forInteractiveTransitionsIn navigationController: UINavigationController, 
                                 transitionsSource: TransitionsSource, 
                                 operationDelegate: InteractiveNavigationTransitionOperationDelegate,
                             interactionController: TransitionInteractionController)

For interactive tabBar transitions a similar initializer is available. For interactive modal transitions, the sourceViewController (the one on which you'd call present(_, animated:, completion:)) is required to also be the InteractiveModalTransitionOperationDelegate, so the initializer signature is slightly different.


Feeling lucky? Let's dive in further and add a shared element!