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

Commit 0c34f24

Browse files
author
Jeff Verkoeyen
committed
Merge branch 'release-candidate' into stable
2 parents d605986 + 8b23118 commit 0c34f24

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1346
-239
lines changed

CHANGELOG.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,58 @@
1+
# 3.2.0
2+
3+
This minor release introduces new features for presentation, view snapshotting, and defered transition work. There is also a new photo album example demonstrating how to build a contextual transition in which the context may change.
4+
5+
## New features
6+
7+
Transition context now has a `deferToCompletion:` API for deferring work to the completion of the transition.
8+
9+
```swift
10+
// Example (Swift):
11+
foreImageView.isHidden = true
12+
context.defer {
13+
foreImageView.isHidden = false
14+
}
15+
```
16+
17+
`MDMTransitionPresentationController` is a presentation controller that supports presenting view controllers at custom frames and showing an overlay scrim view.
18+
19+
The new `MDMTransitionViewSnapshotter` class can be used to create and manage snapshot views during a transition.
20+
21+
```swift
22+
let snapshotter = TransitionViewSnapshotter(containerView: context.containerView)
23+
context.defer {
24+
snapshotter.removeAllSnapshots()
25+
}
26+
27+
let snapshotView = snapshotter.snapshot(of: view, isAppearing: context.direction == .forward)
28+
```
29+
30+
## Source changes
31+
32+
* [Add a snapshotting API and contextual transition example (#37)](https://github.com/material-motion/transitioning-objc/commit/a6ae314ddd5ff4e6f0ca9a8711348f8682d95e66) (featherless)
33+
* [Store the presentation controller as a weak reference. (#34)](https://github.com/material-motion/transitioning-objc/commit/9f73e70e382ef8291f3ad85f7ccac25994f06e43) (featherless)
34+
* [Add a stock presentation controller implementation. (#35)](https://github.com/material-motion/transitioning-objc/commit/6c98fa24f7e733262dc802b1e7c6b30134a29936) (featherless)
35+
* [Minor formatting adjustment.](https://github.com/material-motion/transitioning-objc/commit/28f6e2e72534c8e0e77b60a98140be3bc06cd37a) (Jeff Verkoeyen)
36+
37+
## API changes
38+
39+
## MDMTransitionContext
40+
41+
*new* method: `deferToCompletion:`. Defers execution of the provided work until the completion of the transition.
42+
43+
## MDMTransitionPresentationController
44+
45+
*new* class: `MDMTransitionPresentationController`. A transition presentation controller implementation that supports animation delegation, a darkened overlay view, and custom presentation frames.
46+
47+
## MDMTransitionViewSnapshotter
48+
49+
*new* class: `MDMTransitionViewSnapshotter`. A view snapshotter creates visual replicas of views so that they may be animated during a transition without adversely affecting the original view hierarchy.
50+
51+
## Non-source changes
52+
53+
* [Add photo album example. (#38)](https://github.com/material-motion/transitioning-objc/commit/a1d49a6f432b7fddf8d15c90a5ea185fd8e03c5a) (featherless)
54+
* [Add some organization to the transition examples. (#36)](https://github.com/material-motion/transitioning-objc/commit/27756b1e578cb8be3fa6d727a3aefafe9b1aa496) (featherless)
55+
156
# 3.1.0
257

358
This minor release resolves a build warning and introduces the ability to customize navigation

MotionTransitioning.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Pod::Spec.new do |s|
22
s.name = "MotionTransitioning"
33
s.summary = "Light-weight API for building UIViewController transitions."
4-
s.version = "3.1.0"
4+
s.version = "3.2.0"
55
s.authors = "The Material Motion Authors"
66
s.license = "Apache 2.0"
77
s.homepage = "https://github.com/material-motion/transitioning-objc"

Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
PODS:
22
- CatalogByConvention (2.1.1)
3-
- MotionTransitioning (3.1.0)
3+
- MotionTransitioning (3.2.0)
44

55
DEPENDENCIES:
66
- CatalogByConvention
@@ -12,7 +12,7 @@ EXTERNAL SOURCES:
1212

1313
SPEC CHECKSUMS:
1414
CatalogByConvention: c3a5319de04250a7cd4649127fcfca5fe3322a43
15-
MotionTransitioning: 5a4188866a5b016f7181dd41c1a14f93809688ec
15+
MotionTransitioning: 93ff3fcc6a597786a01ace3232109e5075c57526
1616

1717
PODFILE CHECKSUM: db2e7ac8d9d65704a2cbffa0b77e39a574cb7248
1818

examples/ContextualExample.swift

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
Copyright 2017-present The Material Motion Authors. All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import UIKit
18+
import MotionTransitioning
19+
20+
// This example demonstrates how to build a contextual transition.
21+
22+
class ContextualExampleViewController: ExampleViewController {
23+
24+
func didTap(_ tapGesture: UITapGestureRecognizer) {
25+
let controller = DestinationViewController()
26+
27+
// A contextual transition is provided with information relevant to the transition, such as the
28+
// view that is being expanded/collapsed. This information can be provided at initialization
29+
// time if it is unlikely to ever change (e.g. a static view on the screen as in this example).
30+
//
31+
// If it's possible for the context to change, then a delegate pattern is a preferred solution
32+
// because it will allow the delegate to request the new context each time the transition
33+
// begins. This can be helpful in building photo album transitions, for example.
34+
//
35+
// Note that in this example we're populating the contextual transition with the tapped view.
36+
// Our rudimentary transition will animate the context view to the center of the screen from its
37+
// current location.
38+
controller.transitionController.transition = ContextualTransition(contextView: tapGesture.view!)
39+
40+
present(controller, animated: true)
41+
}
42+
43+
override func viewDidLoad() {
44+
super.viewDidLoad()
45+
46+
let square = UIView(frame: .init(x: 16, y: 200, width: 128, height: 128))
47+
square.autoresizingMask = [.flexibleLeftMargin, .flexibleTopMargin,
48+
.flexibleRightMargin, .flexibleBottomMargin]
49+
square.backgroundColor = .blue
50+
view.addSubview(square)
51+
52+
let circle = UIView(frame: .init(x: 64, y: 400, width: 128, height: 128))
53+
circle.autoresizingMask = [.flexibleLeftMargin, .flexibleTopMargin,
54+
.flexibleRightMargin, .flexibleBottomMargin]
55+
circle.backgroundColor = .red
56+
view.addSubview(circle)
57+
58+
square.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTap(_:))))
59+
circle.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTap(_:))))
60+
}
61+
62+
override func exampleInformation() -> ExampleInfo {
63+
return .init(title: type(of: self).catalogBreadcrumbs().last!,
64+
instructions: "Tap to present a modal transition.")
65+
}
66+
}
67+
68+
private class ContextualTransition: NSObject, Transition {
69+
70+
// Store the context for the lifetime of the transition.
71+
let contextView: UIView
72+
init(contextView: UIView) {
73+
self.contextView = contextView
74+
}
75+
76+
func start(with context: TransitionContext) {
77+
// A small helper function for creating bi-directional animations.
78+
// See https://github.com/material-motion/motion-animator-objc for a more versatile
79+
// bidirectional Core Animation implementation.
80+
let addAnimationToLayer: (CABasicAnimation, CALayer) -> Void = { animation, layer in
81+
if context.direction == .backward {
82+
let swap = animation.fromValue
83+
animation.fromValue = animation.toValue
84+
animation.toValue = swap
85+
}
86+
layer.add(animation, forKey: animation.keyPath)
87+
layer.setValue(animation.toValue, forKeyPath: animation.keyPath!)
88+
}
89+
90+
let snapshotter = TransitionViewSnapshotter(containerView: context.containerView)
91+
context.defer {
92+
snapshotter.removeAllSnapshots()
93+
}
94+
95+
CATransaction.begin()
96+
CATransaction.setCompletionBlock {
97+
context.transitionDidEnd()
98+
}
99+
100+
let fadeIn = CABasicAnimation(keyPath: "opacity")
101+
fadeIn.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
102+
fadeIn.fromValue = 0
103+
fadeIn.toValue = 1
104+
addAnimationToLayer(fadeIn, context.foreViewController.view.layer)
105+
106+
// We use a snapshot view to accomplish two things:
107+
// 1) To not affect the context view's state.
108+
// 2) To allow our context view to appear in front of the fore view controller's view.
109+
//
110+
// The provided view snapshotter will automatically hide the snapshotted view and remove the
111+
// snapshot view upon completion of the transition.
112+
let snapshotContextView = snapshotter.snapshot(of: contextView,
113+
isAppearing: context.direction == .backward)
114+
115+
let expand = CABasicAnimation(keyPath: "transform.scale.xy")
116+
expand.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
117+
expand.fromValue = 1
118+
expand.toValue = 2
119+
addAnimationToLayer(expand, snapshotContextView.layer)
120+
121+
let shift = CASpringAnimation(keyPath: "position")
122+
shift.damping = 500
123+
shift.stiffness = 1000
124+
shift.mass = 3
125+
shift.duration = 0.5
126+
shift.fromValue = snapshotContextView.layer.position
127+
shift.toValue = CGPoint(x: context.foreViewController.view.bounds.midX,
128+
y: context.foreViewController.view.bounds.midY)
129+
addAnimationToLayer(shift, snapshotContextView.layer)
130+
131+
let fadeOut = CABasicAnimation(keyPath: "opacity")
132+
fadeOut.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
133+
fadeOut.fromValue = 1
134+
fadeOut.toValue = 0
135+
addAnimationToLayer(fadeOut, snapshotContextView.layer)
136+
137+
CATransaction.commit()
138+
}
139+
}
140+
141+
private class DestinationViewController: ExampleViewController {
142+
143+
override func viewDidLoad() {
144+
super.viewDidLoad()
145+
146+
view.backgroundColor = .primaryColor
147+
148+
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTap)))
149+
}
150+
151+
func didTap() {
152+
dismiss(animated: true)
153+
}
154+
}

examples/CustomPresentationExample.swift

Lines changed: 6 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class CustomPresentationExampleViewController: ExampleTableViewController {
2525
override init(style: UITableViewStyle) {
2626
super.init(style: style)
2727

28+
transitions = []
29+
2830
// Aside: we're using a simple model pattern here to define the data for the different
2931
// transitions up separate from their presentation. Check out the `didSelectRowAt`
3032
// implementation to see how we're ultimately presenting the modal view controller.
@@ -68,7 +70,7 @@ final class VerticalSheetTransition: NSObject, Transition {
6870

6971
// When provided, the transition will use a presentation controller to customize the presentation
7072
// of the transition.
71-
var calculateFrameOfPresentedViewInContainerView: CalculateFrame?
73+
var calculateFrameOfPresentedViewInContainerView: TransitionFrameCalculation?
7274

7375
func start(with context: TransitionContext) {
7476
CATransaction.begin()
@@ -122,104 +124,14 @@ extension VerticalSheetTransition: TransitionWithPresentation, TransitionWithFal
122124
presenting: UIViewController,
123125
source: UIViewController?) -> UIPresentationController? {
124126
if let calculateFrameOfPresentedViewInContainerView = calculateFrameOfPresentedViewInContainerView {
125-
return DimmingPresentationController(presentedViewController: presented,
126-
presenting: presenting,
127-
calculateFrameOfPresentedViewInContainerView: calculateFrameOfPresentedViewInContainerView)
127+
return TransitionPresentationController(presentedViewController: presented,
128+
presenting: presenting,
129+
calculateFrameOfPresentedView: calculateFrameOfPresentedViewInContainerView)
128130
}
129131
return nil
130132
}
131133
}
132134

133-
// What follows is a fairly typical presentation controller implementation that adds a dimming view
134-
// and fades the dimming view in/out during the transition.
135-
//
136-
// Note that we've conformed to the Transition type: this allows the presentation controller to
137-
// add any custom animations during the transition. The presentation controller's `start` method
138-
// will be invoked before the Transition object's `start` method.
139-
140-
final class DimmingPresentationController: UIPresentationController {
141-
142-
init(presentedViewController: UIViewController,
143-
presenting presentingViewController: UIViewController,
144-
calculateFrameOfPresentedViewInContainerView: @escaping CalculateFrame) {
145-
let dimmingView = UIView()
146-
dimmingView.backgroundColor = UIColor(white: 0, alpha: 0.3)
147-
dimmingView.alpha = 0
148-
dimmingView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
149-
self.dimmingView = dimmingView
150-
151-
self.calculateFrameOfPresentedViewInContainerView = calculateFrameOfPresentedViewInContainerView
152-
153-
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
154-
}
155-
156-
override var frameOfPresentedViewInContainerView: CGRect {
157-
// We delegate out our frame calculation here:
158-
return calculateFrameOfPresentedViewInContainerView(self)
159-
}
160-
161-
override func presentationTransitionWillBegin() {
162-
guard let containerView = containerView else { return }
163-
164-
dimmingView.frame = containerView.bounds
165-
containerView.insertSubview(dimmingView, at: 0)
166-
167-
// This autoresizing mask assumes that the calculated frame is centered in the screen. This
168-
// assumption won't hold true if the frame is aligned to a particular edge. We could improve
169-
// this implementation by allowing the creator of the transition to customize the
170-
// autoresizingMask in some manner.
171-
presentedViewController.view.autoresizingMask = [.flexibleLeftMargin,
172-
.flexibleTopMargin,
173-
.flexibleRightMargin,
174-
.flexibleBottomMargin]
175-
}
176-
177-
override func presentationTransitionDidEnd(_ completed: Bool) {
178-
if !completed {
179-
dimmingView.removeFromSuperview()
180-
}
181-
}
182-
183-
override func dismissalTransitionWillBegin() {
184-
// We fall back to an alongside fade out when there is no active transition instance because
185-
// our start implementation won't be invoked in this case.
186-
if presentedViewController.transitionController.activeTransition == nil {
187-
presentedViewController.transitionCoordinator?.animate(alongsideTransition: { context in
188-
self.dimmingView.alpha = 0
189-
})
190-
}
191-
}
192-
193-
override func dismissalTransitionDidEnd(_ completed: Bool) {
194-
if completed {
195-
dimmingView.removeFromSuperview()
196-
} else {
197-
dimmingView.alpha = 1
198-
}
199-
}
200-
201-
private let calculateFrameOfPresentedViewInContainerView: CalculateFrame
202-
fileprivate let dimmingView: UIView
203-
}
204-
205-
extension DimmingPresentationController: Transition {
206-
func start(with context: TransitionContext) {
207-
let fade = CABasicAnimation(keyPath: "opacity")
208-
fade.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
209-
fade.fromValue = 0
210-
fade.toValue = 1
211-
if context.direction == .backward {
212-
let swap = fade.fromValue
213-
fade.fromValue = fade.toValue
214-
fade.toValue = swap
215-
}
216-
dimmingView.layer.add(fade, forKey: fade.keyPath)
217-
dimmingView.layer.setValue(fade.toValue, forKeyPath: fade.keyPath!)
218-
}
219-
}
220-
221-
typealias CalculateFrame = (UIPresentationController) -> CGRect
222-
223135
// MARK: Supplemental code
224136

225137
extension CustomPresentationExampleViewController {

0 commit comments

Comments
 (0)