Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Toine Heuvelmans committed Mar 13, 2017
0 parents commit 4c2dd43
Show file tree
Hide file tree
Showing 167 changed files with 10,922 additions and 0 deletions.
39 changes: 39 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# OS X
.DS_Store

# Xcode
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
*.xccheckout
profile
*.moved-aside
DerivedData
*.hmap
*.ipa

# Bundler
.bundle

Carthage
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
#
# Note: if you ignore the Pods directory, make sure to uncomment
# `pod install` in .travis.yml
#
# Pods/
Examples/NavigationController/Pods/Reveal-SDK/RevealServer-6/iOS/RevealServer.framework/Info.plist
Examples/NavigationController/Pods/Reveal-SDK/RevealServer-6/iOS/RevealServer.framework/RevealServer
Examples/NavigationController/Pods/Reveal-SDK/RevealServer-6/iOS/RevealServer.framework/_CodeSignature/CodeResources
Examples/NavigationController/Pods/Reveal-SDK/RevealServer-6/iOS/RevealServer.framework/Headers/RevealServer.h
Examples/NavigationController/Pods/Reveal-SDK/RevealServer-6/iOS/RevealServer.framework/Modules/module.modulemap
Examples/NavigationController/Pods/Reveal-SDK/RevealServer-6/iOS/RevealServer.framework/Scripts/copy_and_codesign_revealserver.sh
1 change: 1 addition & 0 deletions .swift-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.0
Binary file added Documentation/artwork/catalog.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Documentation/artwork/header.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Documentation/artwork/modal.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Documentation/artwork/simple.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Documentation/artwork/tabbar.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
184 changes: 184 additions & 0 deletions Documentation/custom_interaction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# 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.**](#2-paninteractioncontroller)

Either way, with custom interaction comes the requirement for an [`InteractiveTransitionOperationDelegate`](#the-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:

```swift
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:

```swift
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:

```swift
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:

```swift
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

```swift
PanInteractionController(forNavigationTransitionsAtEdge: TransitionScreenEdge)
```

or

```swift
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:

```swift
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](shared_element.md)) 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

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

### InteractiveTabBarTransitionOperationDelegate

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

### InteractiveModalTransitionOperationDelegate

```swift
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:

```swift
TransitionController(forTransitionsIn: navigationController,
transitionsSource: transitionsSource)
```

To make it interactive, change to this initializer:

```swift
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](shared_element.md)!
56 changes: 56 additions & 0 deletions Documentation/shared_element.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Adding A Shared Element

Your transition already has a custom animation and custom interaction. What can we add more?

A "shared element". By this we mean a single view that represents content present in both ViewControllers in the transition (the "`from`" and "`to`"). An example might be a photo that is selected from a collectionView, transitioning to a detailView for that specific photo.

### The SharedElementTransition

Commonly this shared element will follow the interaction gesture, moving around in the transitionContext's containerView. During animation phase(s) it should animate towards its target position / size. Therefore there are two parts – animation and interaction – that together form the specification of your shared element's transition:

`SharedElementAnimation` + `SharedElementInteraction` = `SharedElementTransition`

We'll get to the specifics of these, but first:

### The SharedElement

In both parts you'll have access to a `SharedElement` object. This object provides the `transitioningView` that can be put in the transitionContext's containerView. It is advised that this is not the actual view from either your from- or toView, but rather a snapshot that can safely be discarded after transition. The `SharedElement` is a protocol that allows you to encapsulate any other information you might require for properly implementing the animation / updating of the element's movement. Also think about whether or not the snapshot should change scale during transition; you might want to use a UIImageView as `transitioningView`.

### The SharedElementAnimation

In this part of the `SharedElementTransition` you specify how the `SharedElement`'s `transitioningView` should animate. Important to know is that animation of the shared element is set up every time after interaction (and on programmatic initiation at which we'll begin the transition in animation phase). It will be set up for a specific `UIViewAnimatingPosition` (either `.end` or `.start`) based on the current completion position derived from the interaction gesture (or `.end` when no interaction has occcured yet). This position will be available to you via the `animatingPosition` property, such that you can adjust the `sharedElement` to move to either its final or original frame (or adjust it in any other way appropriate for your transition).

### The SharedElementInteraction

In this part of the `SharedElementTransition` you specify how the `SharedElement`'s `transitioningView` should update (move / scale / ...) during interaction. You are given the opportunity to perform initial steps prior to updating by implementing:

```swift
func startInteraction(in context: UIViewControllerContextTransitioning,
gestureRecognizer: UIGestureRecognizer)
```

Note that the gestureRecognizer passed here is not necessarily the gestureRecognizer of your `TransitionInteractionController`. Under the hood a long-press gesture recognizer is installed in your transitioning context, such that interruption can be detected. From that point on your interaction gesture might take over, but to ensure you have the right `location(in: view...)` available at `startInteraction`, the long-press gesture recognizer will be passed if that initiated the interaction.

During interaction the following function will be called frequently, allowing you to update your `transitioningView`:

```swift
func updateInteraction(in context: UIViewControllerContextTransitioning,
interactionController: TransitionInteractionController,
progress: TransitionProgress)
```

You might recall from implementing the `TransitionInteractionController` ([this guide](custom_interaction.md)) that `TransitionProgress` can either be a relative step compared to the last update, or an overall fractionComplete. Ensure that both your `TransitionInteractionController` and `SharedElementInteraction` talk the same language.

### The SharedElementProvider

We still need a means to obtain the `sharedElement`. Since it is often tightly coupled to the interaction gesture (for instance because of hit-testing), the `TransitionInteractionController` includes a property on which you can set the `sharedElementProvider`.

The `SharedElementProvider` is a protocol requiring a single function to be implemented:

```swift
func sharedElementForInteractiveTransition(
with interactionController: TransitionInteractionController,
in operationContext: TransitionOperationContext) -> SharedElement?
```

And that's it!
30 changes: 30 additions & 0 deletions Documentation/timing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# A Note On Timing

### Duration
The **duration** of a transition is used in the *animation phase(s)* of a transition. This can be the entire length of the transition if the transition is initiated programmatically and there's no interruption / interaction. It can also be after interaction ends, animating towards either end or start (completion or cancellation).

### AnimationRange
The transition can have one or more animation layers, each with an `AnimationRange`, indicating the relative start and end time of a layer to the total duration of the transition.
For example, an `AnimationRange(start: 0, end: 0,5)` with a transition duration of 3 seconds will start at 0 seconds and end after 1.5 seconds.

A `Transition`'s `sharedElement` describes how the shared element should move during interaction, but also how it should animate during animation phases. If the `sharedElement` is specified, it will have an implicit `AnimationRange` of 0 to 1 (the `AnimationRange.full` range).

The reason for having an AnimationRange relative to the transition duration is because this duration can be influenced by timing arameters.

### AnimationTimingParameters

Timing parameters describe a timing curve that defines the velocity at which animated properties change to their new values over the duration of the animation. This curve can either be cubic (represented by `UICubicTimingParameters`) or follow a spring behavior (represented by `UISpringTimingParameters`). On initializing a `UIViewPropertyAnimator`, these timing parameters are passed together with the required duration. The property animator will pace the animation by following the timing curve over the length of the duration.

The motion of a spring can be defined either by specifying a `dampingRatio` and `initialVelocity`, or by exactly specifying the spring's `mass`, `stiffness`, `daming` and `initialVelocity`. The former can be configured such that the spring movement settles after the duration. The latter however specifies a fully configured spring system that realistically oscillates according to physics and cannot be influenced to settle at a specific moment. As such it has an **implicit duration**.

We introduce the `AnimationTimingParameters` struct to be able to tell whether the provided timingParameters will have such an implicit duration or not, because from `UISpringTimingParameters` it cannot be derived.

### Effective duration

Any layer (animation or sharedElement) can have timing parameters that have an implicit duration. The presence thereof will influence the **effective duration** of the transition.

* The effective duration is initially expected to be the duration specified in your `Transition`.

* Then, we inspect all animation layers specified in the transition's `animation`. If any layer has an implicit duration that causes that layer to end after the original transition duration, this will extend the effective duration such that the layer ends exactly at the end of the entire transition.

* If there's a `sharedElement` specified, we set its duration to be the effective duration as we know it at this point. However, if the `sharedElement` has timing parameters with an implicit duration, *that* duration will ultimately be the effective duration of the transition.
20 changes: 20 additions & 0 deletions Examples/BuiltInTransitionsCatalog/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Pods/
# Xcode
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
project.xcworkspace/
*.xccheckout
profile
*.moved-aside
DerivedData
*.hmap
*.ipa

Loading

0 comments on commit 4c2dd43

Please sign in to comment.