From c1b760455779226ebc9749e06e528d25a6b444bc Mon Sep 17 00:00:00 2001 From: featherless Date: Thu, 25 May 2017 11:55:24 -0400 Subject: [PATCH 1/5] Initial implementation. (#1) * Initial implementation. Includes support for basic transitions and transitions with presentation, two examples, and a complete readme. Code converted from the Swift implementation of Material Motion: https://github.com/material-motion/reactive-motion-swift * Add link to presentation guide. * Make background a callout. * Remove flowery content. * Remove Material Motion from app title. * Fix typo in readme. * Add basic unit tests. * Port the swift fade example to objective-c. * Add objective-c samples to the readme. * Ran arc lint --everything. * Fix arc config for unit tests. * Fix travis yml. --- .arcconfig | 4 +- .travis.yml | 2 +- Podfile | 14 +- Podfile.lock | 10 +- README.md | 226 ++++++++++++++-- ...sitioning.podspec => Transitioning.podspec | 8 +- examples/CustomPresentationExample.swift | 250 ++++++++++++++++++ .../FadeExample.h | 5 +- examples/FadeExample.m | 88 ++++++ examples/FadeExample.swift | 99 +++++++ .../apps/Catalog/Catalog/AppDelegate.swift | 2 +- .../AppIcon.appiconset/Contents.json | 20 ++ examples/apps/Catalog/TableOfContents.swift | 16 +- .../project.pbxproj | 179 ++++++++----- .../xcschemes/TransitionsCatalog.xcscheme} | 26 +- .../xcshareddata/xcschemes/UnitTests.xcscheme | 2 +- .../supplemental/ExampleViewController.swift | 71 +++++ examples/supplemental/ExampleViews.swift | 30 +++ examples/supplemental/HexColor.swift | 44 +++ examples/supplemental/Layout.swift | 25 ++ .../supplemental/ModalViewController.swift | 44 +++ src/MDMTransition.h | 78 ++++++ src/MDMTransitionContext.h | 82 ++++++ src/MDMTransitionController.h | 41 +++ src/Transitioning.h | 20 ++ src/UIViewController+TransitionController.h | 34 +++ src/UIViewController+TransitionController.m | 61 +++++ .../MDMPresentationTransitionController.h | 28 ++ .../MDMPresentationTransitionController.m | 128 +++++++++ .../MDMViewControllerTransitionContext.h | 44 +++ .../MDMViewControllerTransitionContext.m | 152 +++++++++++ tests/unit/TransitionTests.swift | 52 ++++ .../TransitionWithPresentationTests.swift | 63 +++++ 33 files changed, 1828 insertions(+), 120 deletions(-) rename MaterialMotionTransitioning.podspec => Transitioning.podspec (57%) create mode 100644 examples/CustomPresentationExample.swift rename src/MaterialMotionTransitioning.h => examples/FadeExample.h (87%) create mode 100644 examples/FadeExample.m create mode 100644 examples/FadeExample.swift rename examples/apps/Catalog/{Catalog.xcodeproj => TransitionsCatalog.xcodeproj}/project.pbxproj (70%) rename examples/apps/Catalog/{Catalog.xcodeproj/xcshareddata/xcschemes/Catalog.xcscheme => TransitionsCatalog.xcodeproj/xcshareddata/xcschemes/TransitionsCatalog.xcscheme} (79%) rename examples/apps/Catalog/{Catalog.xcodeproj => TransitionsCatalog.xcodeproj}/xcshareddata/xcschemes/UnitTests.xcscheme (95%) create mode 100644 examples/supplemental/ExampleViewController.swift create mode 100644 examples/supplemental/ExampleViews.swift create mode 100644 examples/supplemental/HexColor.swift create mode 100644 examples/supplemental/Layout.swift create mode 100644 examples/supplemental/ModalViewController.swift create mode 100644 src/MDMTransition.h create mode 100644 src/MDMTransitionContext.h create mode 100644 src/MDMTransitionController.h create mode 100644 src/Transitioning.h create mode 100644 src/UIViewController+TransitionController.h create mode 100644 src/UIViewController+TransitionController.m create mode 100644 src/private/MDMPresentationTransitionController.h create mode 100644 src/private/MDMPresentationTransitionController.m create mode 100644 src/private/MDMViewControllerTransitionContext.h create mode 100644 src/private/MDMViewControllerTransitionContext.m create mode 100644 tests/unit/TransitionTests.swift create mode 100644 tests/unit/TransitionWithPresentationTests.swift diff --git a/.arcconfig b/.arcconfig index 4c65feb..528c27c 100644 --- a/.arcconfig +++ b/.arcconfig @@ -13,13 +13,13 @@ "arc.feature.start.default": "origin/develop", "unit.xcode": { "build": { - "workspace": "MaterialMotionTransitioning.xcworkspace", + "workspace": "Transitioning.xcworkspace", "scheme": "UnitTests", "configuration": "Debug", "destination": "platform=iOS Simulator,name=iPhone 6s" }, "coverage": { - "product": "MaterialMotionTransitioning.framework/MaterialMotionTransitioning" + "product": "Transitioning.framework/Transitioning" }, "pre-build": "pod install" } diff --git a/.travis.yml b/.travis.yml index c38585d..18e29e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,6 @@ before_install: script: - set -o pipefail - arcanist/bin/arc unit --everything --trace - - xcodebuild build -workspace MaterialMotionTransitioning.xcworkspace -scheme Catalog -sdk "iphonesimulator10.1" -destination "name=iPhone 6s,OS=10.1" ONLY_ACTIVE_ARCH=YES | xcpretty -c; + - xcodebuild build -workspace Transitioning.xcworkspace -scheme TransitionsCatalog -sdk "iphonesimulator10.1" -destination "name=iPhone 6s,OS=10.1" ONLY_ACTIVE_ARCH=YES | xcpretty -c; after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/Podfile b/Podfile index c62e6d7..7155316 100644 --- a/Podfile +++ b/Podfile @@ -1,24 +1,24 @@ -workspace 'MaterialMotionTransitioning.xcworkspace' +workspace 'Transitioning.xcworkspace' use_frameworks! -target "Catalog" do +target "TransitionsCatalog" do pod 'CatalogByConvention' - pod 'MaterialMotionTransitioning', :path => './' + pod 'Transitioning', :path => './' - project 'examples/apps/Catalog/Catalog.xcodeproj' + project 'examples/apps/Catalog/TransitionsCatalog.xcodeproj' end target "UnitTests" do - pod 'MaterialMotionTransitioning', :path => './' + pod 'Transitioning', :path => './' - project 'examples/apps/Catalog/Catalog.xcodeproj' + project 'examples/apps/Catalog/TransitionsCatalog.xcodeproj' end post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |configuration| configuration.build_settings['SWIFT_VERSION'] = "3.0" - if target.name.start_with?("Material") + if target.name.start_with?("Transitioning") configuration.build_settings['WARNING_CFLAGS'] ="$(inherited) -Wall -Wcast-align -Wconversion -Werror -Wextra -Wimplicit-atomic-properties -Wmissing-prototypes -Wno-sign-conversion -Wno-unused-parameter -Woverlength-strings -Wshadow -Wstrict-selector-match -Wundeclared-selector -Wunreachable-code" end end diff --git a/Podfile.lock b/Podfile.lock index 207ee7f..8a3cc06 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,19 +1,19 @@ PODS: - CatalogByConvention (2.1.1) - - MaterialMotionTransitioning (1.0.0) + - Transitioning (1.0.0) DEPENDENCIES: - CatalogByConvention - - MaterialMotionTransitioning (from `./`) + - Transitioning (from `./`) EXTERNAL SOURCES: - MaterialMotionTransitioning: + Transitioning: :path: "./" SPEC CHECKSUMS: CatalogByConvention: c3a5319de04250a7cd4649127fcfca5fe3322a43 - MaterialMotionTransitioning: 33406d4f24065281e3d2d171bddd794a89e32b7c + Transitioning: d2d2d0609e0acf133f7049ff5ddbe7ca16973f07 -PODFILE CHECKSUM: 7343cb186774b759ce5a8ae9aa6df20737289df1 +PODFILE CHECKSUM: 1949e62e9d70d554783c0bb931d6b52780775cfb COCOAPODS: 1.2.1 diff --git a/README.md b/README.md index fb6e2bd..477d301 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,66 @@ -# Material Motion Transitioning +# Transitioning -[![Build Status](https://travis-ci.org/material-motion/material-motion-transitioning-objc.svg?branch=develop)](https://travis-ci.org/material-motion/material-motion-transitioning-objc) -[![codecov](https://codecov.io/gh/material-motion/material-motion-transitioning-objc/branch/develop/graph/badge.svg)](https://codecov.io/gh/material-motion/material-motion-transitioning-objc) -[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/MaterialMotionTransitioning.svg)](https://cocoapods.org/pods/MaterialMotionTransitioning) -[![Platform](https://img.shields.io/cocoapods/p/MaterialMotionTransitioning.svg)](http://cocoadocs.org/docsets/MaterialMotionTransitioning) -[![Docs](https://img.shields.io/cocoapods/metrics/doc-percent/MaterialMotionTransitioning.svg)](http://cocoadocs.org/docsets/MaterialMotionTransitioning) +> Light-weight API for building UIViewController transitions. + +[![Build Status](https://travis-ci.org/material-motion/transitioning-objc.svg?branch=develop)](https://travis-ci.org/material-motion/transitioning-objc) +[![codecov](https://codecov.io/gh/material-motion/transitioning-objc/branch/develop/graph/badge.svg)](https://codecov.io/gh/material-motion/transitioning-objc) +[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/Transitioning.svg)](https://cocoapods.org/pods/Transitioning) +[![Platform](https://img.shields.io/cocoapods/p/Transitioning.svg)](http://cocoadocs.org/docsets/Transitioning) +[![Docs](https://img.shields.io/cocoapods/metrics/doc-percent/Transitioning.svg)](http://cocoadocs.org/docsets/Transitioning) + +This library standardizes the way transitions are built on iOS so that with a single line of code +you can pick the custom transition you want to use: + +```swift +let viewController = MyViewController() +viewController.transitionController.transition = CustomTransition() +present(modalViewController, animated: true) +``` + +```objc +MyViewController *viewController = [[MyViewController alloc] init]; +viewController.mdm_transitionController.transition = [[CustomTransition alloc] init]; +[self presentViewController:viewController animated:true completion:nil]; +``` + +The easiest way to make a transition with this library is to create a class that conforms to the +`Transition` protocol: + +```swift +final class CustomTransition: NSObject, Transition { + func start(with context: TransitionContext) { + CATransaction.begin() + + CATransaction.setCompletionBlock { + context.transitionDidEnd() + } + + // Add animations... + + CATransaction.commit() + } +} +``` + +```objc +@interface CustomTransition: NSObject +@end + +@implementation CustomTransition + +- (void)startWithContext:(id)context { + [CATransaction begin]; + [CATransaction setCompletionBlock:^{ + [context transitionDidEnd]; + }]; + + // Add animations... + + [CATransaction commit]; +} + +@end +``` ## Installation @@ -17,9 +73,9 @@ > > gem install cocoapods -Add `MaterialMotionTransitioning` to your `Podfile`: +Add `Transitioning` to your `Podfile`: - pod 'MaterialMotionTransitioning' + pod 'Transitioning' Then run the following command: @@ -29,7 +85,7 @@ Then run the following command: Import the framework: - @import MaterialMotionTransitioning; + @import Transitioning; You will now have access to all of the APIs. @@ -38,25 +94,165 @@ You will now have access to all of the APIs. Check out a local copy of the repo to access the Catalog application by running the following commands: - git clone https://github.com/material-motion/material-motion-transitioning-objc.git - cd material-motion-transitioning-objc + git clone https://github.com/material-motion/transitioning-objc.git + cd transitioning-objc pod install - open MaterialMotionTransitioning.xcworkspace + open Transitioning.xcworkspace ## Guides 1. [Architecture](#architecture) -2. [How to ...](#how-to-...) +2. [How to create a simple transition](#how-to-create-a-simple-transition) +3. [How to customize presentation](#how-to-customize-presentation) ### Architecture -### How to ... +> Background: Transitions in iOS are customized by setting a `transitioningDelegate` on a view +> controller. When a view controller is presented, UIKit will ask the transitioning delegate for an +> animation, interaction, and presentation controller. These controllers are then expected to +> implement the transition's motion. + +Transitioning provides a thin layer atop these protocols with the following advantages: + +- 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**. + +### How to create a simple transition + +In this guide we'll create scaffolding for a simple transition. + +#### Step 1: Define a new Transition type + +Transitions must be `NSObject` types that conform to the `Transition` protocol. + +The sole method we're expected to implement, `start`, is invoked each time the view controller is +presented or dismissed. + +```swift +final class FadeTransition: NSObject, Transition { + func start(with context: TransitionContext) { + + } +} +``` + +#### Step 2: Invoke the completion handler once all animations are complete + +If using Core Animation explicitly: + +```swift +final class FadeTransition: NSObject, Transition { + func start(with context: TransitionContext) { + CATransaction.begin() + + CATransaction.setCompletionBlock { + context.transitionDidEnd() + } + + // Your motion... + + CATransaction.commit() + } +} +``` + +If using UIView implicit animations: + +```swift +final class FadeTransition: NSObject, Transition { + func start(with context: TransitionContext) { + UIView.animate(withDuration: context.duration, animations: { + // Your motion... + + }, completion: { didComplete in + context.transitionDidEnd() + }) + } +} +``` + +#### Step 3: Implement the motion + +With the basic scaffolding in place, you can now implement your motion. + +### How to customize presentation + +You'll 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 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. + +> 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. + +Override any `UIPresentationController` methods you'll need in order to implement your motion. + +```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. + +```swift +extension VerticalSheetTransition: TransitionWithPresentation { + func defaultModalPresentationStyle() -> UIModalPresentationStyle { + return .custom + } + + func presentationController(forPresented presented: UIViewController, + presenting: UIViewController, + source: UIViewController?) -> UIPresentationController? { + return MyPresentationController(presentedViewController: presented, presenting: presenting) + } +} +``` + +#### 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. + +```swift +extension MyPresentationController: Transition { + func start(with context: TransitionContext) { + // Your motion... + } +} +``` ## Contributing We welcome contributions! -Check out our [upcoming milestones](https://github.com/material-motion/material-motion-transitioning-objc/milestones). +Check out our [upcoming milestones](https://github.com/material-motion/transitioning-objc/milestones). Learn more about [our team](https://material-motion.github.io/material-motion/team/), [our community](https://material-motion.github.io/material-motion/team/community/), and diff --git a/MaterialMotionTransitioning.podspec b/Transitioning.podspec similarity index 57% rename from MaterialMotionTransitioning.podspec rename to Transitioning.podspec index 16b8281..4b70fbd 100644 --- a/MaterialMotionTransitioning.podspec +++ b/Transitioning.podspec @@ -1,11 +1,11 @@ Pod::Spec.new do |s| - s.name = "MaterialMotionTransitioning" - s.summary = "Material Motion Transitioning" + s.name = "Transitioning" + s.summary = "Light-weight API for building UIViewController transitions." s.version = "1.0.0" s.authors = "The Material Motion Authors" s.license = "Apache 2.0" - s.homepage = "https://github.com/material-motion/material-motion-transitioning-objc" - s.source = { :git => "https://github.com/material-motion/material-motion-transitioning-objc.git", :tag => "v" + s.version.to_s } + s.homepage = "https://github.com/material-motion/transitioning-objc" + s.source = { :git => "https://github.com/material-motion/transitioning-objc.git", :tag => "v" + s.version.to_s } s.platform = :ios, "8.0" s.requires_arc = true diff --git a/examples/CustomPresentationExample.swift b/examples/CustomPresentationExample.swift new file mode 100644 index 0000000..368b910 --- /dev/null +++ b/examples/CustomPresentationExample.swift @@ -0,0 +1,250 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit +import Transitioning + +// This example demonstrates how to make use of presentation controllers to build a flexible modal +// transition that supports presenting view controllers at aribtrary frames on the screen. + +class CustomPresentationExampleViewController: ExampleTableViewController { + + override init(style: UITableViewStyle) { + super.init(style: style) + + // Aside: we're using a simple model pattern here to define the data for the different + // transitions up separate from their presentation. Check out the `didSelectRowAt` + // implementation to see how we're ultimately presenting the modal view controller. + + // By default, the vertical sheet transition will behave like a full screen transition... + transitions.append(.init(name: "Vertical sheet", transition: VerticalSheetTransition())) + + // ...but we can also customize the frame of the presented view controller by providing a frame + // calculation block. + let modalDialog = VerticalSheetTransition() + modalDialog.calculateFrameOfPresentedViewInContainerView = { info in + // Note: this block is retained for the lifetime of the view controller, so be careful not to + // create a memory loop by referencing self or the presented view controller directly - use + // the provided info structure to access these values instead. + + // Center the dialog in the container view. + let size = CGSize(width: 200, height: 200) + return CGRect(x: (info.containerView.bounds.width - size.width) / 2, + y: (info.containerView.bounds.height - size.height) / 2, + width: size.width, + height: size.height) + } + transitions.append(.init(name: "Modal dialog", transition: modalDialog)) + } + + override func exampleInformation() -> ExampleInfo { + return .init(title: type(of: self).catalogBreadcrumbs().last!, + instructions: "Tap to present a modal transition.") + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +final class VerticalSheetTransition: NSObject, Transition { + + // When provided, the transition will use a presentation controller to customize the presentation + // of the transition. + var calculateFrameOfPresentedViewInContainerView: CalculateFrame? + + func start(with context: TransitionContext) { + CATransaction.begin() + CATransaction.setCompletionBlock { + context.transitionDidEnd() + } + + let shift = CASpringAnimation(keyPath: "position.y") + + // These values are extracted from UIKit's default modal presentation animation. + shift.damping = 500 + shift.stiffness = 1000 + shift.mass = 3 + shift.duration = 0.5 + + // Start off-screen... + shift.fromValue = context.containerView.bounds.height + context.foreViewController.view.layer.bounds.height / 2 + // ...and shift on-screen. + shift.toValue = context.foreViewController.view.layer.position.y + + if context.direction == .backward { + let swap = shift.fromValue + shift.fromValue = shift.toValue + shift.toValue = swap + } + context.foreViewController.view.layer.add(shift, forKey: shift.keyPath) + context.foreViewController.view.layer.setValue(shift.toValue, forKeyPath: shift.keyPath!) + + CATransaction.commit() + } +} + +extension VerticalSheetTransition: TransitionWithPresentation { + + // This method is invoked when we assign the transition to the transition controller. The result + // is assigned to the view controller's modalPresentationStyle property. + func defaultModalPresentationStyle() -> UIModalPresentationStyle { + if calculateFrameOfPresentedViewInContainerView != nil { + return .custom + } + return .fullScreen + } + + func presentationController(forPresented presented: UIViewController, + presenting: UIViewController, + source: UIViewController?) -> UIPresentationController? { + if let calculateFrameOfPresentedViewInContainerView = calculateFrameOfPresentedViewInContainerView { + return DimmingPresentationController(presentedViewController: presented, + presenting: presenting, + calculateFrameOfPresentedViewInContainerView: calculateFrameOfPresentedViewInContainerView) + } + return nil + } +} + +// What follows is a fairly typical presentation controller implementation that adds a dimming view +// and fades the dimming view in/out during the transition. +// +// Note that we've conformed to the Transition type: this allows the presentation controller to +// add any custom animations during the transition. The presentation controller's `start` method +// will be invoked before the Transition object's `start` method. + +final class DimmingPresentationController: UIPresentationController { + + init(presentedViewController: UIViewController, + presenting presentingViewController: UIViewController, + calculateFrameOfPresentedViewInContainerView: @escaping CalculateFrame) { + let dimmingView = UIView() + dimmingView.backgroundColor = UIColor(white: 0, alpha: 0.3) + dimmingView.alpha = 0 + dimmingView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + self.dimmingView = dimmingView + + self.calculateFrameOfPresentedViewInContainerView = calculateFrameOfPresentedViewInContainerView + + super.init(presentedViewController: presentedViewController, presenting: presentingViewController) + } + + override var frameOfPresentedViewInContainerView: CGRect { + guard let containerView = containerView else { + assertionFailure("Missing container view during frame query.") + return .zero + } + // We delegate out our frame calculation here: + let info = CalculateFrameInfo(containerView: containerView, + presentingViewController: presentingViewController, + presentedViewController: presentedViewController) + return calculateFrameOfPresentedViewInContainerView(info) + } + + override func presentationTransitionWillBegin() { + guard let containerView = containerView else { return } + + dimmingView.frame = containerView.bounds + containerView.insertSubview(dimmingView, at: 0) + + // This autoresizing mask assumes that the calculated frame is centered in the screen. This + // assumption won't hold true if the frame is aligned to a particular edge. We could improve + // this implementation by allowing the creator of the transition to customize the + // autoresizingMask in some manner. + presentedViewController.view.autoresizingMask = [.flexibleLeftMargin, + .flexibleTopMargin, + .flexibleRightMargin, + .flexibleBottomMargin] + } + + override func presentationTransitionDidEnd(_ completed: Bool) { + if !completed { + dimmingView.removeFromSuperview() + } + } + + override func dismissalTransitionDidEnd(_ completed: Bool) { + if completed { + dimmingView.removeFromSuperview() + } + } + + private let calculateFrameOfPresentedViewInContainerView: CalculateFrame + fileprivate let dimmingView: UIView +} + +extension DimmingPresentationController: Transition { + func start(with context: TransitionContext) { + let fade = CABasicAnimation(keyPath: "opacity") + fade.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + fade.fromValue = 0 + fade.toValue = 1 + if context.direction == .backward { + let swap = fade.fromValue + fade.fromValue = fade.toValue + fade.toValue = swap + } + dimmingView.layer.add(fade, forKey: fade.keyPath) + dimmingView.layer.setValue(fade.toValue, forKeyPath: fade.keyPath!) + } +} + +typealias CalculateFrame = (CalculateFrameInfo) -> CGRect + +struct CalculateFrameInfo { + let containerView: UIView + let presentingViewController: UIViewController + let presentedViewController: UIViewController +} + +// MARK: Supplemental code + +extension CustomPresentationExampleViewController { + override func viewDidLoad() { + super.viewDidLoad() + + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") + } +} + +struct TransitionInfo { + let name: String + let transition: Transition +} +var transitions: [TransitionInfo] = [] + +extension CustomPresentationExampleViewController { + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return transitions.count + } + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) + cell.textLabel?.text = transitions[indexPath.row].name + return cell + } +} + +extension CustomPresentationExampleViewController { + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let modal = ModalViewController() + modal.transitionController.transition = transitions[indexPath.row].transition + showDetailViewController(modal, sender: self) + } +} diff --git a/src/MaterialMotionTransitioning.h b/examples/FadeExample.h similarity index 87% rename from src/MaterialMotionTransitioning.h rename to examples/FadeExample.h index 1c08184..f22f70b 100644 --- a/src/MaterialMotionTransitioning.h +++ b/examples/FadeExample.h @@ -14,4 +14,7 @@ limitations under the License. */ -// TODO: Import public headers. +#import + +@interface FadeExampleObjcViewController : UIViewController +@end diff --git a/examples/FadeExample.m b/examples/FadeExample.m new file mode 100644 index 0000000..26568c7 --- /dev/null +++ b/examples/FadeExample.m @@ -0,0 +1,88 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FadeExample.h" + +#import "TransitionsCatalog-Swift.h" + +// This example demonstrates the minimal path to building a custom transition using the Material +// Motion Transitioning APIs in Objective-C. Please see the companion Swift implementation for +// detailed comments. + +@interface FadeTransition : NSObject +@end + +@implementation FadeExampleObjcViewController + +- (void)didTap { + ModalViewController *viewController = [[ModalViewController alloc] init]; + + viewController.mdm_transitionController.transition = [[FadeTransition alloc] init]; + + [self presentViewController:viewController animated:true completion:nil]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor backgroundColor]; + + UILabel *label = [[UILabel alloc] initWithFrame:self.view.bounds]; + label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + label.textColor = [UIColor whiteColor]; + label.textAlignment = NSTextAlignmentCenter; + label.text = @"Tap to start the transition"; + [self.view addSubview:label]; + + UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self + action:@selector(didTap)]; + [self.view addGestureRecognizer:tap]; +} + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"1. Fade transition (objc)" ]; +} + +@end + +@implementation FadeTransition + +- (void)startWithContext:(id)context { + [CATransaction begin]; + [CATransaction setCompletionBlock:^{ + [context transitionDidEnd]; + }]; + + CABasicAnimation *fade = [CABasicAnimation animationWithKeyPath:@"opacity"]; + + fade.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + + fade.fromValue = @0; + fade.toValue = @1; + + if (context.direction == MDMTransitionDirectionBackward) { + id swap = fade.fromValue; + fade.fromValue = fade.toValue; + fade.toValue = swap; + } + + [context.foreViewController.view.layer addAnimation:fade forKey:fade.keyPath]; + [context.foreViewController.view.layer setValue:fade.toValue forKey:fade.keyPath]; + + [CATransaction commit]; +} + +@end diff --git a/examples/FadeExample.swift b/examples/FadeExample.swift new file mode 100644 index 0000000..8d2d088 --- /dev/null +++ b/examples/FadeExample.swift @@ -0,0 +1,99 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit +import Transitioning + +// This example demonstrates the minimal path to building a custom transition using the Material +// Motion Transitioning APIs in Swift. The essential steps have been documented below. + +class FadeExampleViewController: ExampleViewController { + + func didTap() { + let modalViewController = ModalViewController() + + // 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 + // 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() + + // 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 + // want to use a different transition (e.g. to use an edge-swipe-to-dismiss transition) then we + // can simply change the transition object before initiating the transition. + + present(modalViewController, animated: true) + } + + override func viewDidLoad() { + super.viewDidLoad() + + let label = UILabel(frame: view.bounds) + label.autoresizingMask = [.flexibleWidth, .flexibleHeight] + label.textColor = .white + label.textAlignment = .center + label.text = "Tap to start the transition" + view.addSubview(label) + + let tap = UITapGestureRecognizer(target: self, action: #selector(didTap)) + view.addGestureRecognizer(tap) + } + + override func exampleInformation() -> ExampleInfo { + return .init(title: type(of: self).catalogBreadcrumbs().last!, + instructions: "Tap to present a modal transition.") + } +} + +// Transitions must be NSObject types that conform to the Transition protocol. +private final class FadeTransition: NSObject, Transition { + + // The sole method we're expected to implement, start is invoked each time the view controller is + // presented or dismissed. + func start(with context: TransitionContext) { + CATransaction.begin() + + CATransaction.setCompletionBlock { + // Let UIKit know that the transition has come to an end. + context.transitionDidEnd() + } + + let fade = CABasicAnimation(keyPath: "opacity") + + fade.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + + // Define our animation assuming that we're going forward (presenting)... + fade.fromValue = 0 + fade.toValue = 1 + + // ...and reverse it if we're going backwards (dismissing). + if context.direction == .backward { + let swap = fade.fromValue + fade.fromValue = fade.toValue + fade.toValue = swap + } + + // Add the animation... + context.foreViewController.view.layer.add(fade, forKey: fade.keyPath) + + // ...and ensure that our model layer reflects the final value. + context.foreViewController.view.layer.setValue(fade.toValue, forKeyPath: fade.keyPath!) + + CATransaction.commit() + } +} diff --git a/examples/apps/Catalog/Catalog/AppDelegate.swift b/examples/apps/Catalog/Catalog/AppDelegate.swift index 7d13ff6..670feb0 100644 --- a/examples/apps/Catalog/Catalog/AppDelegate.swift +++ b/examples/apps/Catalog/Catalog/AppDelegate.swift @@ -27,7 +27,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { self.window = window let rootViewController = CBCNodeListViewController(node: CBCCreateNavigationTree()) - rootViewController.title = "Material Motion Transitioning" + rootViewController.title = "Transitioning" window.rootViewController = UINavigationController(rootViewController: rootViewController) window.makeKeyAndVisible() diff --git a/examples/apps/Catalog/Catalog/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/apps/Catalog/Catalog/Assets.xcassets/AppIcon.appiconset/Contents.json index eeea76c..1d060ed 100644 --- a/examples/apps/Catalog/Catalog/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/examples/apps/Catalog/Catalog/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,5 +1,15 @@ { "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, { "idiom" : "iphone", "size" : "29x29", @@ -30,6 +40,16 @@ "size" : "60x60", "scale" : "3x" }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, { "idiom" : "ipad", "size" : "29x29", diff --git a/examples/apps/Catalog/TableOfContents.swift b/examples/apps/Catalog/TableOfContents.swift index b44f6e3..f7903ad 100644 --- a/examples/apps/Catalog/TableOfContents.swift +++ b/examples/apps/Catalog/TableOfContents.swift @@ -16,12 +16,10 @@ // MARK: Catalog by convention -// Example entry in the table of contents: -// Extend a UIViewController instance and implement catalogBreadcrumbs(), returning the list of -// breadcrumbs required to navigate to an instance of this view controller. -// -//extension ExampleViewController { -// class func catalogBreadcrumbs() -> [String] { -// return ["Example"] -// } -//} +extension FadeExampleViewController { + class func catalogBreadcrumbs() -> [String] { return ["1. Fade transition"] } +} + +extension CustomPresentationExampleViewController { + class func catalogBreadcrumbs() -> [String] { return ["2. Custom presentation transitions"] } +} diff --git a/examples/apps/Catalog/Catalog.xcodeproj/project.pbxproj b/examples/apps/Catalog/TransitionsCatalog.xcodeproj/project.pbxproj similarity index 70% rename from examples/apps/Catalog/Catalog.xcodeproj/project.pbxproj rename to examples/apps/Catalog/TransitionsCatalog.xcodeproj/project.pbxproj index 3e49618..a7d9fd6 100644 --- a/examples/apps/Catalog/Catalog.xcodeproj/project.pbxproj +++ b/examples/apps/Catalog/TransitionsCatalog.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 6629151E1ED5E0E0002B9A5D /* CustomPresentationExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6629151D1ED5E0E0002B9A5D /* CustomPresentationExample.swift */; }; + 662915201ED5E137002B9A5D /* ModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6629151F1ED5E137002B9A5D /* ModalViewController.swift */; }; + 662915231ED64A10002B9A5D /* TransitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662915221ED64A10002B9A5D /* TransitionTests.swift */; }; 666FAA841D384A6B000363DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 666FAA831D384A6B000363DA /* AppDelegate.swift */; }; 666FAA8B1D384A6B000363DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 666FAA8A1D384A6B000363DA /* Assets.xcassets */; }; 666FAA8E1D384A6B000363DA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 666FAA8C1D384A6B000363DA /* LaunchScreen.storyboard */; }; @@ -14,8 +17,15 @@ 667A3F491DEE269400CB3A99 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 667A3F481DEE269400CB3A99 /* Assets.xcassets */; }; 667A3F4C1DEE269400CB3A99 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 667A3F4A1DEE269400CB3A99 /* LaunchScreen.storyboard */; }; 667A3F541DEE273000CB3A99 /* TableOfContents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 667A3F531DEE273000CB3A99 /* TableOfContents.swift */; }; - 7C748E764C1244B0A12E72DE /* Pods_Catalog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B7DADAAF1F30D9DD46FECA7 /* Pods_Catalog.framework */; }; - D8AF137CF53ADAD2F2E52ABD /* Pods_UnitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D90C2397D3F61B958D5B442E /* Pods_UnitTests.framework */; }; + 66BBC75E1ED37DAD0015CB9B /* FadeExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66BBC75D1ED37DAD0015CB9B /* FadeExample.swift */; }; + 66BBC76D1ED4C8790015CB9B /* ExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66BBC7691ED4C8790015CB9B /* ExampleViewController.swift */; }; + 66BBC76E1ED4C8790015CB9B /* ExampleViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66BBC76A1ED4C8790015CB9B /* ExampleViews.swift */; }; + 66BBC76F1ED4C8790015CB9B /* HexColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66BBC76B1ED4C8790015CB9B /* HexColor.swift */; }; + 66BBC7701ED4C8790015CB9B /* Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66BBC76C1ED4C8790015CB9B /* Layout.swift */; }; + 66BBC7721ED728DB0015CB9B /* TransitionWithPresentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66BBC7711ED728DB0015CB9B /* TransitionWithPresentationTests.swift */; }; + 66BBC7751ED729A80015CB9B /* FadeExample.m in Sources */ = {isa = PBXBuildFile; fileRef = 66BBC7741ED729A70015CB9B /* FadeExample.m */; }; + 773CE08578E72083CED81F9E /* Pods_TransitionsCatalog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 56D9DF9E44D993D12FE85E99 /* Pods_TransitionsCatalog.framework */; }; + FA87211112A578A2487B6013 /* Pods_UnitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2408A4B72C0BA93CC963452F /* Pods_UnitTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -36,8 +46,15 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 26CB86119175B12BFEDB6611 /* Pods-Catalog.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Catalog.release.xcconfig"; path = "../../../Pods/Target Support Files/Pods-Catalog/Pods-Catalog.release.xcconfig"; sourceTree = ""; }; - 666FAA801D384A6B000363DA /* Catalog.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Catalog.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 0C2327F961D4F16DEBF0EEB8 /* Pods-UnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnitTests.debug.xcconfig"; path = "../../../Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests.debug.xcconfig"; sourceTree = ""; }; + 2408A4B72C0BA93CC963452F /* Pods_UnitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_UnitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3734DFFD1C84494E48784617 /* Pods-TransitionsCatalog.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TransitionsCatalog.release.xcconfig"; path = "../../../Pods/Target Support Files/Pods-TransitionsCatalog/Pods-TransitionsCatalog.release.xcconfig"; sourceTree = ""; }; + 56D9DF9E44D993D12FE85E99 /* Pods_TransitionsCatalog.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TransitionsCatalog.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6629151D1ED5E0E0002B9A5D /* CustomPresentationExample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomPresentationExample.swift; sourceTree = ""; }; + 6629151F1ED5E137002B9A5D /* ModalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModalViewController.swift; sourceTree = ""; }; + 662915211ED5F222002B9A5D /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../../../README.md; sourceTree = ""; }; + 662915221ED64A10002B9A5D /* TransitionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionTests.swift; sourceTree = ""; }; + 666FAA801D384A6B000363DA /* TransitionsCatalog.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TransitionsCatalog.app; sourceTree = BUILT_PRODUCTS_DIR; }; 666FAA831D384A6B000363DA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Catalog/AppDelegate.swift; sourceTree = ""; }; 666FAA8A1D384A6B000363DA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 666FAA8D1D384A6B000363DA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; @@ -50,11 +67,16 @@ 667A3F4B1DEE269400CB3A99 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 667A3F4D1DEE269400CB3A99 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 667A3F531DEE273000CB3A99 /* TableOfContents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableOfContents.swift; sourceTree = ""; }; - 7B7DADAAF1F30D9DD46FECA7 /* Pods_Catalog.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Catalog.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 7D4F5FB9DCA29E6CF4DEBD9A /* Pods-Catalog.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Catalog.debug.xcconfig"; path = "../../../Pods/Target Support Files/Pods-Catalog/Pods-Catalog.debug.xcconfig"; sourceTree = ""; }; - 9221E13BAA68CADCE55AB64D /* Pods-UnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnitTests.debug.xcconfig"; path = "../../../Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests.debug.xcconfig"; sourceTree = ""; }; - D90C2397D3F61B958D5B442E /* Pods_UnitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_UnitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - FEDDDD6B137529E9B197CF6A /* Pods-UnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnitTests.release.xcconfig"; path = "../../../Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests.release.xcconfig"; sourceTree = ""; }; + 66BBC75D1ED37DAD0015CB9B /* FadeExample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FadeExample.swift; sourceTree = ""; }; + 66BBC7691ED4C8790015CB9B /* ExampleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleViewController.swift; sourceTree = ""; }; + 66BBC76A1ED4C8790015CB9B /* ExampleViews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleViews.swift; sourceTree = ""; }; + 66BBC76B1ED4C8790015CB9B /* HexColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HexColor.swift; sourceTree = ""; }; + 66BBC76C1ED4C8790015CB9B /* Layout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Layout.swift; sourceTree = ""; }; + 66BBC7711ED728DB0015CB9B /* TransitionWithPresentationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionWithPresentationTests.swift; sourceTree = ""; }; + 66BBC7731ED729A70015CB9B /* FadeExample.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FadeExample.h; sourceTree = ""; }; + 66BBC7741ED729A70015CB9B /* FadeExample.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FadeExample.m; sourceTree = ""; }; + 738D98979677D88D24513391 /* Pods-TransitionsCatalog.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TransitionsCatalog.debug.xcconfig"; path = "../../../Pods/Target Support Files/Pods-TransitionsCatalog/Pods-TransitionsCatalog.debug.xcconfig"; sourceTree = ""; }; + D7BB2931AFCEE4C91AE92E5D /* Pods-UnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnitTests.release.xcconfig"; path = "../../../Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -62,7 +84,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D8AF137CF53ADAD2F2E52ABD /* Pods_UnitTests.framework in Frameworks */, + FA87211112A578A2487B6013 /* Pods_UnitTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -70,7 +92,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7C748E764C1244B0A12E72DE /* Pods_Catalog.framework in Frameworks */, + 773CE08578E72083CED81F9E /* Pods_TransitionsCatalog.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -84,35 +106,35 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 576A22BB98FC22437912573E /* Pods */ = { + 370037A4F3C3DB841D4FFC9F /* Frameworks */ = { isa = PBXGroup; children = ( - 7D4F5FB9DCA29E6CF4DEBD9A /* Pods-Catalog.debug.xcconfig */, - 26CB86119175B12BFEDB6611 /* Pods-Catalog.release.xcconfig */, - 9221E13BAA68CADCE55AB64D /* Pods-UnitTests.debug.xcconfig */, - FEDDDD6B137529E9B197CF6A /* Pods-UnitTests.release.xcconfig */, + 56D9DF9E44D993D12FE85E99 /* Pods_TransitionsCatalog.framework */, + 2408A4B72C0BA93CC963452F /* Pods_UnitTests.framework */, ); - name = Pods; + name = Frameworks; sourceTree = ""; }; 666FAA771D384A6B000363DA = { isa = PBXGroup; children = ( + 662915211ED5F222002B9A5D /* README.md */, 667A3F531DEE273000CB3A99 /* TableOfContents.swift */, 666FAAA31D384B13000363DA /* examples */, + 66BBC7681ED4C8790015CB9B /* supplemental */, 666FAAA61D384B77000363DA /* app */, 666FAA971D384A6B000363DA /* tests */, 666FAA821D384A6B000363DA /* resources */, 666FAA811D384A6B000363DA /* Products */, - 576A22BB98FC22437912573E /* Pods */, - C2A537915BE4F06A956E97CC /* Frameworks */, + E33C6E4C4AE99A65F266DD78 /* Pods */, + 370037A4F3C3DB841D4FFC9F /* Frameworks */, ); sourceTree = ""; }; 666FAA811D384A6B000363DA /* Products */ = { isa = PBXGroup; children = ( - 666FAA801D384A6B000363DA /* Catalog.app */, + 666FAA801D384A6B000363DA /* TransitionsCatalog.app */, 666FAA941D384A6B000363DA /* UnitTests.xctest */, 667A3F3F1DEE269400CB3A99 /* TestHarness.app */, ); @@ -133,6 +155,8 @@ 666FAA971D384A6B000363DA /* tests */ = { isa = PBXGroup; children = ( + 662915221ED64A10002B9A5D /* TransitionTests.swift */, + 66BBC7711ED728DB0015CB9B /* TransitionWithPresentationTests.swift */, ); name = tests; path = ../../../tests/unit; @@ -141,6 +165,10 @@ 666FAAA31D384B13000363DA /* examples */ = { isa = PBXGroup; children = ( + 66BBC75D1ED37DAD0015CB9B /* FadeExample.swift */, + 6629151D1ED5E0E0002B9A5D /* CustomPresentationExample.swift */, + 66BBC7731ED729A70015CB9B /* FadeExample.h */, + 66BBC7741ED729A70015CB9B /* FadeExample.m */, ); name = examples; path = ../..; @@ -184,48 +212,63 @@ path = ../TestHarness; sourceTree = ""; }; - C2A537915BE4F06A956E97CC /* Frameworks */ = { + 66BBC7681ED4C8790015CB9B /* supplemental */ = { + isa = PBXGroup; + children = ( + 66BBC7691ED4C8790015CB9B /* ExampleViewController.swift */, + 66BBC76A1ED4C8790015CB9B /* ExampleViews.swift */, + 66BBC76B1ED4C8790015CB9B /* HexColor.swift */, + 66BBC76C1ED4C8790015CB9B /* Layout.swift */, + 6629151F1ED5E137002B9A5D /* ModalViewController.swift */, + ); + name = supplemental; + path = ../../supplemental; + sourceTree = ""; + }; + E33C6E4C4AE99A65F266DD78 /* Pods */ = { isa = PBXGroup; children = ( - 7B7DADAAF1F30D9DD46FECA7 /* Pods_Catalog.framework */, - D90C2397D3F61B958D5B442E /* Pods_UnitTests.framework */, + 738D98979677D88D24513391 /* Pods-TransitionsCatalog.debug.xcconfig */, + 3734DFFD1C84494E48784617 /* Pods-TransitionsCatalog.release.xcconfig */, + 0C2327F961D4F16DEBF0EEB8 /* Pods-UnitTests.debug.xcconfig */, + D7BB2931AFCEE4C91AE92E5D /* Pods-UnitTests.release.xcconfig */, ); - name = Frameworks; + name = Pods; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 666FAA7F1D384A6B000363DA /* Catalog */ = { + 666FAA7F1D384A6B000363DA /* TransitionsCatalog */ = { isa = PBXNativeTarget; - buildConfigurationList = 666FAA9D1D384A6B000363DA /* Build configuration list for PBXNativeTarget "Catalog" */; + buildConfigurationList = 666FAA9D1D384A6B000363DA /* Build configuration list for PBXNativeTarget "TransitionsCatalog" */; buildPhases = ( - 90BE82836230743E11366313 /* [CP] Check Pods Manifest.lock */, + 98D73D231683AA2871B76D81 /* [CP] Check Pods Manifest.lock */, 666FAA7C1D384A6B000363DA /* Sources */, 666FAA7E1D384A6B000363DA /* Resources */, 332B5EA9D6D03DB5A5EC0149 /* Frameworks */, - 4D270C0F6B5B09302B9B073B /* [CP] Embed Pods Frameworks */, - 13A21C1D583AD624ADF035AF /* [CP] Copy Pods Resources */, + EBE92D1BA3D06BD67925A4E0 /* [CP] Embed Pods Frameworks */, + 8FFB1F82A9D08AF069E44DFB /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); - name = Catalog; + name = TransitionsCatalog; productName = Catalog; - productReference = 666FAA801D384A6B000363DA /* Catalog.app */; + productReference = 666FAA801D384A6B000363DA /* TransitionsCatalog.app */; productType = "com.apple.product-type.application"; }; 666FAA931D384A6B000363DA /* UnitTests */ = { isa = PBXNativeTarget; buildConfigurationList = 666FAAA01D384A6B000363DA /* Build configuration list for PBXNativeTarget "UnitTests" */; buildPhases = ( - C8120B12451EF123B2D62613 /* [CP] Check Pods Manifest.lock */, + DD0DE1433C97D7502DC5B756 /* [CP] Check Pods Manifest.lock */, 666FAA901D384A6B000363DA /* Sources */, 666FAA921D384A6B000363DA /* Resources */, 03BB10DAA9B410FC78A061DE /* Frameworks */, - 196247D9A153A7195C723EBA /* [CP] Embed Pods Frameworks */, - 3ACE00CB448C5D69C702B4E2 /* [CP] Copy Pods Resources */, + B07B7D3E33EA12345D5D53CC /* [CP] Embed Pods Frameworks */, + 560BE48F7F0BF2FEBC8AF290 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -267,11 +310,11 @@ TargetAttributes = { 666FAA7F1D384A6B000363DA = { CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 0800; + LastSwiftMigration = 0830; }; 666FAA931D384A6B000363DA = { CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 0800; + LastSwiftMigration = 0820; TestTargetID = 667A3F3E1DEE269400CB3A99; }; 667A3F3E1DEE269400CB3A99 = { @@ -280,7 +323,7 @@ }; }; }; - buildConfigurationList = 666FAA7B1D384A6B000363DA /* Build configuration list for PBXProject "Catalog" */; + buildConfigurationList = 666FAA7B1D384A6B000363DA /* Build configuration list for PBXProject "TransitionsCatalog" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; @@ -293,7 +336,7 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 666FAA7F1D384A6B000363DA /* Catalog */, + 666FAA7F1D384A6B000363DA /* TransitionsCatalog */, 667A3F3E1DEE269400CB3A99 /* TestHarness */, 666FAA931D384A6B000363DA /* UnitTests */, ); @@ -329,7 +372,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 13A21C1D583AD624ADF035AF /* [CP] Copy Pods Resources */ = { + 560BE48F7F0BF2FEBC8AF290 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -341,40 +384,40 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/../../../Pods/Target Support Files/Pods-Catalog/Pods-Catalog-resources.sh\"\n"; + shellScript = "\"${SRCROOT}/../../../Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 196247D9A153A7195C723EBA /* [CP] Embed Pods Frameworks */ = { + 8FFB1F82A9D08AF069E44DFB /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "[CP] Embed Pods Frameworks"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/../../../Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests-frameworks.sh\"\n"; + shellScript = "\"${SRCROOT}/../../../Pods/Target Support Files/Pods-TransitionsCatalog/Pods-TransitionsCatalog-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 3ACE00CB448C5D69C702B4E2 /* [CP] Copy Pods Resources */ = { + 98D73D231683AA2871B76D81 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "[CP] Copy Pods Resources"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/../../../Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; - 4D270C0F6B5B09302B9B073B /* [CP] Embed Pods Frameworks */ = { + B07B7D3E33EA12345D5D53CC /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -386,10 +429,10 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/../../../Pods/Target Support Files/Pods-Catalog/Pods-Catalog-frameworks.sh\"\n"; + shellScript = "\"${SRCROOT}/../../../Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 90BE82836230743E11366313 /* [CP] Check Pods Manifest.lock */ = { + DD0DE1433C97D7502DC5B756 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -404,19 +447,19 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; - C8120B12451EF123B2D62613 /* [CP] Check Pods Manifest.lock */ = { + EBE92D1BA3D06BD67925A4E0 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "\"${SRCROOT}/../../../Pods/Target Support Files/Pods-TransitionsCatalog/Pods-TransitionsCatalog-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -427,7 +470,15 @@ buildActionMask = 2147483647; files = ( 666FAA841D384A6B000363DA /* AppDelegate.swift in Sources */, + 66BBC76F1ED4C8790015CB9B /* HexColor.swift in Sources */, + 66BBC7751ED729A80015CB9B /* FadeExample.m in Sources */, + 66BBC76D1ED4C8790015CB9B /* ExampleViewController.swift in Sources */, 667A3F541DEE273000CB3A99 /* TableOfContents.swift in Sources */, + 66BBC7701ED4C8790015CB9B /* Layout.swift in Sources */, + 6629151E1ED5E0E0002B9A5D /* CustomPresentationExample.swift in Sources */, + 66BBC76E1ED4C8790015CB9B /* ExampleViews.swift in Sources */, + 662915201ED5E137002B9A5D /* ModalViewController.swift in Sources */, + 66BBC75E1ED37DAD0015CB9B /* FadeExample.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -435,6 +486,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 66BBC7721ED728DB0015CB9B /* TransitionWithPresentationTests.swift in Sources */, + 662915231ED64A10002B9A5D /* TransitionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -451,7 +504,7 @@ /* Begin PBXTargetDependency section */ 666FAA961D384A6B000363DA /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 666FAA7F1D384A6B000363DA /* Catalog */; + target = 666FAA7F1D384A6B000363DA /* TransitionsCatalog */; targetProxy = 666FAA951D384A6B000363DA /* PBXContainerItemProxy */; }; 667A3F521DEE269D00CB3A99 /* PBXTargetDependency */ = { @@ -518,7 +571,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -558,7 +611,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -568,22 +621,25 @@ }; 666FAA9E1D384A6B000363DA /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7D4F5FB9DCA29E6CF4DEBD9A /* Pods-Catalog.debug.xcconfig */; + baseConfigurationReference = 738D98979677D88D24513391 /* Pods-TransitionsCatalog.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = Catalog/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.google.Catalog; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 3.0; }; name = Debug; }; 666FAA9F1D384A6B000363DA /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 26CB86119175B12BFEDB6611 /* Pods-Catalog.release.xcconfig */; + baseConfigurationReference = 3734DFFD1C84494E48784617 /* Pods-TransitionsCatalog.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = Catalog/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.google.Catalog; @@ -595,7 +651,7 @@ }; 666FAAA11D384A6B000363DA /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9221E13BAA68CADCE55AB64D /* Pods-UnitTests.debug.xcconfig */; + baseConfigurationReference = 0C2327F961D4F16DEBF0EEB8 /* Pods-UnitTests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -612,7 +668,7 @@ }; 666FAAA21D384A6B000363DA /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FEDDDD6B137529E9B197CF6A /* Pods-UnitTests.release.xcconfig */; + baseConfigurationReference = D7BB2931AFCEE4C91AE92E5D /* Pods-UnitTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -662,7 +718,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 666FAA7B1D384A6B000363DA /* Build configuration list for PBXProject "Catalog" */ = { + 666FAA7B1D384A6B000363DA /* Build configuration list for PBXProject "TransitionsCatalog" */ = { isa = XCConfigurationList; buildConfigurations = ( 666FAA9B1D384A6B000363DA /* Debug */, @@ -671,7 +727,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 666FAA9D1D384A6B000363DA /* Build configuration list for PBXNativeTarget "Catalog" */ = { + 666FAA9D1D384A6B000363DA /* Build configuration list for PBXNativeTarget "TransitionsCatalog" */ = { isa = XCConfigurationList; buildConfigurations = ( 666FAA9E1D384A6B000363DA /* Debug */, @@ -696,6 +752,7 @@ 667A3F4F1DEE269400CB3A99 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/examples/apps/Catalog/Catalog.xcodeproj/xcshareddata/xcschemes/Catalog.xcscheme b/examples/apps/Catalog/TransitionsCatalog.xcodeproj/xcshareddata/xcschemes/TransitionsCatalog.xcscheme similarity index 79% rename from examples/apps/Catalog/Catalog.xcodeproj/xcshareddata/xcschemes/Catalog.xcscheme rename to examples/apps/Catalog/TransitionsCatalog.xcodeproj/xcshareddata/xcschemes/TransitionsCatalog.xcscheme index 035fb09..1cd6c27 100644 --- a/examples/apps/Catalog/Catalog.xcodeproj/xcshareddata/xcschemes/Catalog.xcscheme +++ b/examples/apps/Catalog/TransitionsCatalog.xcodeproj/xcshareddata/xcschemes/TransitionsCatalog.xcscheme @@ -15,9 +15,9 @@ + BuildableName = "TransitionsCatalog.app" + BlueprintName = "TransitionsCatalog" + ReferencedContainer = "container:TransitionsCatalog.xcodeproj"> @@ -35,7 +35,7 @@ BlueprintIdentifier = "666FAA931D384A6B000363DA" BuildableName = "UnitTests.xctest" BlueprintName = "UnitTests" - ReferencedContainer = "container:Catalog.xcodeproj"> + ReferencedContainer = "container:TransitionsCatalog.xcodeproj"> @@ -43,9 +43,9 @@ + BuildableName = "TransitionsCatalog.app" + BlueprintName = "TransitionsCatalog" + ReferencedContainer = "container:TransitionsCatalog.xcodeproj"> @@ -66,9 +66,9 @@ + BuildableName = "TransitionsCatalog.app" + BlueprintName = "TransitionsCatalog" + ReferencedContainer = "container:TransitionsCatalog.xcodeproj"> @@ -85,9 +85,9 @@ + BuildableName = "TransitionsCatalog.app" + BlueprintName = "TransitionsCatalog" + ReferencedContainer = "container:TransitionsCatalog.xcodeproj"> diff --git a/examples/apps/Catalog/Catalog.xcodeproj/xcshareddata/xcschemes/UnitTests.xcscheme b/examples/apps/Catalog/TransitionsCatalog.xcodeproj/xcshareddata/xcschemes/UnitTests.xcscheme similarity index 95% rename from examples/apps/Catalog/Catalog.xcodeproj/xcshareddata/xcschemes/UnitTests.xcscheme rename to examples/apps/Catalog/TransitionsCatalog.xcodeproj/xcshareddata/xcschemes/UnitTests.xcscheme index b09521c..b05b0ea 100644 --- a/examples/apps/Catalog/Catalog.xcodeproj/xcshareddata/xcschemes/UnitTests.xcscheme +++ b/examples/apps/Catalog/TransitionsCatalog.xcodeproj/xcshareddata/xcschemes/UnitTests.xcscheme @@ -19,7 +19,7 @@ BlueprintIdentifier = "666FAA931D384A6B000363DA" BuildableName = "UnitTests.xctest" BlueprintName = "UnitTests" - ReferencedContainer = "container:Catalog.xcodeproj"> + ReferencedContainer = "container:TransitionsCatalog.xcodeproj"> diff --git a/examples/supplemental/ExampleViewController.swift b/examples/supplemental/ExampleViewController.swift new file mode 100644 index 0000000..095a771 --- /dev/null +++ b/examples/supplemental/ExampleViewController.swift @@ -0,0 +1,71 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +struct ExampleInfo { + let title: String + let instructions: String +} + +class ExampleViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .backgroundColor + } + + func exampleInformation() -> ExampleInfo { + return ExampleInfo(title: "Uninitialized", instructions: "") + } + + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + + self.title = exampleInformation().title + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class ExampleTableViewController: UITableViewController { + + func exampleInformation() -> ExampleInfo { + return ExampleInfo(title: "Uninitialized", instructions: "") + } + + convenience init() { + self.init(style: .plain) + } + + override init(style: UITableViewStyle) { + super.init(style: style) + + self.title = exampleInformation().title + } + + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + + self.title = exampleInformation().title + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/examples/supplemental/ExampleViews.swift b/examples/supplemental/ExampleViews.swift new file mode 100644 index 0000000..f049807 --- /dev/null +++ b/examples/supplemental/ExampleViews.swift @@ -0,0 +1,30 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +func createExampleView() -> UIView { + let view = UIView(frame: .init(x: 0, y: 0, width: 128, height: 128)) + view.backgroundColor = .primaryColor + view.layer.cornerRadius = view.bounds.width / 2 + return view +} + +func createExampleSquareView() -> UIView { + let view = UIView(frame: .init(x: 0, y: 0, width: 128, height: 128)) + view.backgroundColor = .primaryColor + return view +} diff --git a/examples/supplemental/HexColor.swift b/examples/supplemental/HexColor.swift new file mode 100644 index 0000000..90edb2a --- /dev/null +++ b/examples/supplemental/HexColor.swift @@ -0,0 +1,44 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +extension UIColor { + private convenience init(red: Int, green: Int, blue: Int) { + assert(red >= 0 && red <= 255, "Invalid red component") + assert(green >= 0 && green <= 255, "Invalid green component") + assert(blue >= 0 && blue <= 255, "Invalid blue component") + + self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0) + } + + convenience init(hexColor: Int) { + self.init(red: (hexColor >> 16) & 0xff, green: (hexColor >> 8) & 0xff, blue: hexColor & 0xff) + } + + static var primaryColor: UIColor { + return UIColor(hexColor: 0xFF80AB) + } + + static var secondaryColor: UIColor { + return UIColor(hexColor: 0xC51162) + } + + static var backgroundColor: UIColor { + return UIColor(hexColor: 0x212121) + } +} diff --git a/examples/supplemental/Layout.swift b/examples/supplemental/Layout.swift new file mode 100644 index 0000000..7345cbf --- /dev/null +++ b/examples/supplemental/Layout.swift @@ -0,0 +1,25 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +func center(_ view: UIView, within containerView: UIView) -> UIView { + let x = (containerView.bounds.width - view.bounds.width) / 2 + let y = (containerView.bounds.height - view.bounds.height) / 2 + view.frame = .init(origin: .init(x: x, y: y), size: view.bounds.size) + view.autoresizingMask = [.flexibleTopMargin, .flexibleRightMargin, .flexibleBottomMargin, .flexibleLeftMargin] + return view +} diff --git a/examples/supplemental/ModalViewController.swift b/examples/supplemental/ModalViewController.swift new file mode 100644 index 0000000..8fb078a --- /dev/null +++ b/examples/supplemental/ModalViewController.swift @@ -0,0 +1,44 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +class ModalViewController: ExampleViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .primaryColor + + view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTap))) + + let label = UILabel(frame: view.bounds) + label.numberOfLines = 0 + label.lineBreakMode = .byWordWrapping + label.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In aliquam dolor eget orci condimentum, eu blandit metus dictum. Suspendisse vitae metus pellentesque, sagittis massa vel, sodales velit. Aliquam placerat nibh et posuere interdum. Etiam fermentum purus vel turpis lobortis auctor. Curabitur auctor maximus purus, ac iaculis mi. In ac hendrerit sapien, eget porttitor risus. Integer placerat cursus viverra. Proin mollis nulla vitae nisi posuere, eu rutrum mauris condimentum. Nullam in faucibus nulla, non tincidunt lectus. Maecenas mollis massa purus, in viverra elit molestie eu. Nunc volutpat magna eget mi vestibulum pharetra. Suspendisse nulla ligula, laoreet non ante quis, vehicula facilisis libero. Morbi faucibus, sapien a convallis sodales, leo quam scelerisque leo, ut tincidunt diam velit laoreet nulla. Proin at quam vel nibh varius ultrices porta id diam. Pellentesque pretium consequat neque volutpat tristique. Sed placerat a purus ut molestie. Nullam laoreet venenatis urna non pulvinar. Proin a vestibulum nulla, eu placerat est. Morbi molestie aliquam justo, ut aliquet neque tristique consectetur. In hac habitasse platea dictumst. Fusce vehicula justo in euismod elementum. Ut vel malesuada est. Aliquam mattis, ex vel viverra eleifend, mauris nibh faucibus nibh, in fringilla sem purus vitae elit. Donec sed dapibus orci, ut vulputate sapien. Integer eu magna efficitur est pellentesque tempor. Sed ac imperdiet ex. Maecenas congue quis lacus vel dictum. Phasellus dictum mi at sollicitudin euismod. Mauris laoreet, eros vitae euismod commodo, libero ligula pretium massa, in scelerisque eros dui eu metus. Fusce elementum mauris velit, eu tempor nulla congue ut. In at tellus id quam feugiat semper eget ut felis. Nulla quis varius quam. Nullam tincidunt laoreet risus, ut aliquet nisl gravida id. Nulla iaculis mauris velit, vitae feugiat nunc scelerisque ac. Vivamus eget ligula porta, pulvinar ex vitae, sollicitudin erat. Maecenas semper ornare suscipit. Ut et neque condimentum lectus pulvinar maximus in sit amet odio. Aliquam congue purus erat, eu rutrum risus placerat a." + label.autoresizingMask = [.flexibleWidth, .flexibleHeight] + view.addSubview(label) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } + + func didTap() { + dismiss(animated: true) + } +} diff --git a/src/MDMTransition.h b/src/MDMTransition.h new file mode 100644 index 0000000..618c8b3 --- /dev/null +++ b/src/MDMTransition.h @@ -0,0 +1,78 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +@protocol MDMTransitionContext; + +/** + A transition coordinates the animated presentation or dismissal of a view controller. + + By default a transition is only expected to implement startWithContext: and to eventually call the + context's `transitionDidEnd` method once the transition completes. + + A transition can opt in to extra behavior by conforming to other TransitionWith* protocols. + */ +NS_SWIFT_NAME(Transition) +@protocol MDMTransition + +/** + Invoked on initiation of a view controller transition. + */ +- (void)startWithContext:(nonnull id)context; + +@end + +/** + A transition with presentation is able to customize the overall presentation of the transition, + including adding temporary views and changing the destination frame of the presented view + controller. + */ +NS_SWIFT_NAME(TransitionWithPresentation) +@protocol MDMTransitionWithPresentation + +/** + The modal presentation style this transition expects to use. + + This method is queried when the transition is assigned to a view controller's + `transitionController` transition property. The result, if any, is assigned to the view + controller's `modalPresentationStyle` property. + + Note: In order for a presentation controller to be used the view controller's + `modalPresentationStyle` must be `.custom`. + + Return `.none` if no presentation controller should be requested. + */ +- (UIModalPresentationStyle)defaultModalPresentationStyle; + +/** + Queried when the presented view controller is first presented. + + The returned object is cached for the lifetime of the presented view controller. + + If the returned object conforms to MDMTransition then its `startWithContext:` implementation will + be invoked before the transition's `startWithContext:`. + + If nil is returned then no presentation controller will be used. + */ +// clang-format off +- (nullable UIPresentationController *)presentationControllerForPresentedViewController:(nonnull UIViewController *)presented + presentingViewController:(nonnull UIViewController *)presenting + sourceViewController:(nullable UIViewController *)source +NS_SWIFT_NAME(presentationController(forPresented:presenting:source:)); +// clang-format on + +@end diff --git a/src/MDMTransitionContext.h b/src/MDMTransitionContext.h new file mode 100644 index 0000000..df50cf4 --- /dev/null +++ b/src/MDMTransitionContext.h @@ -0,0 +1,82 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +/** + The possible directions of a transition. + */ +NS_SWIFT_NAME(TransitionDirection) +typedef NS_ENUM(NSUInteger, MDMTransitionDirection) { + /** + The fore view controller is being presented. + */ + MDMTransitionDirectionForward, + + /** + The fore view controller is being dismissed. + */ + MDMTransitionDirectionBackward, +}; + +/** + A presentation info instance contains objects related to a transition. + */ +NS_SWIFT_NAME(TransitionContext) +@protocol MDMTransitionContext + +/** + Informs the context that the transition has ended. + */ +- (void)transitionDidEnd; + +/** + The direction this transition is moving in. + */ +@property(nonatomic, readonly) MDMTransitionDirection direction; + +/** + The duration of this transition. + */ +@property(nonatomic, readonly) NSTimeInterval duration; + +/** + The source view controller for this transition. + + This is the view controller that initiated the transition. + */ +@property(nonatomic, strong, readonly, nullable) UIViewController *sourceViewController; + +/** + The back view controller for this transition. + + This is the destination when the transition's direction is backward. + */ +@property(nonatomic, strong, readonly, nonnull) UIViewController *backViewController; + +/** + The fore view controller for this transition. + + This is the destination when the transition's direction is forward. + */ +@property(nonatomic, strong, readonly, nonnull) UIViewController *foreViewController; + +/** + The container view for the transition as reported by UIKit's transition context. + */ +@property(nonatomic, strong, readonly, nonnull) UIView *containerView; + +@end diff --git a/src/MDMTransitionController.h b/src/MDMTransitionController.h new file mode 100644 index 0000000..53e40fa --- /dev/null +++ b/src/MDMTransitionController.h @@ -0,0 +1,41 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +@protocol MDMTransition; + +/** + A transition controller is a bridge between UIKit's view controller transitioning APIs and + Material Motion transitions. + + Each view controller owns its own transition controller via the mdm_transitionController property. + */ +NS_SWIFT_NAME(TransitionController) +@protocol MDMTransitionController + +/** + The transition instance that will govern any presentation or dismissal of the view controller. + + If no transition is provided then a default UIKit transition will be used. + + Side effects: if the transition conforms to MDMTransitionWithPresentation, then the transition's + default modal presentation style will be queried and assigned to the associated view controller's + `modalPresentationStyle` property. + */ +@property(nonatomic, strong, nullable) id transition; + +@end diff --git a/src/Transitioning.h b/src/Transitioning.h new file mode 100644 index 0000000..11806a7 --- /dev/null +++ b/src/Transitioning.h @@ -0,0 +1,20 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MDMTransition.h" +#import "MDMTransitionContext.h" +#import "MDMTransitionController.h" +#import "UIViewController+TransitionController.h" diff --git a/src/UIViewController+TransitionController.h b/src/UIViewController+TransitionController.h new file mode 100644 index 0000000..1feaf84 --- /dev/null +++ b/src/UIViewController+TransitionController.h @@ -0,0 +1,34 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +@protocol MDMTransitionController; + +@interface UIViewController (MDMTransitionController) + +/** + A transition controller may be used to implement custom transitions. + + The transition controller is lazily created upon access. + + Side effects: If the view controller's transitioningDelegate is nil when the controller is created, + then the controller will also be set to the transitioningDelegate property. + */ +@property(nonatomic, strong, readonly, nonnull) id mdm_transitionController + NS_SWIFT_NAME(transitionController); + +@end diff --git a/src/UIViewController+TransitionController.m b/src/UIViewController+TransitionController.m new file mode 100644 index 0000000..5f9de4a --- /dev/null +++ b/src/UIViewController+TransitionController.m @@ -0,0 +1,61 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "UIViewController+TransitionController.h" + +#import "private/MDMPresentationTransitionController.h" + +#import + +@implementation UIViewController (MDMTransitionController) + +#pragma mark - Public + +- (id)mdm_transitionController { + const void *key = [self mdm_transitionControllerKey]; + + MDMPresentationTransitionController *controller = objc_getAssociatedObject(self, key); + if (!controller) { + controller = [[MDMPresentationTransitionController alloc] initWithViewController:self]; + [self mdm_setTransitionController:controller]; + } + return controller; +} + +#pragma mark - Private + +- (void)mdm_setTransitionController:(MDMPresentationTransitionController *)controller { + const void *key = [self mdm_transitionControllerKey]; + + // Clear the previous delegate if we'd previously set one. + MDMPresentationTransitionController *existingController = objc_getAssociatedObject(self, key); + id delegate = self.transitioningDelegate; + if (existingController == delegate) { + self.transitioningDelegate = nil; + } + + objc_setAssociatedObject(self, key, controller, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + if (!delegate) { + self.transitioningDelegate = controller; + } +} + +- (const void *)mdm_transitionControllerKey { + return @selector(mdm_transitionController); +} + +@end diff --git a/src/private/MDMPresentationTransitionController.h b/src/private/MDMPresentationTransitionController.h new file mode 100644 index 0000000..b620bba --- /dev/null +++ b/src/private/MDMPresentationTransitionController.h @@ -0,0 +1,28 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "MDMTransitionController.h" + +@interface MDMPresentationTransitionController : NSObject + +- (nonnull instancetype)initWithViewController:(nonnull UIViewController *)viewController + NS_DESIGNATED_INITIALIZER; + +- (nonnull instancetype)init NS_UNAVAILABLE; + +@end diff --git a/src/private/MDMPresentationTransitionController.m b/src/private/MDMPresentationTransitionController.m new file mode 100644 index 0000000..e4dd214 --- /dev/null +++ b/src/private/MDMPresentationTransitionController.m @@ -0,0 +1,128 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MDMPresentationTransitionController.h" + +#import "MDMTransition.h" +#import "MDMViewControllerTransitionContext.h" + +@interface MDMPresentationTransitionController () +@end + +@implementation MDMPresentationTransitionController { + // We expect the view controller to hold a strong reference to its transition controller, so keep + // a weak reference to the view controller here. + __weak UIViewController *_associatedViewController; + + UIPresentationController *_presentationController; + + MDMViewControllerTransitionContext *_context; + __weak UIViewController *_source; +} + +@synthesize transition = _transition; + +- (nonnull instancetype)initWithViewController:(nonnull UIViewController *)viewController { + self = [super init]; + if (self) { + _associatedViewController = viewController; + } + return self; +} + +#pragma mark - Public + +- (void)setTransition:(id)transition { + _transition = transition; + + // Set the default modal presentation style. + if ([_transition respondsToSelector:@selector(defaultModalPresentationStyle)]) { + id withPresentation = (id)_transition; + UIModalPresentationStyle style = [withPresentation defaultModalPresentationStyle]; + _associatedViewController.modalPresentationStyle = style; + } +} + +#pragma mark - UIViewControllerTransitioningDelegate + +// Animated transitions + +- (id)animationControllerForPresentedController:(UIViewController *)presented + presentingController:(UIViewController *)presenting + sourceController:(UIViewController *)source { + _source = source; + + [self prepareForTransitionWithSourceViewController:source + backViewController:presenting + foreViewController:presented + direction:MDMTransitionDirectionForward]; + return _context; +} + +- (id)animationControllerForDismissedController:(UIViewController *)dismissed { + [self prepareForTransitionWithSourceViewController:_source + backViewController:dismissed.presentingViewController + foreViewController:dismissed + direction:MDMTransitionDirectionBackward]; + return _context; +} + +// Presentation + +- (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented + presentingViewController:(UIViewController *)presenting + sourceViewController:(UIViewController *)source { + if (![_transition respondsToSelector:@selector(presentationControllerForPresentedViewController:presentingViewController:sourceViewController:)]) { + return nil; + } + id withPresentation = (id)_transition; + _presentationController = [withPresentation presentationControllerForPresentedViewController:presented + presentingViewController:presenting + sourceViewController:source]; + return _presentationController; +} + +#pragma mark - MDMViewControllerTransitionContextDelegate + +- (void)transitionDidCompleteWithContext:(MDMViewControllerTransitionContext *)context { + if (_context == context) { + _context = nil; + } +} + +#pragma mark - Private + +- (void)prepareForTransitionWithSourceViewController:(nullable UIViewController *)source + backViewController:(nonnull UIViewController *)back + foreViewController:(nonnull UIViewController *)fore + direction:(MDMTransitionDirection)direction { + if (direction == MDMTransitionDirectionBackward) { + _context = nil; + } + NSAssert(!_context, @"A transition is already active."); + + if (_transition) { + _context = [[MDMViewControllerTransitionContext alloc] initWithTransition:_transition + direction:direction + sourceViewController:source + backViewController:back + foreViewController:fore + presentationController:_presentationController]; + _context.delegate = self; + } +} + +@end diff --git a/src/private/MDMViewControllerTransitionContext.h b/src/private/MDMViewControllerTransitionContext.h new file mode 100644 index 0000000..7c2c509 --- /dev/null +++ b/src/private/MDMViewControllerTransitionContext.h @@ -0,0 +1,44 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "MDMTransitionContext.h" + +@protocol MDMTransition; +@protocol MDMViewControllerTransitionContextDelegate; + +@interface MDMViewControllerTransitionContext : NSObject + +- (nonnull instancetype)initWithTransition:(nonnull id)transition + direction:(MDMTransitionDirection)direction + sourceViewController:(nullable UIViewController *)sourceViewController + backViewController:(nonnull UIViewController *)backViewController + foreViewController:(nonnull UIViewController *)foreViewController + presentationController:(nullable UIPresentationController *)presentationController + NS_DESIGNATED_INITIALIZER; + +- (nonnull instancetype)init NS_UNAVAILABLE; + +@property(nonatomic, weak, nullable) id delegate; + +@end + +@protocol MDMViewControllerTransitionContextDelegate + +- (void)transitionDidCompleteWithContext:(nonnull MDMViewControllerTransitionContext *)context; + +@end diff --git a/src/private/MDMViewControllerTransitionContext.m b/src/private/MDMViewControllerTransitionContext.m new file mode 100644 index 0000000..c8c1076 --- /dev/null +++ b/src/private/MDMViewControllerTransitionContext.m @@ -0,0 +1,152 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MDMViewControllerTransitionContext.h" + +#import "MDMTransition.h" + +@implementation MDMViewControllerTransitionContext { + id _transition; + id _transitionContext; + UIPresentationController *_presentationController; +} + +@synthesize direction = _direction; +@synthesize sourceViewController = _sourceViewController; +@synthesize backViewController = _backViewController; +@synthesize foreViewController = _foreViewController; + +- (nonnull instancetype)initWithTransition:(nonnull id)transition + direction:(MDMTransitionDirection)direction + sourceViewController:(nullable UIViewController *)sourceViewController + backViewController:(nonnull UIViewController *)backViewController + foreViewController:(nonnull UIViewController *)foreViewController + presentationController:(nullable UIPresentationController *)presentationController { + self = [super init]; + if (self) { + _transition = transition; + _direction = direction; + _sourceViewController = sourceViewController; + _backViewController = backViewController; + _foreViewController = foreViewController; + _presentationController = presentationController; + } + return self; +} + +#pragma mark - UIViewControllerAnimatedTransitioning + +- (NSTimeInterval)transitionDuration:(id)transitionContext { + // TODO(featherless): Expose a TransitionWithTiming protocol that allows the transition to + // customize this value. + return 0.35; +} + +- (void)animateTransition:(id)transitionContext { + _transitionContext = transitionContext; + + [self initiateTransition]; +} + +// TODO(featherless): Implement interactive transitioning. Need to implement +// UIViewControllerInteractiveTransitioning here and isInteractive and interactionController* in +// MDMPresentationTransitionController. + +#pragma mark - MDMTransitionContext + +- (NSTimeInterval)duration { + return [self transitionDuration:_transitionContext]; +} + +- (UIView *)containerView { + return _transitionContext.containerView; +} + +- (void)transitionDidEnd { + [_transitionContext completeTransition:true]; + + _transition = nil; + + [_delegate transitionDidCompleteWithContext:self]; +} + +#pragma mark - Private + +- (void)initiateTransition { + UIViewController *from = [_transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; + if (from) { + CGRect finalFrame = [_transitionContext finalFrameForViewController:from]; + if (!CGRectIsEmpty(finalFrame)) { + from.view.frame = finalFrame; + } + } + + UIViewController *to = [_transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; + if (to) { + CGRect finalFrame = [_transitionContext finalFrameForViewController:to]; + if (!CGRectIsEmpty(finalFrame)) { + to.view.frame = finalFrame; + } + + switch (_direction) { + case MDMTransitionDirectionForward: + [_transitionContext.containerView addSubview:to.view]; + break; + + case MDMTransitionDirectionBackward: + if (!to.view.superview) { + [_transitionContext.containerView insertSubview:to.view atIndex:0]; + } + break; + } + + [to.view layoutIfNeeded]; + } + + [self anticipateOnlyExplicitAnimations]; + + [CATransaction begin]; + [CATransaction setAnimationDuration:[self transitionDuration:_transitionContext]]; + + if ([_presentationController respondsToSelector:@selector(startWithContext:)]) { + id asTransition = (id)_presentationController; + [asTransition startWithContext:self]; + } + + [_transition startWithContext:self]; + + [CATransaction commit]; +} + +// UIKit transitions will not animate any of the system animations (status bar changes, notably) +// unless we have at least one implicit UIView animation. Material Motion doesn't use implicit +// animations out of the box, so to ensure that system animations still occur we create an +// invisible throwaway view and apply an animation to it. +- (void)anticipateOnlyExplicitAnimations { + UIView *throwawayView = [[UIView alloc] init]; + [self.containerView addSubview:throwawayView]; + + [UIView animateWithDuration:[self transitionDuration:_transitionContext] + animations:^{ + throwawayView.frame = CGRectOffset(throwawayView.frame, 1, 0); + + } + completion:^(BOOL finished) { + [throwawayView removeFromSuperview]; + }]; +} + +@end diff --git a/tests/unit/TransitionTests.swift b/tests/unit/TransitionTests.swift new file mode 100644 index 0000000..a82f68e --- /dev/null +++ b/tests/unit/TransitionTests.swift @@ -0,0 +1,52 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import XCTest +import Transitioning + +class TransitionTests: XCTestCase { + + private var window: UIWindow! + override func setUp() { + window = UIWindow() + window.rootViewController = UIViewController() + window.makeKeyAndVisible() + } + + override func tearDown() { + window = nil + } + + func testTransitionDidEndCausesTransitionCompletion() { + let presentedViewController = UIViewController() + presentedViewController.transitionController.transition = InstantCompletionTransition() + + let didComplete = expectation(description: "Did complete") + window.rootViewController!.present(presentedViewController, animated: true) { + didComplete.fulfill() + } + + waitForExpectations(timeout: 0.5) + + XCTAssertEqual(window.rootViewController!.presentedViewController, presentedViewController) + } +} + +final class InstantCompletionTransition: NSObject, Transition { + func start(with context: TransitionContext) { + context.transitionDidEnd() + } +} diff --git a/tests/unit/TransitionWithPresentationTests.swift b/tests/unit/TransitionWithPresentationTests.swift new file mode 100644 index 0000000..ed4b05f --- /dev/null +++ b/tests/unit/TransitionWithPresentationTests.swift @@ -0,0 +1,63 @@ +/* + Copyright 2017-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import XCTest +import Transitioning + +class TransitionWithPresentationTests: XCTestCase { + + private var window: UIWindow! + override func setUp() { + window = UIWindow() + window.rootViewController = UIViewController() + window.makeKeyAndVisible() + } + + override func tearDown() { + window = nil + } + + func testPresentationControllerIsQueried() { + let presentedViewController = UIViewController() + presentedViewController.transitionController.transition = PresentationTransition() + + let didComplete = expectation(description: "Did complete") + window.rootViewController!.present(presentedViewController, animated: true) { + didComplete.fulfill() + } + + waitForExpectations(timeout: 0.5) + + XCTAssert(presentedViewController.presentationController is TestingPresentationController) + } +} + +final class TestingPresentationController: UIPresentationController { +} + +final class PresentationTransition: NSObject, TransitionWithPresentation { + func defaultModalPresentationStyle() -> UIModalPresentationStyle { + return .custom + } + + func presentationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController?) -> UIPresentationController? { + return TestingPresentationController(presentedViewController: presented, presenting: presenting) + } + + func start(with context: TransitionContext) { + context.transitionDidEnd() + } +} From 8688b045594ee38204744c7c644d4cce58165ec6 Mon Sep 17 00:00:00 2001 From: featherless Date: Thu, 25 May 2017 14:02:39 -0400 Subject: [PATCH 2/5] Simplify the frame calculation APIs in the example. (#5) --- examples/CustomPresentationExample.swift | 25 ++++++++---------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/examples/CustomPresentationExample.swift b/examples/CustomPresentationExample.swift index 368b910..3728370 100644 --- a/examples/CustomPresentationExample.swift +++ b/examples/CustomPresentationExample.swift @@ -36,14 +36,18 @@ class CustomPresentationExampleViewController: ExampleTableViewController { // calculation block. let modalDialog = VerticalSheetTransition() modalDialog.calculateFrameOfPresentedViewInContainerView = { info in + guard let containerView = info.containerView else { + assertionFailure("Missing container view during frame query.") + return .zero + } // Note: this block is retained for the lifetime of the view controller, so be careful not to // create a memory loop by referencing self or the presented view controller directly - use // the provided info structure to access these values instead. // Center the dialog in the container view. let size = CGSize(width: 200, height: 200) - return CGRect(x: (info.containerView.bounds.width - size.width) / 2, - y: (info.containerView.bounds.height - size.height) / 2, + return CGRect(x: (containerView.bounds.width - size.width) / 2, + y: (containerView.bounds.height - size.height) / 2, width: size.width, height: size.height) } @@ -144,15 +148,8 @@ final class DimmingPresentationController: UIPresentationController { } override var frameOfPresentedViewInContainerView: CGRect { - guard let containerView = containerView else { - assertionFailure("Missing container view during frame query.") - return .zero - } // We delegate out our frame calculation here: - let info = CalculateFrameInfo(containerView: containerView, - presentingViewController: presentingViewController, - presentedViewController: presentedViewController) - return calculateFrameOfPresentedViewInContainerView(info) + return calculateFrameOfPresentedViewInContainerView(self) } override func presentationTransitionWillBegin() { @@ -203,13 +200,7 @@ extension DimmingPresentationController: Transition { } } -typealias CalculateFrame = (CalculateFrameInfo) -> CGRect - -struct CalculateFrameInfo { - let containerView: UIView - let presentingViewController: UIViewController - let presentedViewController: UIViewController -} +typealias CalculateFrame = (UIPresentationController) -> CGRect // MARK: Supplemental code From 84c23e5f7c490e2a7d299cca6c4046ac4f368551 Mon Sep 17 00:00:00 2001 From: featherless Date: Thu, 25 May 2017 14:14:48 -0400 Subject: [PATCH 3/5] Clarify the docs for default modal presentation styles. (#4) --- src/MDMTransition.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/MDMTransition.h b/src/MDMTransition.h index 618c8b3..13b72b1 100644 --- a/src/MDMTransition.h +++ b/src/MDMTransition.h @@ -54,7 +54,8 @@ NS_SWIFT_NAME(TransitionWithPresentation) Note: In order for a presentation controller to be used the view controller's `modalPresentationStyle` must be `.custom`. - Return `.none` if no presentation controller should be requested. + If you do not wish to use a presentation controller, return anything other than + `UIModalPresentationStyleCustom`. */ - (UIModalPresentationStyle)defaultModalPresentationStyle; From 7ebb7d682765135c7a173fe074ce7c2eb8bf1ef3 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Thu, 25 May 2017 15:05:47 -0400 Subject: [PATCH 4/5] Automatic changelog preparation for release. --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29..a67ab22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# #develop# + + TODO: Enumerate changes. + + From c9ae79bb93c8b61beb57d4fb823eba1a3f48c1bc Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Thu, 25 May 2017 15:07:19 -0400 Subject: [PATCH 5/5] Add release notes. --- CHANGELOG.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a67ab22..6df596c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ -# #develop# +# 1.0.0 - TODO: Enumerate changes. +Initial release. +Includes support for building simple view controller transitions and transitions that support custom presentation. +## Source changes + +* [Clarify the docs for default modal presentation styles. (#4)](https://github.com/material-motion/transitioning-objc/commit/84c23e5f7c490e2a7d299cca6c4046ac4f368551) (featherless) +* [Initial implementation. (#1)](https://github.com/material-motion/transitioning-objc/commit/c1b760455779226ebc9749e06e528d25a6b444bc) (featherless) + +## Non-source changes + +* [Simplify the frame calculation APIs in the example. (#5)](https://github.com/material-motion/transitioning-objc/commit/8688b045594ee38204744c7c644d4cce58165ec6) (featherless)