Skip to content
This repository has been archived by the owner on Aug 30, 2023. It is now read-only.

Commit

Permalink
Merge branch 'release-candidate' into stable
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeff Verkoeyen committed Oct 20, 2017
2 parents 92ab4a0 + f08a19b commit 1ddd7da
Show file tree
Hide file tree
Showing 38 changed files with 1,455 additions and 498 deletions.
68 changes: 68 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,71 @@
# 4.0.0

This major release adds support for composable transitions. See the catalog app for a variety of
examples making use of this new functionality.

## Fixed issues

- [Transitions would not complete if the presentation controller didn't implement the startWithContext method](https://github.com/material-motion/transitioning-objc/pull/45)

## Breaking changes

- `MDMTransitionWithFallback`'s return value is now nonnull. If you depended on the nil behavior,
you must now conform to the new protocol `MDMTransitionWithFeasibility` and return `NO` for
`canPerformTransitionWithContext:`.
- `MDMTransitionDirection` has been renamed to `TransitionDirection` in Swift.

## New features

`MDMTransitionWithFeasibility` allows a transition to indicate whether it is capable of performing
the transition with a given context.

The new `composeWithTransition:` API on `MDMTransitionContext` makes it possible to build modular
transition objects that delegate responsibility out to other transition objects. View the
`PhotoAlbumTransition` example transition to see the following code in action:

```swift
context.compose(with: FadeTransition(target: .foreView, style: .fadeIn))
context.compose(with: SpringFrameTransition(target: .target(snapshotContextView),
size: fitSize))

if let toolbar = foreDelegate.toolbar(for: self) {
context.compose(with: SlideUpTransition(target: .target(toolbar)))
}
```

## Source changes

* [Add nullability annotations to MDMTransitionNavigationControllerDelegate. (#46)](https://github.com/material-motion/motion-transitioning-objc/commit/302d3c4ec526ffa942d23937fdfe8ef5163d473d) (featherless)
* [Update Xcode build settings to Xcode 9 warnings and resolve build error.](https://github.com/material-motion/transitioning-objc/commit/5ed85cdc795ae6660901c5e2ae237732f04649e1) (Jeff Verkoeyen)
* [Rework multi-transition support using composition. (#43)](https://github.com/material-motion/transitioning-objc/commit/0b57361557476c7d3ecb8f4c9878da21a2e735ab) (featherless)
* [Fix the Swift symbol name for MDMTransitionDirection. (#44)](https://github.com/material-motion/transitioning-objc/commit/4cdcf4ca0324a1f83d572440887fe5a5d18ee00b) (featherless)
* [Fix bug where transitions would not complete if the presentation controller didn't implement the startWithContext method. (#45)](https://github.com/material-motion/transitioning-objc/commit/784328dae8509df0a2beb3a5afa9701f1e275950) (featherless)
* [Fix broken unit tests.](https://github.com/material-motion/transitioning-objc/commit/46c92ebcab642969ba70ea43aa512cac1cc3cad4) (Jeff Verkoeyen)
* [Add multi-transition support. (#40)](https://github.com/material-motion/transitioning-objc/commit/8653958a5a9419891861fb6fd7648791ca3c744c) (featherless)
* [Remove unused protocol forward declaration.](https://github.com/material-motion/transitioning-objc/commit/74c1655fc3614e5e9788db8b53e8bff83691137a) (Jeff Verkoeyen)

## API changes

### MDMTransitionWithCustomDuration

*changed* protocol `MDMTransitionWithCustomDuration` now conforms to `MDMTransition`.

### MDMTransitionWithFallback

*changed* protocol `MDMTransitionWithFallback` now conforms to `MDMTransition`.

### MDMTransitionWithFeasibility

*new* protocol `MDMTransitionWithFeasibility`.

### MDMTransitionContext

*new* method `composeWithTransition:`

## Non-source changes

* [Add platform to the Podfile per pod install recommendation.](https://github.com/material-motion/transitioning-objc/commit/7384187b2ddd6a2760f5279cabb5032ea3b1e24e) (Jeff Verkoeyen)

# 3.3.0

This minor release deprecates some behavior and replaces it with a new API.
Expand Down
2 changes: 1 addition & 1 deletion MotionTransitioning.podspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "MotionTransitioning"
s.summary = "Light-weight API for building UIViewController transitions."
s.version = "3.3.0"
s.version = "4.0.0"
s.authors = "The Material Motion Authors"
s.license = "Apache 2.0"
s.homepage = "https://github.com/material-motion/transitioning-objc"
Expand Down
1 change: 1 addition & 0 deletions Podfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
workspace 'MotionTransitioning.xcworkspace'
use_frameworks!
platform :ios, '8.0'

target "TransitionsCatalog" do
pod 'CatalogByConvention'
Expand Down
10 changes: 5 additions & 5 deletions Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
PODS:
- CatalogByConvention (2.1.1)
- MotionTransitioning (3.3.0)
- MotionTransitioning (4.0.0)

DEPENDENCIES:
- CatalogByConvention
- MotionTransitioning (from `./`)

EXTERNAL SOURCES:
MotionTransitioning:
:path: "./"
:path: ./

SPEC CHECKSUMS:
CatalogByConvention: c3a5319de04250a7cd4649127fcfca5fe3322a43
MotionTransitioning: caaa488e0469d93f004793b96a2ed04447af808d
MotionTransitioning: be4161ebcbff7911a1d9c4549e396f486041ca6f

PODFILE CHECKSUM: db2e7ac8d9d65704a2cbffa0b77e39a574cb7248
PODFILE CHECKSUM: 25d5942fb7698339a03667bb46c3fbb77529b92d

COCOAPODS: 1.2.1
COCOAPODS: 1.3.1
101 changes: 62 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ you can pick the custom transition you want to use:
```swift
let viewController = MyViewController()
viewController.transitionController.transition = CustomTransition()
present(modalViewController, animated: true)
present(viewController, animated: true)
```

```objc
Expand Down Expand Up @@ -102,7 +102,7 @@ commands:
## Guides
1. [Architecture](#architecture)
2. [How to create a simple transition](#how-to-create-a-simple-transition)
2. [How to create a fade transition](#how-to-create-a-fade-transition)
3. [How to customize presentation](#how-to-customize-presentation)
4. [How to customize navigation controller transitions](#how-to-customize-navigation-controller-transitions)
Expand All @@ -118,23 +118,27 @@ MotionTransitioning provides a thin layer atop these protocols with the followin
- Every view controller has its own **transition controller**. This encourages choosing the
transition based on the context.
- Transitions are represented in terms of **backward/forward** rather than from/to. When presenting,
we're moving forward. When dismissing, we're moving backward. This makes it easier to refer to
each "side" of a transition consistently.
- Transition objects can customize their behavior by conforming to more `TransitionWith*` protocols.
This protocol-oriented design is more Swift-friendly than a variety of optional methods on a
protocol.
- But most importantly: **this library handles the plumbing, allowing you to focus on the motion**.
we're moving forward. When dismissing, we're moving backward. This allows transition code to be
written with fewer conditional branches of logic.
- Transition objects can customize their behavior by conforming to the family of `TransitionWith*` protocols.
### How to create a simple transition
### How to create a fade transition
In this guide we'll create scaffolding for a simple transition.
We'll create a new fade transition so that the following lines of code customizes the presentation
and dismissal of our view controller:
```swift
let viewController = MyViewController()
viewController.transitionController.transition = FadeTransition()
present(viewController, animated: true)
```

#### Step 1: Define a new Transition type

Transitions must be `NSObject` types that conform to the `Transition` protocol.
A transition is an `NSObject` subclass that conforms to the `Transition` protocol.

The sole method we're expected to implement, `start`, is invoked each time the view controller is
presented or dismissed.
The only method you have to implement is `start(with context:)`. This method is invoked each time
the associated view controller is presented or dismissed.

```swift
final class FadeTransition: NSObject, Transition {
Expand All @@ -146,7 +150,11 @@ final class FadeTransition: NSObject, Transition {

#### Step 2: Invoke the completion handler once all animations are complete

If using Core Animation explicitly:
Every transition is provided with a transition context. The transition context must be told when the
transition's motion has completed so that the context can then inform UIKit of the view controller
transition's completion.

If using explicit Core Animation animations:

```swift
final class FadeTransition: NSObject, Transition {
Expand All @@ -164,7 +172,7 @@ final class FadeTransition: NSObject, Transition {
}
```

If using UIView implicit animations:
If using implicit UIView animations:

```swift
final class FadeTransition: NSObject, Transition {
Expand All @@ -181,41 +189,57 @@ final class FadeTransition: NSObject, Transition {

#### Step 3: Implement the motion

With the basic scaffolding in place, you can now implement your motion.
With the basic scaffolding in place, you can now implement your motion. For simplicity's sake we'll
use implicit UIView animations in this example to build our motion, but you're free to use any
animation system you prefer.

```swift
final class FadeTransition: NSObject, Transition {
func start(with context: TransitionContext) {
// This is a fairly rudimentary way to calculate the values on either side of the transition.
// You may want to try different patterns until you find one that you prefer.
// Also consider trying the MotionAnimator library provided by the Material Motion team:
// https://github.com/material-motion/motion-animator-objc
let backOpacity = 0
let foreOpacity = 1
let initialOpacity = context.direction == .forward ? backOpacity : foreOpacity
let finalOpacity = context.direction == .forward ? foreOpacity : backOpacity
context.foreViewController.view.alpha = initialOpacity
UIView.animate(withDuration: context.duration, animations: {
context.foreViewController.view.alpha = finalOpacity

}, completion: { didComplete in
context.transitionDidEnd()
})
}
}
```

### How to customize presentation

You'll customize the presentation of a transition when you need to do any of the following:
Customize the presentation of a transition when you need to do any of the following:

- Add views, such as dimming views, that live beyond the lifetime of the transition.
- Change the destination frame of the presented view controller.

#### Step 1: Subclass UIPresentationController
You have two options for customizing presentation:

You must subclass UIPresentationController in order to implement your custom behavior. If the user
of your transition can customize any presentation behavior then you'll want to define a custom
initializer.
1. Use the provided `TransitionPresentationController` API.
2. Build your own UIPresentationController subclass.

> Note: Avoid storing the transition context in your presentation controller. Presentation
> controllers live for as long as their associated view controller, while the transition context is
> only valid while a transition is active. Each presentation and dismissal will receive its own
> unique transition context. Storing the context in the presentation controller would keep the
> context alive longer than it's meant to.
#### Option 2: Subclass UIPresentationController

Override any `UIPresentationController` methods you'll need in order to implement your motion.
Start by defining a new presentation controller type:

```swift
final class MyPresentationController: UIPresentationController {
}
```

#### Step 2: Implement TransitionWithPresentation on your transition

This ensures that your transition implement the required methods for presentation.

Presentation will only be customized if you return `.custom` from the
`defaultModalPresentationStyle` method and a non-nil `UIPresentationController` subclass from the
`presentationController` method.
Your Transition type must conform to `TransitionWithPresentation` in order to customize
presentation. Return your custom presentation controller class from the required methods and be sure
to return the `.custom` presentation style, otherwise UIKit will not use your presentation
controller.

```swift
extension VerticalSheetTransition: TransitionWithPresentation {
Expand All @@ -231,15 +255,14 @@ extension VerticalSheetTransition: TransitionWithPresentation {
}
```

#### Optional Step 3: Implement Transition on your presentation controller

If your presentation controller needs to animate anything, you can conform to the `Transition`
protocol in order to receive a `start` invocation each time a transition begins. The presentation
controller's `start` will be invoked before the transition's `start`.

> Note: It's possible for your presentation controller and your transition to have different ideas
> of when a transition has completed, so consider which object should be responsible for invoking
> `transitionDidEnd`. The `Transition` object is usually the one that calls this method.
> Note: Just like your transition, your presentation controller must eventually call
> `transitionDidEnd` on its context, otherwise your transition will not complete. This is because
> the transitioning controller waits until all associated transitions have completed before
> informing UIKit of the view controller transition's completion.
```swift
extension MyPresentationController: Transition {
Expand Down
18 changes: 6 additions & 12 deletions examples/ContextualExample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ class ContextualExampleViewController: ExampleViewController {
// Note that in this example we're populating the contextual transition with the tapped view.
// Our rudimentary transition will animate the context view to the center of the screen from its
// current location.
controller.transitionController.transition = ContextualTransition(contextView: tapGesture.view!)
controller.transitionController.transition = CompositeTransition(transitions: [
FadeTransition(target: .foreView),
ContextualTransition(contextView: tapGesture.view!)
])

present(controller, animated: true)
}
Expand Down Expand Up @@ -97,12 +100,6 @@ private class ContextualTransition: NSObject, Transition {
context.transitionDidEnd()
}

let fadeIn = CABasicAnimation(keyPath: "opacity")
fadeIn.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
fadeIn.fromValue = 0
fadeIn.toValue = 1
addAnimationToLayer(fadeIn, context.foreViewController.view.layer)

// We use a snapshot view to accomplish two things:
// 1) To not affect the context view's state.
// 2) To allow our context view to appear in front of the fore view controller's view.
Expand All @@ -128,11 +125,8 @@ private class ContextualTransition: NSObject, Transition {
y: context.foreViewController.view.bounds.midY)
addAnimationToLayer(shift, snapshotContextView.layer)

let fadeOut = CABasicAnimation(keyPath: "opacity")
fadeOut.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
fadeOut.fromValue = 1
fadeOut.toValue = 0
addAnimationToLayer(fadeOut, snapshotContextView.layer)
context.compose(with: FadeTransition(target: .target(snapshotContextView),
style: .fadeOut))

CATransaction.commit()
}
Expand Down
6 changes: 3 additions & 3 deletions examples/CustomPresentationExample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,12 @@ final class VerticalSheetTransition: NSObject, Transition {
}
}

extension VerticalSheetTransition: TransitionWithPresentation, TransitionWithFallback {
extension VerticalSheetTransition: TransitionWithPresentation, TransitionWithFeasibility {

// We customize the transition going forward but fall back to UIKit for dismissal. Our
// presentation controller will govern both of these transitions.
func fallbackTransition(with context: TransitionContext) -> Transition? {
return context.direction == .forward ? self : nil
func canPerformTransition(with context: TransitionContext) -> Bool {
return context.direction == .forward
}

// This method is invoked when we assign the transition to the transition controller. The result
Expand Down
2 changes: 1 addition & 1 deletion examples/FadeExample.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ - (void)didTap {

// The transition controller is an associated object on all UIViewController instances that
// allows you to customize the way the view controller is presented. The primary API on the
// controller that you'll make use of is the `transition` property. Setting this property will
// controller that you'll make use of is the `transitions` property. Setting this property will
// dictate how the view controller is presented. For this example we've built a custom
// FadeTransition, so we'll make use of that now:
viewController.mdm_transitionController.transition = [[FadeTransition alloc] init];
Expand Down
2 changes: 1 addition & 1 deletion examples/FadeExample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class FadeExampleViewController: ExampleViewController {
// controller that you'll make use of is the `transition` property. Setting this property will
// dictate how the view controller is presented. For this example we've built a custom
// FadeTransition, so we'll make use of that now:
modalViewController.transitionController.transition = FadeTransition()
modalViewController.transitionController.transition = FadeTransition(target: .foreView)

// Note that once we assign the transition object to the view controller, the transition will
// govern all subsequent presentations and dismissals of that view controller instance. If we
Expand Down
2 changes: 1 addition & 1 deletion examples/NavControllerFadeExample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class NavControllerFadeExampleViewController: ExampleViewController {
// controller that you'll make use of is the `transition` property. Setting this property will
// dictate how the view controller is presented. For this example we've built a custom
// FadeTransition, so we'll make use of that now:
modalViewController.transitionController.transition = FadeTransition()
modalViewController.transitionController.transition = FadeTransition(target: .foreView)

cachedNavDelegate = navigationController?.delegate

Expand Down
Loading

0 comments on commit 1ddd7da

Please sign in to comment.