From 0080128a6846d2eda8538cab260cbecbbe32b9a1 Mon Sep 17 00:00:00 2001 From: Sylvain Defresne Date: Thu, 16 Nov 2017 16:07:01 +0100 Subject: [PATCH 01/28] Add missing Info.plist. (#20) Building a framework requires an Info.plist, so add a template that can be used to create the plist for the bundle. --- resources/Info.plist | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 resources/Info.plist diff --git a/resources/Info.plist b/resources/Info.plist new file mode 100644 index 0000000..9645e5d --- /dev/null +++ b/resources/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0.2 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + From 75f0d3515bda6cb9770c96ddb787a3da50a2b7c6 Mon Sep 17 00:00:00 2001 From: featherless Date: Thu, 16 Nov 2017 14:29:29 -0500 Subject: [PATCH 02/28] Standardize the timing curve creation methods on CGFloat. (#21) The underlying storage type is CGFloat, so our creation methods should match that data type. --- src/MDMMotionCurve.h | 19 ++++++++++++------- src/MDMMotionCurve.m | 12 ++++++------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/MDMMotionCurve.h b/src/MDMMotionCurve.h index dedeb85..0b233eb 100644 --- a/src/MDMMotionCurve.h +++ b/src/MDMMotionCurve.h @@ -73,12 +73,14 @@ typedef struct MDMMotionCurve MDMMotionCurve; See the documentation for CAMediaTimingFunction for more information. */ // clang-format off -FOUNDATION_EXTERN MDMMotionCurve MDMMotionCurveMakeBezier(float p1x, float p1y, float p2x, float p2y) +FOUNDATION_EXTERN +MDMMotionCurve MDMMotionCurveMakeBezier(CGFloat p1x, CGFloat p1y, CGFloat p2x, CGFloat p2y) NS_SWIFT_NAME(MotionCurveMakeBezier(p1x:p1y:p2x:p2y:)); // clang-format on // clang-format off -FOUNDATION_EXTERN MDMMotionCurve MDMMotionCurveFromTimingFunction(CAMediaTimingFunction * _Nonnull timingFunction) +FOUNDATION_EXTERN +MDMMotionCurve MDMMotionCurveFromTimingFunction(CAMediaTimingFunction * _Nonnull timingFunction) NS_SWIFT_NAME(MotionCurve(fromTimingFunction:)); // clang-format on @@ -90,7 +92,9 @@ FOUNDATION_EXTERN MDMMotionCurve MDMMotionCurveFromTimingFunction(CAMediaTimingF See the documentation for CASpringAnimation for more information. */ // clang-format off -FOUNDATION_EXTERN MDMMotionCurve MDMMotionCurveMakeSpring(float mass, float tension, float friction) +FOUNDATION_EXTERN MDMMotionCurve MDMMotionCurveMakeSpring(CGFloat mass, + CGFloat tension, + CGFloat friction) NS_SWIFT_NAME(MotionCurveMakeSpring(mass:tension:friction:)); // clang-format on @@ -102,10 +106,11 @@ FOUNDATION_EXTERN MDMMotionCurve MDMMotionCurveMakeSpring(float mass, float tens See the documentation for CASpringAnimation for more information. */ // clang-format off -FOUNDATION_EXTERN MDMMotionCurve MDMMotionCurveMakeSpringWithInitialVelocity(float mass, - float tension, - float friction, - float initialVelocity) +FOUNDATION_EXTERN +MDMMotionCurve MDMMotionCurveMakeSpringWithInitialVelocity(CGFloat mass, + CGFloat tension, + CGFloat friction, + CGFloat initialVelocity) NS_SWIFT_NAME(MotionCurveMakeSpring(mass:tension:friction:initialVelocity:)); // clang-format on diff --git a/src/MDMMotionCurve.m b/src/MDMMotionCurve.m index 828d100..2cc57c8 100644 --- a/src/MDMMotionCurve.m +++ b/src/MDMMotionCurve.m @@ -16,18 +16,18 @@ #import "MDMMotionCurve.h" -MDMMotionCurve MDMMotionCurveMakeBezier(float p1x, float p1y, float p2x, float p2y) { +MDMMotionCurve MDMMotionCurveMakeBezier(CGFloat p1x, CGFloat p1y, CGFloat p2x, CGFloat p2y) { return _MDMBezier(p1x, p1y, p2x, p2y); } -MDMMotionCurve MDMMotionCurveMakeSpring(float mass, float tension, float friction) { +MDMMotionCurve MDMMotionCurveMakeSpring(CGFloat mass, CGFloat tension, CGFloat friction) { return MDMMotionCurveMakeSpringWithInitialVelocity(mass, tension, friction, 0); } -MDMMotionCurve MDMMotionCurveMakeSpringWithInitialVelocity(float mass, - float tension, - float friction, - float initialVelocity) { +MDMMotionCurve MDMMotionCurveMakeSpringWithInitialVelocity(CGFloat mass, + CGFloat tension, + CGFloat friction, + CGFloat initialVelocity) { return _MDMSpringWithInitialVelocity(mass, tension, friction, initialVelocity); } From 866ec18cf2353c2682f89d1350af57c05ce25839 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Mon, 4 Dec 2017 13:14:20 -0500 Subject: [PATCH 03/28] Fix travis. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ab17217..938266c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,11 @@ language: objective-c -osx_image: xcode8.1 +osx_image: xcode8.3 sudo: false before_install: - gem install cocoapods --no-rdoc --no-ri --no-document --quiet - pod install --repo-update script: - set -o pipefail - - xcodebuild build -workspace MotionInterchange.xcworkspace -scheme MotionInterchangeCatalog -sdk "iphonesimulator10.1" -destination "name=iPhone 6s,OS=10.1" ONLY_ACTIVE_ARCH=YES | xcpretty -c; + - xcodebuild build -workspace MotionInterchange.xcworkspace -scheme MotionInterchangeCatalog -sdk "iphonesimulator10.3" -destination "name=iPhone 6s,OS=10.1" ONLY_ACTIVE_ARCH=YES | xcpretty -c; after_success: - bash <(curl -s https://codecov.io/bash) From 3a28e221cfe9fccc2c46523adbb817334e7e918a Mon Sep 17 00:00:00 2001 From: featherless Date: Mon, 4 Dec 2017 17:11:55 -0500 Subject: [PATCH 04/28] Implement v2 APIs (#22) In short: this PR drops the redundant "motion" prefix on various types and aligns naming with system terminology where possible and re-implements the APIs as Objective-C APIs, closing #5. | Old API | New API | Rationale | |:------- |:-------- |:-----------| | MotionTiming | AnimationTraits | This structure is intended to describe animations only, not motion in general. | | MotionCurve | TimingCurve | This brings the API name closer to the similarly-purposed `CAMediaTimingFunction`. MotionCurve could also be easily confused with motion through x/y space rather than through time (e.g. ArcMove), which will be problematic as we start defining paths of motion through space. | | MotionRepetition | RepetitionTraits | This aligns the naming with AnimationTraits. | All prior APIs have been deleted, including the macro-based APIs and any active deprecations. This is a major change and will bump the release to v2. A migration script has been included to ease migration of existing code. --- .../project.pbxproj | 28 ++- scripts/v1_to_v2.sh | 43 ++++ src/CAMediaTimingFunction+MDMTimingCurve.h | 44 ++++ src/CAMediaTimingFunction+MDMTimingCurve.m | 49 +++++ src/MDMAnimationTraits.h | 115 +++++++++++ src/MDMAnimationTraits.m | 71 +++++++ src/MDMMotionCurve.h | 194 ------------------ src/MDMMotionCurve.m | 51 ----- src/MDMMotionRepetition.h | 70 ------- src/MDMMotionTiming.h | 48 ----- src/MDMRepetition.h | 60 ++++++ src/MDMRepetition.m | 43 ++++ src/MDMRepetitionOverTime.h | 55 +++++ src/MDMRepetitionOverTime.m | 42 ++++ src/MDMRepetitionTraits.h | 30 +++ src/MDMSpringTimingCurve.h | 95 +++++++++ src/MDMSpringTimingCurve.m | 45 ++++ src/MDMTimingCurve.h | 24 +++ src/MotionInterchange.h | 10 +- tests/unit/CAMediaTimingFunctionTests.swift | 39 ++++ tests/unit/MDMAnimationTraitsTests.swift | 113 ++++++++++ tests/unit/MDMModalMovementTimingTests.m | 27 +-- tests/unit/MDMMotionCurveTests.m | 75 ------- tests/unit/MDMMotionCurveTests.swift | 65 ------ tests/unit/MDMRepetitionOverTimeTests.swift | 33 +++ tests/unit/MDMRepetitionTests.swift | 33 +++ tests/unit/MDMSpringTimingCurve.swift | 37 ++++ 27 files changed, 1013 insertions(+), 526 deletions(-) create mode 100755 scripts/v1_to_v2.sh create mode 100644 src/CAMediaTimingFunction+MDMTimingCurve.h create mode 100644 src/CAMediaTimingFunction+MDMTimingCurve.m create mode 100644 src/MDMAnimationTraits.h create mode 100644 src/MDMAnimationTraits.m delete mode 100644 src/MDMMotionCurve.h delete mode 100644 src/MDMMotionCurve.m delete mode 100644 src/MDMMotionRepetition.h delete mode 100644 src/MDMMotionTiming.h create mode 100644 src/MDMRepetition.h create mode 100644 src/MDMRepetition.m create mode 100644 src/MDMRepetitionOverTime.h create mode 100644 src/MDMRepetitionOverTime.m create mode 100644 src/MDMRepetitionTraits.h create mode 100644 src/MDMSpringTimingCurve.h create mode 100644 src/MDMSpringTimingCurve.m create mode 100644 src/MDMTimingCurve.h create mode 100644 tests/unit/CAMediaTimingFunctionTests.swift create mode 100644 tests/unit/MDMAnimationTraitsTests.swift delete mode 100644 tests/unit/MDMMotionCurveTests.m delete mode 100644 tests/unit/MDMMotionCurveTests.swift create mode 100644 tests/unit/MDMRepetitionOverTimeTests.swift create mode 100644 tests/unit/MDMRepetitionTests.swift create mode 100644 tests/unit/MDMSpringTimingCurve.swift diff --git a/examples/apps/Catalog/MotionInterchangeCatalog.xcodeproj/project.pbxproj b/examples/apps/Catalog/MotionInterchangeCatalog.xcodeproj/project.pbxproj index c0139ca..899b7e7 100644 --- a/examples/apps/Catalog/MotionInterchangeCatalog.xcodeproj/project.pbxproj +++ b/examples/apps/Catalog/MotionInterchangeCatalog.xcodeproj/project.pbxproj @@ -7,14 +7,17 @@ objects = { /* Begin PBXBuildFile section */ + 660248AE1FD1EE78004C0147 /* MDMSpringTimingCurve.swift in Sources */ = {isa = PBXBuildFile; fileRef = 660248AC1FD1EE78004C0147 /* MDMSpringTimingCurve.swift */; }; 6619E1D91FA0ED0300F3AB25 /* MDMModalMovementTimingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6619E1D81FA0ED0300F3AB25 /* MDMModalMovementTimingTests.m */; }; 663ED7C51EDF1F0C0096B2A9 /* ExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 663ED7C01EDF1F0C0096B2A9 /* ExampleViewController.swift */; }; 663ED7C61EDF1F0C0096B2A9 /* ExampleViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 663ED7C11EDF1F0C0096B2A9 /* ExampleViews.swift */; }; 663ED7C71EDF1F0C0096B2A9 /* HexColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 663ED7C21EDF1F0C0096B2A9 /* HexColor.swift */; }; 663ED7C81EDF1F0C0096B2A9 /* Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 663ED7C31EDF1F0C0096B2A9 /* Layout.swift */; }; 663ED7C91EDF1F0C0096B2A9 /* ModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 663ED7C41EDF1F0C0096B2A9 /* ModalViewController.swift */; }; - 663ED8011EE628BA0096B2A9 /* MDMMotionCurveTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 663ED8001EE628BA0096B2A9 /* MDMMotionCurveTests.swift */; }; - 663ED8031EE6299A0096B2A9 /* MDMMotionCurveTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 663ED8021EE6299A0096B2A9 /* MDMMotionCurveTests.m */; }; + 664C8C531FD5A555004ED471 /* CAMediaTimingFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664C8C521FD5A555004ED471 /* CAMediaTimingFunctionTests.swift */; }; + 664C8C551FD5A7B7004ED471 /* MDMRepetitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664C8C541FD5A7B7004ED471 /* MDMRepetitionTests.swift */; }; + 664C8C571FD5A831004ED471 /* MDMRepetitionOverTimeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664C8C561FD5A831004ED471 /* MDMRepetitionOverTimeTests.swift */; }; + 664C8C591FD5A8AC004ED471 /* MDMAnimationTraitsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664C8C581FD5A8AC004ED471 /* MDMAnimationTraitsTests.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 */; }; @@ -47,14 +50,17 @@ 09CEA5DEA01BA723D08D84E6 /* 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 = ""; }; 2DE76D4D35953D836F578CDE /* Pods-MotionInterchangeCatalog.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MotionInterchangeCatalog.debug.xcconfig"; path = "../../../Pods/Target Support Files/Pods-MotionInterchangeCatalog/Pods-MotionInterchangeCatalog.debug.xcconfig"; sourceTree = ""; }; 4AAB8EBB088513D48896641A /* Pods-MotionInterchangeCatalog.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MotionInterchangeCatalog.release.xcconfig"; path = "../../../Pods/Target Support Files/Pods-MotionInterchangeCatalog/Pods-MotionInterchangeCatalog.release.xcconfig"; sourceTree = ""; }; + 660248AC1FD1EE78004C0147 /* MDMSpringTimingCurve.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MDMSpringTimingCurve.swift; sourceTree = ""; }; 6619E1D81FA0ED0300F3AB25 /* MDMModalMovementTimingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MDMModalMovementTimingTests.m; sourceTree = ""; }; 663ED7C01EDF1F0C0096B2A9 /* ExampleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleViewController.swift; sourceTree = ""; }; 663ED7C11EDF1F0C0096B2A9 /* ExampleViews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleViews.swift; sourceTree = ""; }; 663ED7C21EDF1F0C0096B2A9 /* HexColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HexColor.swift; sourceTree = ""; }; 663ED7C31EDF1F0C0096B2A9 /* Layout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Layout.swift; sourceTree = ""; }; 663ED7C41EDF1F0C0096B2A9 /* ModalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModalViewController.swift; sourceTree = ""; }; - 663ED8001EE628BA0096B2A9 /* MDMMotionCurveTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MDMMotionCurveTests.swift; sourceTree = ""; }; - 663ED8021EE6299A0096B2A9 /* MDMMotionCurveTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MDMMotionCurveTests.m; sourceTree = ""; }; + 664C8C521FD5A555004ED471 /* CAMediaTimingFunctionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CAMediaTimingFunctionTests.swift; sourceTree = ""; }; + 664C8C541FD5A7B7004ED471 /* MDMRepetitionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MDMRepetitionTests.swift; sourceTree = ""; }; + 664C8C561FD5A831004ED471 /* MDMRepetitionOverTimeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MDMRepetitionOverTimeTests.swift; sourceTree = ""; }; + 664C8C581FD5A8AC004ED471 /* MDMAnimationTraitsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MDMAnimationTraitsTests.swift; sourceTree = ""; }; 666FAA801D384A6B000363DA /* MotionInterchangeCatalog.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MotionInterchangeCatalog.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 = ""; }; @@ -162,9 +168,12 @@ 666FAA971D384A6B000363DA /* tests */ = { isa = PBXGroup; children = ( - 663ED8001EE628BA0096B2A9 /* MDMMotionCurveTests.swift */, - 663ED8021EE6299A0096B2A9 /* MDMMotionCurveTests.m */, + 664C8C521FD5A555004ED471 /* CAMediaTimingFunctionTests.swift */, + 664C8C581FD5A8AC004ED471 /* MDMAnimationTraitsTests.swift */, 6619E1D81FA0ED0300F3AB25 /* MDMModalMovementTimingTests.m */, + 664C8C541FD5A7B7004ED471 /* MDMRepetitionTests.swift */, + 664C8C561FD5A831004ED471 /* MDMRepetitionOverTimeTests.swift */, + 660248AC1FD1EE78004C0147 /* MDMSpringTimingCurve.swift */, ); name = tests; path = ../../../tests/unit; @@ -478,9 +487,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 663ED8031EE6299A0096B2A9 /* MDMMotionCurveTests.m in Sources */, - 663ED8011EE628BA0096B2A9 /* MDMMotionCurveTests.swift in Sources */, + 664C8C531FD5A555004ED471 /* CAMediaTimingFunctionTests.swift in Sources */, + 660248AE1FD1EE78004C0147 /* MDMSpringTimingCurve.swift in Sources */, 6619E1D91FA0ED0300F3AB25 /* MDMModalMovementTimingTests.m in Sources */, + 664C8C571FD5A831004ED471 /* MDMRepetitionOverTimeTests.swift in Sources */, + 664C8C591FD5A8AC004ED471 /* MDMAnimationTraitsTests.swift in Sources */, + 664C8C551FD5A7B7004ED471 /* MDMRepetitionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/scripts/v1_to_v2.sh b/scripts/v1_to_v2.sh new file mode 100755 index 0000000..b601faa --- /dev/null +++ b/scripts/v1_to_v2.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# +# 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. + +# Migration script from v1 to v2 interchange APIs. + +if [ "$#" -ne 1 ]; then + echo "Usage: $(basename $0) " + exit 1 +fi + +search_path="$1" + +replace_objc() { + find "$search_path" -type f -name "*.h" | xargs sed -i '' "$1" + find "$search_path" -type f -name "*.m" | xargs sed -i '' "$1" +} + +replace_swift() { + find "$search_path" -type f -name "*.swift" | xargs sed -i '' "$1" +} + +replace_all() { + replace_objc "$1" + replace_swift "$1" +} + +replace_all "s/timing.curve/traits.timingCurve/g" +replace_all "s/traits.curve/traits.timingCurve/g" +replace_objc "s/MDMMotionTiming/MDMAnimationTraits */g" +replace_swift "s/MotionTiming/MDMAnimationTraits/g" diff --git a/src/CAMediaTimingFunction+MDMTimingCurve.h b/src/CAMediaTimingFunction+MDMTimingCurve.h new file mode 100644 index 0000000..e140d0b --- /dev/null +++ b/src/CAMediaTimingFunction+MDMTimingCurve.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 + +#import "MDMTimingCurve.h" + +// A CAMediaTimingFunction is a timing curve - we simply define its conformity to our protocol here. +@interface CAMediaTimingFunction () +@end + +@interface CAMediaTimingFunction (MotionInterchangeExtension) + +/** + Returns a instance of the timing function with its control points reversed. + */ +- (nonnull CAMediaTimingFunction *)mdm_reversed; + +/** + Returns the first control point of the timing function. + */ +@property(nonatomic, assign, readonly) CGPoint mdm_point1; + +/** + Returns the second control point of the timing function. + */ +@property(nonatomic, assign, readonly) CGPoint mdm_point2; + +@end + diff --git a/src/CAMediaTimingFunction+MDMTimingCurve.m b/src/CAMediaTimingFunction+MDMTimingCurve.m new file mode 100644 index 0000000..f344f17 --- /dev/null +++ b/src/CAMediaTimingFunction+MDMTimingCurve.m @@ -0,0 +1,49 @@ +/* + 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 "CAMediaTimingFunction+MDMTimingCurve.h" + +@implementation CAMediaTimingFunction (MotionInterchangeExtension) + +- (CAMediaTimingFunction *)mdm_reversed { + float pt1[2]; + float pt2[2]; + [self getControlPointAtIndex:1 values:pt1]; + [self getControlPointAtIndex:2 values:pt2]; + + float reversedPt1[2]; + float reversedPt2[2]; + reversedPt1[0] = 1 - pt2[0]; + reversedPt1[1] = 1 - pt2[1]; + reversedPt2[0] = 1 - pt1[0]; + reversedPt2[1] = 1 - pt1[1]; + return [CAMediaTimingFunction functionWithControlPoints:reversedPt1[0] :reversedPt1[1] + :reversedPt2[0] :reversedPt2[1]]; +} + +- (CGPoint)mdm_point1 { + float point[2]; + [self getControlPointAtIndex:1 values:point]; + return CGPointMake(point[0], point[1]); +} + +- (CGPoint)mdm_point2 { + float point[2]; + [self getControlPointAtIndex:2 values:point]; + return CGPointMake(point[0], point[1]); +} + +@end diff --git a/src/MDMAnimationTraits.h b/src/MDMAnimationTraits.h new file mode 100644 index 0000000..3eefaac --- /dev/null +++ b/src/MDMAnimationTraits.h @@ -0,0 +1,115 @@ +/* + 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 + +#import "MDMRepetitionTraits.h" +#import "MDMTimingCurve.h" + +/** + A generic representation of animation traits. + */ +@interface MDMAnimationTraits: NSObject + +/** + Initializes the instance with the provided duration and kCAMediaTimingFunctionEaseInEaseOut timing + curve. + + @param duration The animation will occur over this length of time, in seconds. + */ +- (nonnull instancetype)initWithDuration:(NSTimeInterval)duration; + +/** + Initializes the instance with the provided duration, delay, and + kCAMediaTimingFunctionEaseInEaseOut timing curve. + + @param delay The amount of time, in seconds, to wait before starting the animation. + @param duration The animation will occur over this length of time, in seconds, after the delay time + has passed. + */ +- (nonnull instancetype)initWithDelay:(NSTimeInterval)delay duration:(NSTimeInterval)duration; + +/** + Initializes the instance with the provided duration, delay, and timing curve. + + @param delay The amount of time, in seconds, to wait before starting the animation. + @param duration The animation will occur over this length of time, in seconds, after the delay time + has passed. + @param timingCurve If provided, defines the acceleration timing for the animation. If nil, the + animation will be treated as instant and the duration/delay will be ignored. + */ +- (nonnull instancetype)initWithDelay:(NSTimeInterval)delay + duration:(NSTimeInterval)duration + timingCurve:(nullable id)timingCurve; + +/** + Initializes an animation trait with the provided timing curve, duration, delay, and repetition. + + @param duration The animation will occur over this length of time, in seconds, after the delay time + has passed. + @param delay The amount of time, in seconds, to wait before starting the animation. + @param timingCurve If provided, defines the acceleration timing for the animation. If nil, the + animation will be treated as instant and the duration/delay will be ignored. + @param repetition The repetition traits of the animation. Most often an instance of MDMRepetition + or MDMRepetitionOverTime. If nil, the animation will not repeat. + */ +- (nonnull instancetype)initWithDelay:(NSTimeInterval)delay + duration:(NSTimeInterval)duration + timingCurve:(nullable id)timingCurve + repetition:(nullable id)repetition + NS_DESIGNATED_INITIALIZER; + +#pragma mark - Traits + +/** + The amount of time, in seconds, before this animation's value interpolation should begin. + */ +@property(nonatomic, assign, readonly) NSTimeInterval delay; + +/** + The amount of time, in seconds, over which this animation should interpolate between its values. + */ +@property(nonatomic, assign, readonly) NSTimeInterval duration; + +/** + The velocity and acceleration of the animation over time. + */ +@property(nonatomic, strong, nullable, readonly) id timingCurve; + +/** + The repetition characteristics of the animation. + */ +@property(nonatomic, strong, nullable, readonly) id repetition; + +#pragma mark - Unavailable + +/** + Unavailable. + */ +- (nonnull instancetype)init NS_UNAVAILABLE; + +@end + +@interface MDMAnimationTraits (SystemTraits) + +/** + Animation traits for an iOS modal presentation slide animation. + */ +@property(nonatomic, class, strong, nonnull, readonly) MDMAnimationTraits *systemModalMovement; + +@end + diff --git a/src/MDMAnimationTraits.m b/src/MDMAnimationTraits.m new file mode 100644 index 0000000..99bf382 --- /dev/null +++ b/src/MDMAnimationTraits.m @@ -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 "MDMAnimationTraits.h" + +#import "CAMediaTimingFunction+MDMTimingCurve.h" +#import "MDMSpringTimingCurve.h" + +@implementation MDMAnimationTraits + +- (instancetype)init { + [self doesNotRecognizeSelector:_cmd]; + return nil; +} + +- (nonnull instancetype)initWithDuration:(NSTimeInterval)duration { + return [self initWithDelay:0 duration:duration]; +} + +- (instancetype)initWithDelay:(NSTimeInterval)delay duration:(NSTimeInterval)duration { + CAMediaTimingFunction *easeInOut = + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + return [self initWithDelay:delay duration:duration timingCurve:easeInOut]; +} + +- (instancetype)initWithDelay:(NSTimeInterval)delay + duration:(NSTimeInterval)duration + timingCurve:(id)timingCurve { + return [self initWithDelay:delay duration:duration timingCurve:timingCurve repetition:nil]; +} + +- (instancetype)initWithDelay:(NSTimeInterval)delay + duration:(NSTimeInterval)duration + timingCurve:(id)timingCurve + repetition:(id)repetition { + self = [super init]; + if (self) { + _duration = duration; + _delay = delay; + _timingCurve = timingCurve; + _repetition = repetition; + } + return self; +} + +@end + +@implementation MDMAnimationTraits (SystemTraits) + ++ (MDMAnimationTraits *)systemModalMovement { + MDMSpringTimingCurve *timingCurve = [[MDMSpringTimingCurve alloc] initWithMass:3 + tension:1000 + friction:500]; + return [[MDMAnimationTraits alloc] initWithDelay:0 duration:0.500 timingCurve:timingCurve]; +} + +@end + diff --git a/src/MDMMotionCurve.h b/src/MDMMotionCurve.h deleted file mode 100644 index 0b233eb..0000000 --- a/src/MDMMotionCurve.h +++ /dev/null @@ -1,194 +0,0 @@ -/* - 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 -#import - -/** - The possible kinds of motion curves that can be used to describe an animation. - */ -typedef NS_ENUM(NSUInteger, MDMMotionCurveType) { - /** - The value will be instantly set with no animation. - */ - MDMMotionCurveTypeInstant, - - /** - The value will be animated using a cubic bezier curve to model its velocity. - */ - MDMMotionCurveTypeBezier, - - /** - The value will be animated using a spring simulation. - - A spring will treat the duration property of the motion timing as a suggestion and may choose to - ignore it altogether. - */ - MDMMotionCurveTypeSpring, - - /** - The default curve will be used. - */ - MDMMotionCurveTypeDefault __deprecated_enum_msg("Use MDMMotionCurveTypeBezier instead."), - -} NS_SWIFT_NAME(MotionCurveType); - -/** - A generalized representation of a motion curve. - */ -struct MDMMotionCurve { - /** - The type defines how to interpret the data values. - */ - MDMMotionCurveType type; - - /** - The data values corresponding with this curve. - */ - CGFloat data[4]; -} NS_SWIFT_NAME(MotionCurve); -typedef struct MDMMotionCurve MDMMotionCurve; - -/** - Creates a bezier motion curve with the provided control points. - - A cubic bezier has four control points in total. We assume that the first control point is 0, 0 and - the last control point is 1, 1. This method requires that you provide the second and third control - points. - - See the documentation for CAMediaTimingFunction for more information. - */ -// clang-format off -FOUNDATION_EXTERN -MDMMotionCurve MDMMotionCurveMakeBezier(CGFloat p1x, CGFloat p1y, CGFloat p2x, CGFloat p2y) - NS_SWIFT_NAME(MotionCurveMakeBezier(p1x:p1y:p2x:p2y:)); -// clang-format on - -// clang-format off -FOUNDATION_EXTERN -MDMMotionCurve MDMMotionCurveFromTimingFunction(CAMediaTimingFunction * _Nonnull timingFunction) - NS_SWIFT_NAME(MotionCurve(fromTimingFunction:)); -// clang-format on - -/** - Creates a spring curve with the provided configuration. - - Tension and friction map to Core Animation's stiffness and damping, respectively. - - See the documentation for CASpringAnimation for more information. - */ -// clang-format off -FOUNDATION_EXTERN MDMMotionCurve MDMMotionCurveMakeSpring(CGFloat mass, - CGFloat tension, - CGFloat friction) - NS_SWIFT_NAME(MotionCurveMakeSpring(mass:tension:friction:)); -// clang-format on - -/** - Creates a spring curve with the provided configuration. - - Tension and friction map to Core Animation's stiffness and damping, respectively. - - See the documentation for CASpringAnimation for more information. - */ -// clang-format off -FOUNDATION_EXTERN -MDMMotionCurve MDMMotionCurveMakeSpringWithInitialVelocity(CGFloat mass, - CGFloat tension, - CGFloat friction, - CGFloat initialVelocity) - NS_SWIFT_NAME(MotionCurveMakeSpring(mass:tension:friction:initialVelocity:)); -// clang-format on - -/** - For cubic bezier curves, returns a reversed cubic bezier curve. For all other curve types, a copy - of the original curve is returned. - */ -// clang-format off -FOUNDATION_EXTERN MDMMotionCurve MDMMotionCurveReversedBezier(MDMMotionCurve motionCurve) - NS_SWIFT_NAME(MotionCurveReversedBezier(fromMotionCurve:)); -// clang-format on - -/** - Named indices for the bezier motion curve's data array. - */ -typedef NS_ENUM(NSUInteger, MDMBezierMotionCurveDataIndex) { - MDMBezierMotionCurveDataIndexP1X, - MDMBezierMotionCurveDataIndexP1Y, - MDMBezierMotionCurveDataIndexP2X, - MDMBezierMotionCurveDataIndexP2Y -} NS_SWIFT_NAME(BezierMotionCurveDataIndex); - -/** - Named indices for the spring motion curve's data array. - */ -typedef NS_ENUM(NSUInteger, MDMSpringMotionCurveDataIndex) { - MDMSpringMotionCurveDataIndexMass, - MDMSpringMotionCurveDataIndexTension, - MDMSpringMotionCurveDataIndexFriction, - - /** - The initial velocity of the animation. - - A value of zero indicates no initial velocity. - A positive value indicates movement toward the destination. - A negative value indicates movement away from the destination. - - The value's units are dependent on the context and the value being animated. - */ - MDMSpringMotionCurveDataIndexInitialVelocity -} NS_SWIFT_NAME(SpringMotionCurveDataIndex); - -// Objective-C-specific macros - -#define _MDMBezier(p1x, p1y, p2x, p2y) \ - ((MDMMotionCurve) { \ - .type = MDMMotionCurveTypeBezier, \ - .data = { p1x, \ - p1y, \ - p2x, \ - p2y } \ - }) - -#define _MDMSpring(mass, tension, friction) \ - ((MDMMotionCurve) { \ - .type = MDMMotionCurveTypeSpring, \ - .data = { mass, \ - tension, \ - friction } \ - }) - -#define _MDMSpringWithInitialVelocity(mass, tension, friction, initialVelocity) \ - ((MDMMotionCurve) { \ - .type = MDMMotionCurveTypeSpring, \ - .data = { mass, \ - tension, \ - friction, \ - initialVelocity } \ - }) - -/** - A linear bezier motion curve. - */ -#define MDMLinearMotionCurve _MDMBezier(0, 0, 1, 1) - -/** - Timing information for an iOS modal presentation slide animation. - */ -#define MDMModalMovementTiming { \ - .delay = 0.000, .duration = 0.500, .curve = _MDMSpring(3, 1000, 500) \ -} diff --git a/src/MDMMotionCurve.m b/src/MDMMotionCurve.m deleted file mode 100644 index 2cc57c8..0000000 --- a/src/MDMMotionCurve.m +++ /dev/null @@ -1,51 +0,0 @@ -/* - 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 "MDMMotionCurve.h" - -MDMMotionCurve MDMMotionCurveMakeBezier(CGFloat p1x, CGFloat p1y, CGFloat p2x, CGFloat p2y) { - return _MDMBezier(p1x, p1y, p2x, p2y); -} - -MDMMotionCurve MDMMotionCurveMakeSpring(CGFloat mass, CGFloat tension, CGFloat friction) { - return MDMMotionCurveMakeSpringWithInitialVelocity(mass, tension, friction, 0); -} - -MDMMotionCurve MDMMotionCurveMakeSpringWithInitialVelocity(CGFloat mass, - CGFloat tension, - CGFloat friction, - CGFloat initialVelocity) { - return _MDMSpringWithInitialVelocity(mass, tension, friction, initialVelocity); -} - -MDMMotionCurve MDMMotionCurveFromTimingFunction(CAMediaTimingFunction *timingFunction) { - float pt1[2]; - float pt2[2]; - [timingFunction getControlPointAtIndex:1 values:pt1]; - [timingFunction getControlPointAtIndex:2 values:pt2]; - return MDMMotionCurveMakeBezier(pt1[0], pt1[1], pt2[0], pt2[1]); -} - -MDMMotionCurve MDMMotionCurveReversedBezier(MDMMotionCurve motionCurve) { - MDMMotionCurve reversed = motionCurve; - if (motionCurve.type == MDMMotionCurveTypeBezier) { - reversed.data[0] = 1 - motionCurve.data[2]; - reversed.data[1] = 1 - motionCurve.data[3]; - reversed.data[2] = 1 - motionCurve.data[0]; - reversed.data[3] = 1 - motionCurve.data[1]; - } - return reversed; -} diff --git a/src/MDMMotionRepetition.h b/src/MDMMotionRepetition.h deleted file mode 100644 index 94fc266..0000000 --- a/src/MDMMotionRepetition.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - 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 - -/** - The possible kinds of repetition that can be used to describe an animation. - */ -typedef NS_ENUM(NSUInteger, MDMMotionRepetitionType) { - /** - The animation will be not be repeated. - */ - MDMMotionRepetitionTypeNone, - - /** - The animation will be repeated a given number of times. - */ - MDMMotionRepetitionTypeCount, - - /** - The animation will be repeated for a given number of seconds. - */ - MDMMotionRepetitionTypeDuration, - -} NS_SWIFT_NAME(MotionReptitionType); - -/** - A generalized representation of a motion curve. - */ -struct MDMMotionRepetition { - /** - The type defines how to interpret the amount. - */ - MDMMotionRepetitionType type; - - /** - The amount of repetition. - */ - double amount; - - /** - Whether the animation should animate backwards after animating forwards. - */ - BOOL autoreverses; - -} NS_SWIFT_NAME(MotionRepetition); -typedef struct MDMMotionRepetition MDMMotionRepetition; - -// Objective-C-specific macros - -#define _MDMNoRepetition \ - (MDMMotionRepetition) { \ - .type = MDMMotionRepetitionTypeNone, \ - .amount = 0, \ - .autoreverses = false \ - } diff --git a/src/MDMMotionTiming.h b/src/MDMMotionTiming.h deleted file mode 100644 index b225570..0000000 --- a/src/MDMMotionTiming.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - 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 - -#import "MDMMotionCurve.h" -#import "MDMMotionRepetition.h" - -/** - A representation of timing for an animation. - */ -struct MDMMotionTiming { - /** - The amount of time, in seconds, before this animation's value interpolation should begin. - */ - CFTimeInterval delay; - - /** - The amount of time, in seconds, over which this animation should interpolate between its values. - */ - CFTimeInterval duration; - - /** - The velocity and acceleration of the animation over time. - */ - MDMMotionCurve curve; - - /** - The repetition characteristics of the animation. - */ - MDMMotionRepetition repetition; - -} NS_SWIFT_NAME(MotionTiming); -typedef struct MDMMotionTiming MDMMotionTiming; diff --git a/src/MDMRepetition.h b/src/MDMRepetition.h new file mode 100644 index 0000000..523082f --- /dev/null +++ b/src/MDMRepetition.h @@ -0,0 +1,60 @@ +/* + 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 "MDMRepetitionTraits.h" + +/** + Represents repetition that repeats a specific number of times. + */ +@interface MDMRepetition: NSObject + +/** + Initializes the instance with the given number of repetitions. + + Autoreversing is disabled. + + @param numberOfRepetitions May be fractional. Initializing with greatestFiniteMagnitude will cause + the animation to repeat forever. + */ +- (nonnull instancetype)initWithNumberOfRepetitions:(double)numberOfRepetitions; + +/** + Initializes the instance with the given number of repetitions and autoreversal behavior. + + @param numberOfRepetitions May be fractional. Initializing with greatestFiniteMagnitude will cause + the animation to repeat forever. + @param autoreverses Whether the animation should animate backwards after animating forwards. + */ +- (nonnull instancetype)initWithNumberOfRepetitions:(double)numberOfRepetitions + autoreverses:(BOOL)autoreverses + NS_DESIGNATED_INITIALIZER; + +#pragma mark - Traits + +/** + The number of repetitions that will occur before this animation stops repeating. + */ +@property(nonatomic, assign, readonly) double numberOfRepetitions; + +/** + Unavailable. + */ +- (nonnull instancetype)init NS_UNAVAILABLE; + +@end + diff --git a/src/MDMRepetition.m b/src/MDMRepetition.m new file mode 100644 index 0000000..e3d5a1c --- /dev/null +++ b/src/MDMRepetition.m @@ -0,0 +1,43 @@ +/* + 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 "MDMRepetition.h" + +@implementation MDMRepetition + +@synthesize autoreverses = _autoreverses; + +- (instancetype)init { + [self doesNotRecognizeSelector:_cmd]; + return nil; +} + +- (instancetype)initWithNumberOfRepetitions:(double)numberOfRepetitions { + return [self initWithNumberOfRepetitions:numberOfRepetitions autoreverses:NO]; +} + +- (instancetype)initWithNumberOfRepetitions:(double)numberOfRepetitions + autoreverses:(BOOL)autoreverses { + self = [super init]; + if (self) { + _numberOfRepetitions = numberOfRepetitions; + _autoreverses = autoreverses; + } + return self; +} + +@end + diff --git a/src/MDMRepetitionOverTime.h b/src/MDMRepetitionOverTime.h new file mode 100644 index 0000000..507b422 --- /dev/null +++ b/src/MDMRepetitionOverTime.h @@ -0,0 +1,55 @@ +/* + 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 "MDMRepetitionTraits.h" + +/** + Represents repetition that repeats until a specific duration has passed. + */ +@interface MDMRepetitionOverTime: NSObject + +/** + Initializes the instance with the given duration. + + @param duration The amount of time, in seconds, over which the animation will repeat. + */ +- (nonnull instancetype)initWithDuration:(double)duration; + +/** + Initializes the instance with the given duration and autoreversal behavior. + + @param duration The amount of time, in seconds, over which the animation will repeat. + @param autoreverses Whether the animation should animate backwards after animating forwards. + */ +- (nonnull instancetype)initWithDuration:(double)duration autoreverses:(BOOL)autoreverses + NS_DESIGNATED_INITIALIZER; + +#pragma mark - Traits + +/** + The amount of time, in seconds, that will pass before this animation stops repeating. + */ +@property(nonatomic, assign, readonly) double duration; + +/** + Unavailable. + */ +- (nonnull instancetype)init NS_UNAVAILABLE; + +@end + diff --git a/src/MDMRepetitionOverTime.m b/src/MDMRepetitionOverTime.m new file mode 100644 index 0000000..55cdcab --- /dev/null +++ b/src/MDMRepetitionOverTime.m @@ -0,0 +1,42 @@ +/* + 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 "MDMRepetitionOverTime.h" + +@implementation MDMRepetitionOverTime + +@synthesize autoreverses = _autoreverses; + +- (instancetype)init { + [self doesNotRecognizeSelector:_cmd]; + return nil; +} + +- (instancetype)initWithDuration:(double)duration { + return [self initWithDuration:duration autoreverses:NO]; +} + +- (instancetype)initWithDuration:(double)duration autoreverses:(BOOL)autoreverses { + self = [super init]; + if (self) { + _duration = duration; + _autoreverses = autoreverses; + } + return self; +} + +@end + diff --git a/src/MDMRepetitionTraits.h b/src/MDMRepetitionTraits.h new file mode 100644 index 0000000..211f227 --- /dev/null +++ b/src/MDMRepetitionTraits.h @@ -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 + +/** + A generalized representation of a repetition traits. + */ +@protocol MDMRepetitionTraits + +/** + Whether the animation should animate backwards after animating forwards. + */ +@property(nonatomic, assign, readonly) BOOL autoreverses; + +@end + diff --git a/src/MDMSpringTimingCurve.h b/src/MDMSpringTimingCurve.h new file mode 100644 index 0000000..30f7ca9 --- /dev/null +++ b/src/MDMSpringTimingCurve.h @@ -0,0 +1,95 @@ +/* + 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 + +#import "MDMTimingCurve.h" + +/** + A timing curve that represents the motion of a single-dimensional attached spring. + */ +@interface MDMSpringTimingCurve: NSObject + +/** + Initializes the timing curve with the given parameters and an initial velocity of zero. + + @param mass The mass of the spring simulation. Affects the animation's momentum. + @param tension The tension of the spring simulation. Affects how quickly the animation moves + toward its destination. + @param friction The friction of the spring simulation. Affects how quickly the animation starts + and stops. + */ +- (nonnull instancetype)initWithMass:(CGFloat)mass + tension:(CGFloat)tension + friction:(CGFloat)friction; + +/** + Initializes the timing curve with the given parameters. + + @param mass The mass of the spring simulation. Affects the animation's momentum. + @param tension The tension of the spring simulation. Affects how quickly the animation moves + toward its destination. + @param friction The friction of the spring simulation. Affects how quickly the animation starts + and stops. + @param initialVelocity The initial velocity of the spring simulation. Measured in units of + translation per second. For example, if the property being animated is positional, then this value + is in screen units per second. + */ +- (nonnull instancetype)initWithMass:(CGFloat)mass + tension:(CGFloat)tension + friction:(CGFloat)friction + initialVelocity:(CGFloat)initialVelocity + NS_DESIGNATED_INITIALIZER; + +#pragma mark - Traits + +/** + The mass of the spring simulation. + + Affects the animation's momentum. This is usually 1. + */ +@property(nonatomic, assign, readonly) CGFloat mass; + +/** + The tension of the spring simulation. + + Affects how quickly the animation moves toward its destination. + */ +@property(nonatomic, assign, readonly) CGFloat tension; + +/** + The friction of the spring simulation. + + Affects how quickly the animation starts and stops. + */ +@property(nonatomic, assign, readonly) CGFloat friction; + +/** + The initial velocity of the spring simulation. + + Measured in units of translation per second. + */ +@property(nonatomic, assign, readonly) CGFloat initialVelocity; + +/** + Unavailable. + */ +- (nonnull instancetype)init NS_UNAVAILABLE; + +@end + + diff --git a/src/MDMSpringTimingCurve.m b/src/MDMSpringTimingCurve.m new file mode 100644 index 0000000..138a556 --- /dev/null +++ b/src/MDMSpringTimingCurve.m @@ -0,0 +1,45 @@ +/* + 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 "MDMSpringTimingCurve.h" + +@implementation MDMSpringTimingCurve + +- (instancetype)init { + [self doesNotRecognizeSelector:_cmd]; + return nil; +} + +- (instancetype)initWithMass:(CGFloat)mass tension:(CGFloat)tension friction:(CGFloat)friction { + return [self initWithMass:mass tension:tension friction:friction initialVelocity:0]; +} + +- (instancetype)initWithMass:(CGFloat)mass + tension:(CGFloat)tension + friction:(CGFloat)friction + initialVelocity:(CGFloat)initialVelocity { + self = [super init]; + if (self) { + _mass = mass; + _tension = tension; + _friction = friction; + _initialVelocity = initialVelocity; + } + return self; +} + +@end + diff --git a/src/MDMTimingCurve.h b/src/MDMTimingCurve.h new file mode 100644 index 0000000..3a628ec --- /dev/null +++ b/src/MDMTimingCurve.h @@ -0,0 +1,24 @@ +/* + 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 + +/** + A generalized representation of a timing curve. + */ +@protocol MDMTimingCurve +@end diff --git a/src/MotionInterchange.h b/src/MotionInterchange.h index 33e8de8..540e559 100644 --- a/src/MotionInterchange.h +++ b/src/MotionInterchange.h @@ -14,6 +14,10 @@ limitations under the License. */ -#import "MDMMotionCurve.h" -#import "MDMMotionRepetition.h" -#import "MDMMotionTiming.h" +#import "CAMediaTimingFunction+MDMTimingCurve.h" +#import "MDMAnimationTraits.h" +#import "MDMRepetitionTraits.h" +#import "MDMRepetition.h" +#import "MDMRepetitionOverTime.h" +#import "MDMTimingCurve.h" +#import "MDMSpringTimingCurve.h" diff --git a/tests/unit/CAMediaTimingFunctionTests.swift b/tests/unit/CAMediaTimingFunctionTests.swift new file mode 100644 index 0000000..ae953ec --- /dev/null +++ b/tests/unit/CAMediaTimingFunctionTests.swift @@ -0,0 +1,39 @@ +/* + 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 MotionInterchange + +class CAMediaTimingFunctionTests: XCTestCase { + func testReversalAlgorithm() { + let curve = CAMediaTimingFunction(controlPoints: 0.1, 0.2, 0.3, 0.4) + let reversed = curve.mdm_reversed() + XCTAssertEqualWithAccuracy(curve.mdm_point1.x, 1 - reversed.mdm_point2.x, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.mdm_point1.y, 1 - reversed.mdm_point2.y, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.mdm_point2.x, 1 - reversed.mdm_point1.x, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.mdm_point2.y, 1 - reversed.mdm_point1.y, accuracy: 0.001) + } + + func testReversingBezierCurveTwiceGivesSameResult() { + let curve = CAMediaTimingFunction(controlPoints: 0.1, 0.2, 0.3, 0.4) + let reversed = curve.mdm_reversed() + let reversedAgain = reversed.mdm_reversed() + XCTAssertEqualWithAccuracy(curve.mdm_point1.x, reversedAgain.mdm_point1.x, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.mdm_point1.y, reversedAgain.mdm_point1.y, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.mdm_point2.x, reversedAgain.mdm_point2.x, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.mdm_point2.y, reversedAgain.mdm_point2.y, accuracy: 0.001) + } +} diff --git a/tests/unit/MDMAnimationTraitsTests.swift b/tests/unit/MDMAnimationTraitsTests.swift new file mode 100644 index 0000000..8755967 --- /dev/null +++ b/tests/unit/MDMAnimationTraitsTests.swift @@ -0,0 +1,113 @@ +/* + 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 MotionInterchange + +class MDMAnimationTraitsTests: XCTestCase { + + func testInitializerValuesWithDuration() { + let traits = MDMAnimationTraits(duration: 0.5) + + XCTAssertEqualWithAccuracy(traits.duration, 0.5, accuracy: 0.001) + XCTAssertEqualWithAccuracy(traits.delay, 0, accuracy: 0.001) + XCTAssertTrue(traits.timingCurve is CAMediaTimingFunction) + if let timingCurve = traits.timingCurve as? CAMediaTimingFunction { + let easeInOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + XCTAssertEqualWithAccuracy(timingCurve.mdm_point1.x, easeInOut.mdm_point1.x, accuracy: 0.001) + XCTAssertEqualWithAccuracy(timingCurve.mdm_point1.y, easeInOut.mdm_point1.y, accuracy: 0.001) + XCTAssertEqualWithAccuracy(timingCurve.mdm_point2.x, easeInOut.mdm_point2.x, accuracy: 0.001) + XCTAssertEqualWithAccuracy(timingCurve.mdm_point2.y, easeInOut.mdm_point2.y, accuracy: 0.001) + } + XCTAssertNil(traits.repetition) + } + + func testInitializerValuesWithDurationDelay() { + let traits = MDMAnimationTraits(delay: 0.2, duration: 0.5) + + XCTAssertEqualWithAccuracy(traits.duration, 0.5, accuracy: 0.001) + XCTAssertEqualWithAccuracy(traits.delay, 0.2, accuracy: 0.001) + XCTAssertTrue(traits.timingCurve is CAMediaTimingFunction) + if let timingCurve = traits.timingCurve as? CAMediaTimingFunction { + let easeInOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + XCTAssertEqualWithAccuracy(timingCurve.mdm_point1.x, easeInOut.mdm_point1.x, accuracy: 0.001) + XCTAssertEqualWithAccuracy(timingCurve.mdm_point1.y, easeInOut.mdm_point1.y, accuracy: 0.001) + XCTAssertEqualWithAccuracy(timingCurve.mdm_point2.x, easeInOut.mdm_point2.x, accuracy: 0.001) + XCTAssertEqualWithAccuracy(timingCurve.mdm_point2.y, easeInOut.mdm_point2.y, accuracy: 0.001) + } + XCTAssertNil(traits.repetition) + } + + func testInitializerValuesWithDurationDelayNilTimingCurve() { + let traits = MDMAnimationTraits(delay: 0.2, duration: 0.5, timingCurve: nil) + + XCTAssertEqualWithAccuracy(traits.duration, 0.5, accuracy: 0.001) + XCTAssertEqualWithAccuracy(traits.delay, 0.2, accuracy: 0.001) + XCTAssertNil(traits.timingCurve) + XCTAssertNil(traits.repetition) + } + + func testInitializerValuesWithDurationDelayLinearTimingCurve() { + let linear = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) + let traits = MDMAnimationTraits(delay: 0.2, duration: 0.5, timingCurve: linear) + + XCTAssertEqualWithAccuracy(traits.duration, 0.5, accuracy: 0.001) + XCTAssertEqualWithAccuracy(traits.delay, 0.2, accuracy: 0.001) + XCTAssertTrue(traits.timingCurve is CAMediaTimingFunction) + if let timingCurve = traits.timingCurve as? CAMediaTimingFunction { + XCTAssertEqualWithAccuracy(timingCurve.mdm_point1.x, linear.mdm_point1.x, accuracy: 0.001) + XCTAssertEqualWithAccuracy(timingCurve.mdm_point1.y, linear.mdm_point1.y, accuracy: 0.001) + XCTAssertEqualWithAccuracy(timingCurve.mdm_point2.x, linear.mdm_point2.x, accuracy: 0.001) + XCTAssertEqualWithAccuracy(timingCurve.mdm_point2.y, linear.mdm_point2.y, accuracy: 0.001) + } + XCTAssertNil(traits.repetition) + } + + func testInitializerValuesWithDurationDelaySpringTimingCurve() { + let spring = MDMSpringTimingCurve(mass: 0.7, tension: 0.8, friction: 0.9) + let traits = MDMAnimationTraits(delay: 0.2, duration: 0.5, timingCurve: spring) + + XCTAssertEqualWithAccuracy(traits.duration, 0.5, accuracy: 0.001) + XCTAssertEqualWithAccuracy(traits.delay, 0.2, accuracy: 0.001) + XCTAssertTrue(traits.timingCurve is MDMSpringTimingCurve) + if let timingCurve = traits.timingCurve as? MDMSpringTimingCurve { + XCTAssertEqualWithAccuracy(timingCurve.mass, spring.mass, accuracy: 0.001) + XCTAssertEqualWithAccuracy(timingCurve.friction, spring.friction, accuracy: 0.001) + XCTAssertEqualWithAccuracy(timingCurve.tension, spring.tension, accuracy: 0.001) + XCTAssertEqualWithAccuracy(timingCurve.initialVelocity, spring.initialVelocity, + accuracy: 0.001) + } + XCTAssertNil(traits.repetition) + } + + func testInitializerValuesWithDurationDelayNilTimingCurveRepetition() { + let repetition = MDMRepetition(numberOfRepetitions: 5) + let traits = MDMAnimationTraits(delay: 0.2, + duration: 0.5, + timingCurve: nil, + repetition: repetition) + + XCTAssertEqualWithAccuracy(traits.duration, 0.5, accuracy: 0.001) + XCTAssertEqualWithAccuracy(traits.delay, 0.2, accuracy: 0.001) + XCTAssertNil(traits.timingCurve) + XCTAssertTrue(traits.repetition is MDMRepetition) + if let setRepetition = traits.repetition as? MDMRepetition { + XCTAssertEqualWithAccuracy(setRepetition.numberOfRepetitions, repetition.numberOfRepetitions, + accuracy: 0.001) + XCTAssertEqual(setRepetition.autoreverses, repetition.autoreverses) + } + } +} diff --git a/tests/unit/MDMModalMovementTimingTests.m b/tests/unit/MDMModalMovementTimingTests.m index f955a9a..d5aa2b2 100644 --- a/tests/unit/MDMModalMovementTimingTests.m +++ b/tests/unit/MDMModalMovementTimingTests.m @@ -18,7 +18,7 @@ #import "MotionInterchange.h" -@interface MDMModalMovementTimingTests : XCTestCase +@interface MDMAnimationTraitsSystemModalMovementTests : XCTestCase @property(nonatomic, strong) UIWindow *window; @end @@ -39,7 +39,7 @@ - (void)viewDidLayoutSubviews { @end -@implementation MDMModalMovementTimingTests +@implementation MDMAnimationTraitsSystemModalMovementTests - (void)setUp { [super setUp]; @@ -72,16 +72,19 @@ - (void)testSystemModalMovementTimingCurveMatchesModalMovementTiming { CASpringAnimation *springAnimation = (CASpringAnimation *)presentedViewController.presentationPositionAnimation; - MDMMotionTiming timing = MDMModalMovementTiming; - XCTAssertEqualWithAccuracy(timing.curve.data[MDMSpringMotionCurveDataIndexMass], - springAnimation.mass, - 0.001); - XCTAssertEqualWithAccuracy(timing.curve.data[MDMSpringMotionCurveDataIndexTension], - springAnimation.stiffness, - 0.001); - XCTAssertEqualWithAccuracy(timing.curve.data[MDMSpringMotionCurveDataIndexFriction], - springAnimation.damping, - 0.001); + MDMAnimationTraits *traits = [MDMAnimationTraits systemModalMovement]; + XCTAssertTrue([traits.timingCurve isKindOfClass:[MDMSpringTimingCurve class]], + @"Expected the system timing curve to be a %@ type, but it was '%@' instead.", + NSStringFromClass([MDMSpringTimingCurve class]), + NSStringFromClass([traits.timingCurve class])); + if ([traits.timingCurve isKindOfClass:[MDMSpringTimingCurve class]]) { + MDMSpringTimingCurve *spring = (MDMSpringTimingCurve *)traits.timingCurve; + + XCTAssertEqualWithAccuracy(spring.mass, springAnimation.mass, 0.001); + XCTAssertEqualWithAccuracy(spring.tension, springAnimation.stiffness, 0.001); + XCTAssertEqualWithAccuracy(spring.friction, springAnimation.damping, 0.001); + XCTAssertEqualWithAccuracy(spring.initialVelocity, springAnimation.initialVelocity, 0.001); + } } @end diff --git a/tests/unit/MDMMotionCurveTests.m b/tests/unit/MDMMotionCurveTests.m deleted file mode 100644 index 0cf4941..0000000 --- a/tests/unit/MDMMotionCurveTests.m +++ /dev/null @@ -1,75 +0,0 @@ -/* - 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 "MotionInterchange.h" - -@interface MDMMotionCurveTests : XCTestCase -@end - -@implementation MDMMotionCurveTests - -- (void)testLinearCurveConstantMatchesSystemLinearCurve { - MDMMotionCurve curve = MDMLinearMotionCurve; - CAMediaTimingFunction *linearTimingFunction = - [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; - MDMMotionCurve systemLinearCurve = MDMMotionCurveFromTimingFunction(linearTimingFunction); - XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP1X], - systemLinearCurve.data[MDMBezierMotionCurveDataIndexP1X], - 0.001); - XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP1Y], - systemLinearCurve.data[MDMBezierMotionCurveDataIndexP1Y], - 0.001); - XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP2X], - systemLinearCurve.data[MDMBezierMotionCurveDataIndexP2X], - 0.001); - XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP2Y], - systemLinearCurve.data[MDMBezierMotionCurveDataIndexP2Y], - 0.001); -} - -- (void)testBezierCurveData { - MDMMotionCurve curve = MDMMotionCurveMakeBezier(0.1f, 0.2f, 0.3f, 0.4f); - XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP1X], 0.1, 0.001); - XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP1Y], 0.2, 0.001); - XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP2X], 0.3, 0.001); - XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP2Y], 0.4, 0.001); -} - -- (void)testSpringCurveData { - MDMMotionCurve curve = MDMMotionCurveMakeSpring(0.1f, 0.2f, 0.3f); - XCTAssertEqualWithAccuracy(curve.data[MDMSpringMotionCurveDataIndexMass], 0.1, 0.001); - XCTAssertEqualWithAccuracy(curve.data[MDMSpringMotionCurveDataIndexTension], 0.2, 0.001); - XCTAssertEqualWithAccuracy(curve.data[MDMSpringMotionCurveDataIndexFriction], 0.3, 0.001); -} - -- (void)testBezierCurveDataWithMacro { - MDMMotionCurve curve = _MDMBezier(0.1, 0.2, 0.3, 0.4); - XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP1X], 0.1, 0.001); - XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP1Y], 0.2, 0.001); - XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP2X], 0.3, 0.001); - XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP2Y], 0.4, 0.001); -} - -- (void)testSpringCurveDataWithMacro { - MDMMotionCurve curve = _MDMSpring(0.1, 0.2, 0.3); - XCTAssertEqualWithAccuracy(curve.data[MDMSpringMotionCurveDataIndexMass], 0.1, 0.001); - XCTAssertEqualWithAccuracy(curve.data[MDMSpringMotionCurveDataIndexTension], 0.2, 0.001); - XCTAssertEqualWithAccuracy(curve.data[MDMSpringMotionCurveDataIndexFriction], 0.3, 0.001); -} - -@end diff --git a/tests/unit/MDMMotionCurveTests.swift b/tests/unit/MDMMotionCurveTests.swift deleted file mode 100644 index 0f1fb27..0000000 --- a/tests/unit/MDMMotionCurveTests.swift +++ /dev/null @@ -1,65 +0,0 @@ -/* - 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 MotionInterchange - -class MDMMotionCurveTests: XCTestCase { - - func testBezierCurveData() { - let curve = MotionCurveMakeBezier(p1x: 0.1, p1y: 0.2, p2x: 0.3, p2y: 0.4) - XCTAssertEqualWithAccuracy(curve.data.0, 0.1, accuracy: 0.001) - XCTAssertEqualWithAccuracy(curve.data.1, 0.2, accuracy: 0.001) - XCTAssertEqualWithAccuracy(curve.data.2, 0.3, accuracy: 0.001) - XCTAssertEqualWithAccuracy(curve.data.3, 0.4, accuracy: 0.001) - } - - func testBezierCurveFromTimingFunction() { - let timingFunction = CAMediaTimingFunction(controlPoints: 0.1, 0.2, 0.3, 0.4) - let curve = MotionCurve(fromTimingFunction: timingFunction) - XCTAssertEqualWithAccuracy(curve.data.0, 0.1, accuracy: 0.001) - XCTAssertEqualWithAccuracy(curve.data.1, 0.2, accuracy: 0.001) - XCTAssertEqualWithAccuracy(curve.data.2, 0.3, accuracy: 0.001) - XCTAssertEqualWithAccuracy(curve.data.3, 0.4, accuracy: 0.001) - } - - func testSpringCurveData() { - let curve = MotionCurveMakeSpring(mass: 0.1, tension: 0.2, friction: 0.3) - XCTAssertEqualWithAccuracy(curve.data.0, 0.1, accuracy: 0.001) // mass - XCTAssertEqualWithAccuracy(curve.data.1, 0.2, accuracy: 0.001) // tension - XCTAssertEqualWithAccuracy(curve.data.2, 0.3, accuracy: 0.001) // friction - XCTAssertEqualWithAccuracy(curve.data.3, 0.0, accuracy: 0.001) - } - - func testReversedBezierCurve() { - let curve = MotionCurveMakeBezier(p1x: 0.1, p1y: 0.2, p2x: 0.3, p2y: 0.4) - let reversed = MotionCurveReversedBezier(fromMotionCurve: curve) - XCTAssertEqualWithAccuracy(curve.data.0, 1 - reversed.data.2, accuracy: 0.001) - XCTAssertEqualWithAccuracy(curve.data.1, 1 - reversed.data.3, accuracy: 0.001) - XCTAssertEqualWithAccuracy(curve.data.2, 1 - reversed.data.0, accuracy: 0.001) - XCTAssertEqualWithAccuracy(curve.data.3, 1 - reversed.data.1, accuracy: 0.001) - } - - func testReversingBezierCurveTwiceGivesSameResult() { - let curve = MotionCurveMakeBezier(p1x: 0.1, p1y: 0.2, p2x: 0.3, p2y: 0.4) - let reversed = MotionCurveReversedBezier(fromMotionCurve: curve) - let reversedAgain = MotionCurveReversedBezier(fromMotionCurve: reversed) - XCTAssertEqualWithAccuracy(curve.data.0, reversedAgain.data.0, accuracy: 0.001) - XCTAssertEqualWithAccuracy(curve.data.1, reversedAgain.data.1, accuracy: 0.001) - XCTAssertEqualWithAccuracy(curve.data.2, reversedAgain.data.2, accuracy: 0.001) - XCTAssertEqualWithAccuracy(curve.data.3, reversedAgain.data.3, accuracy: 0.001) - } -} diff --git a/tests/unit/MDMRepetitionOverTimeTests.swift b/tests/unit/MDMRepetitionOverTimeTests.swift new file mode 100644 index 0000000..f571d07 --- /dev/null +++ b/tests/unit/MDMRepetitionOverTimeTests.swift @@ -0,0 +1,33 @@ +/* + 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 MotionInterchange + +class MDMRepetitionOverTimeTests: XCTestCase { + + func testInitializationWithDuration() { + let repetition = MDMRepetitionOverTime(duration: 5.5) + XCTAssertEqualWithAccuracy(repetition.duration, 5.5, accuracy: 0.001) + XCTAssertFalse(repetition.autoreverses) + } + + func testInitializationWithDurationAndAutoreversed() { + let repetition = MDMRepetitionOverTime(duration: 5.5, autoreverses: true) + XCTAssertEqualWithAccuracy(repetition.duration, 5.5, accuracy: 0.001) + XCTAssertTrue(repetition.autoreverses) + } +} diff --git a/tests/unit/MDMRepetitionTests.swift b/tests/unit/MDMRepetitionTests.swift new file mode 100644 index 0000000..e90f2bb --- /dev/null +++ b/tests/unit/MDMRepetitionTests.swift @@ -0,0 +1,33 @@ +/* + 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 MotionInterchange + +class MDMRepetitionTests: XCTestCase { + + func testInitializationWithNumberOfRepetitions() { + let repetition = MDMRepetition(numberOfRepetitions: 5.5) + XCTAssertEqualWithAccuracy(repetition.numberOfRepetitions, 5.5, accuracy: 0.001) + XCTAssertFalse(repetition.autoreverses) + } + + func testInitializationWithNumberOfRepetitionsAndAutoreversed() { + let repetition = MDMRepetition(numberOfRepetitions: 5.5, autoreverses: true) + XCTAssertEqualWithAccuracy(repetition.numberOfRepetitions, 5.5, accuracy: 0.001) + XCTAssertTrue(repetition.autoreverses) + } +} diff --git a/tests/unit/MDMSpringTimingCurve.swift b/tests/unit/MDMSpringTimingCurve.swift new file mode 100644 index 0000000..d0115c6 --- /dev/null +++ b/tests/unit/MDMSpringTimingCurve.swift @@ -0,0 +1,37 @@ +/* + 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 MotionInterchange + +class MDMTimingCurveTests: XCTestCase { + + func testInitializerValuesWithNoInitialVelocity() { + let curve = MDMSpringTimingCurve(mass: 0.1, tension: 0.2, friction: 0.3) + XCTAssertEqualWithAccuracy(curve.mass, 0.1, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.tension, 0.2, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.friction, 0.3, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.initialVelocity, 0.0, accuracy: 0.001) + } + + func testInitializerValuesWithInitialVelocity() { + let curve = MDMSpringTimingCurve(mass: 0.1, tension: 0.2, friction: 0.3, initialVelocity: 0.4) + XCTAssertEqualWithAccuracy(curve.mass, 0.1, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.tension, 0.2, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.friction, 0.3, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.initialVelocity, 0.4, accuracy: 0.001) + } +} From 72e75e44a940e815a06ee516237451be0646b688 Mon Sep 17 00:00:00 2001 From: featherless Date: Mon, 4 Dec 2017 22:16:32 -0500 Subject: [PATCH 05/28] Add APIs for initializing an animation trait with a named timing function. (#25) --- src/MDMAnimationTraits.h | 24 ++++++++++++++++++++++++ src/MDMAnimationTraits.m | 18 +++++++++++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/MDMAnimationTraits.h b/src/MDMAnimationTraits.h index 3eefaac..f33ce9f 100644 --- a/src/MDMAnimationTraits.h +++ b/src/MDMAnimationTraits.h @@ -33,6 +33,15 @@ */ - (nonnull instancetype)initWithDuration:(NSTimeInterval)duration; +/** + Initializes the instance with the provided duration and named bezier timing curve. + + @param duration The animation will occur over this length of time, in seconds. + @param timingFunctionName A kCAMediaTimingFunction name representing a cubic bezier timing curve. + */ +- (nonnull instancetype)initWithDuration:(NSTimeInterval)duration + timingFunctionName:(nonnull NSString *)timingFunctionName; + /** Initializes the instance with the provided duration, delay, and kCAMediaTimingFunctionEaseInEaseOut timing curve. @@ -43,6 +52,21 @@ */ - (nonnull instancetype)initWithDelay:(NSTimeInterval)delay duration:(NSTimeInterval)duration; +/** + Initializes the instance with the provided duration, delay, and named bezier timing curve. + + This is a convenience API for defining a timing curve using the Core Animation timing function + names. See the documentation for CAMediaTimingFunction for more details. + + @param delay The amount of time, in seconds, to wait before starting the animation. + @param duration The animation will occur over this length of time, in seconds, after the delay time + has passed. + @param timingFunctionName A kCAMediaTimingFunction name representing a cubic bezier timing curve. + */ +- (nonnull instancetype)initWithDelay:(NSTimeInterval)delay + duration:(NSTimeInterval)duration + timingFunctionName:(nonnull NSString *)timingFunctionName; + /** Initializes the instance with the provided duration, delay, and timing curve. diff --git a/src/MDMAnimationTraits.m b/src/MDMAnimationTraits.m index 99bf382..167a219 100644 --- a/src/MDMAnimationTraits.m +++ b/src/MDMAnimationTraits.m @@ -30,10 +30,22 @@ - (nonnull instancetype)initWithDuration:(NSTimeInterval)duration { return [self initWithDelay:0 duration:duration]; } +- (instancetype)initWithDuration:(NSTimeInterval)duration + timingFunctionName:(NSString *)timingFunctionName { + return [self initWithDelay:0 duration:duration timingFunctionName:timingFunctionName]; +} + - (instancetype)initWithDelay:(NSTimeInterval)delay duration:(NSTimeInterval)duration { - CAMediaTimingFunction *easeInOut = - [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; - return [self initWithDelay:delay duration:duration timingCurve:easeInOut]; + return [self initWithDelay:delay + duration:duration + timingFunctionName:kCAMediaTimingFunctionEaseInEaseOut]; +} + +- (instancetype)initWithDelay:(NSTimeInterval)delay + duration:(NSTimeInterval)duration + timingFunctionName:(NSString *)timingFunctionName { + CAMediaTimingFunction *timingCurve = [CAMediaTimingFunction functionWithName:timingFunctionName]; + return [self initWithDelay:delay duration:duration timingCurve:timingCurve]; } - (instancetype)initWithDelay:(NSTimeInterval)delay From 4e0a2e7ad5f258bc450a9dde3281fa7c3752bae6 Mon Sep 17 00:00:00 2001 From: featherless Date: Tue, 5 Dec 2017 10:08:13 -0500 Subject: [PATCH 06/28] Initial pass at fleshing out the readme (#24) --- README.md | 61 ++++++++++++++++++++++++++++-- img/motion-interchange-banner.gif | Bin 0 -> 17907 bytes 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 img/motion-interchange-banner.gif diff --git a/README.md b/README.md index ece9daa..71c06a6 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,67 @@ -# Motion Interchange +![Motion Interchange Banner](img/motion-interchange-banner.gif) -> A standard format for representing motion specifications in Objective-C and Swift. +> A standard format for animation traits in Objective-C and Swift. [![Build Status](https://travis-ci.org/material-motion/motion-interchange-objc.svg?branch=develop)](https://travis-ci.org/material-motion/motion-interchange-objc) [![codecov](https://codecov.io/gh/material-motion/motion-interchange-objc/branch/develop/graph/badge.svg)](https://codecov.io/gh/material-motion/motion-interchange-objc) [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/MotionInterchange.svg)](https://cocoapods.org/pods/MotionInterchange) [![Platform](https://img.shields.io/cocoapods/p/MotionInterchange.svg)](http://cocoadocs.org/docsets/MotionInterchange) -[![Docs](https://img.shields.io/cocoapods/metrics/doc-percent/MotionInterchange.svg)](http://cocoadocs.org/docsets/MotionInterchange) + +"Magic numbers" — those lonely, abandoned values without a home — are often one of the first things +targeted in code review for cleanup. And yet, numbers related to animations may go unnoticed and +left behind, scattered throughout a code base with little to no organizational diligence. These +forgotten metrics form the backbone of mobile interactions and are often the ones needing the most +care - so why are we ok leaving them scattered throughout a code base? + +```objc +// Let's play "find the magic number": how many magic numbers are hidden in this code? +[UIView animateWithDuration:0.230 + delay:0 + options:UIViewAnimationOptionCurveEaseOut + animations:^{ + myButton.position = updatedPosition; + } + completion:nil]; +// Hint: the answer is not "one, the number 0.230". +``` + +The challenge with extracting animation magic numbers is that we often don't have a clear +definition of *what an animation is composed of*. An animation is not simply determined by its +duration, in the same way that a color is not simply determined by how red it is. + +The traits of an animation — like the red, green, and blue components of a color — include the +following: + +- Delay. +- Duration. +- Timing curve. +- Repetition. + +Within this library you will find simple data types for storing and representing animation +traits so that the lost magic numbers for animation your code can find a place to call home. + +Welcome home, lost numbers. + +## Sibling library: Motion Animator + +While it is possible to use the Motion Interchange as a standalone library, the Motion Animator +is designed to be the primary consumer of Motion Interchange data types. Consider using these +libraries together, with MotionAnimator as your primary dependency. + +```objc +MDMAnimationTraits *animationTraits = + [[MDMAnimationTraits alloc] initWithDuration:0.230 + timingFunctionName:kCAMediaTimingFunctionEaseInEaseOut]; + +MDMMotionAnimator *animator = [[MDMMotionAnimator alloc] init]; +[animator animateWithTraits:animationTraits animations:^{ + view.alpha = 0; +}]; +``` + +To learn more, visit the MotionAnimator GitHub page: + +https://github.com/material-motion/motion-animator-objc ## Installation diff --git a/img/motion-interchange-banner.gif b/img/motion-interchange-banner.gif new file mode 100644 index 0000000000000000000000000000000000000000..6ce4003e242a3b672069a28577a6b6e8ecb3fa99 GIT binary patch literal 17907 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 yJj} Date: Tue, 5 Dec 2017 10:10:02 -0500 Subject: [PATCH 07/28] Fix minor typo. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71c06a6..1effdec 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ following: - Repetition. Within this library you will find simple data types for storing and representing animation -traits so that the lost magic numbers for animation your code can find a place to call home. +traits so that the magic numbers that define your animations can find a place to call home. Welcome home, lost numbers. From 3f9405eac1445d0f12cb4fad4ad0a89631ded920 Mon Sep 17 00:00:00 2001 From: featherless Date: Tue, 5 Dec 2017 21:09:41 -0500 Subject: [PATCH 08/28] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1effdec..2b6fe4e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![Motion Interchange Banner](img/motion-interchange-banner.gif) -> A standard format for animation traits in Objective-C and Swift. +> A standard format for representing animation traits in Objective-C and Swift. [![Build Status](https://travis-ci.org/material-motion/motion-interchange-objc.svg?branch=develop)](https://travis-ci.org/material-motion/motion-interchange-objc) [![codecov](https://codecov.io/gh/material-motion/motion-interchange-objc/branch/develop/graph/badge.svg)](https://codecov.io/gh/material-motion/motion-interchange-objc) From f56f8a7a75e93a087aadc0f0d5759be13fd43cb7 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Wed, 6 Dec 2017 12:54:56 -0500 Subject: [PATCH 09/28] Run tests on Travis. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 938266c..ccd7b9e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,6 @@ before_install: - pod install --repo-update script: - set -o pipefail - - xcodebuild build -workspace MotionInterchange.xcworkspace -scheme MotionInterchangeCatalog -sdk "iphonesimulator10.3" -destination "name=iPhone 6s,OS=10.1" ONLY_ACTIVE_ARCH=YES | xcpretty -c; + - xcodebuild test -workspace MotionInterchange.xcworkspace -scheme MotionInterchangeCatalog -sdk "iphonesimulator10.3" -destination "name=iPhone 6s,OS=10.1" ONLY_ACTIVE_ARCH=YES | xcpretty -c; after_success: - bash <(curl -s https://codecov.io/bash) From 111d91ea2da6ee2a8acd73c89664f4b10b6232c1 Mon Sep 17 00:00:00 2001 From: featherless Date: Wed, 6 Dec 2017 22:17:23 -0500 Subject: [PATCH 10/28] Enable code coverage on travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ccd7b9e..7dca0e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,6 @@ before_install: - pod install --repo-update script: - set -o pipefail - - xcodebuild test -workspace MotionInterchange.xcworkspace -scheme MotionInterchangeCatalog -sdk "iphonesimulator10.3" -destination "name=iPhone 6s,OS=10.1" ONLY_ACTIVE_ARCH=YES | xcpretty -c; + - xcodebuild test -workspace MotionInterchange.xcworkspace -scheme MotionInterchangeCatalog -sdk "iphonesimulator10.3" -destination "name=iPhone 6s,OS=10.1" -enableCodeCoverage YES ONLY_ACTIVE_ARCH=YES | xcpretty -c; after_success: - bash <(curl -s https://codecov.io/bash) From d2e1450ac327ebfba49c7489375bd635e9cfc1bb Mon Sep 17 00:00:00 2001 From: featherless Date: Thu, 7 Dec 2017 19:04:30 -0500 Subject: [PATCH 11/28] Add todo configuration See https://github.com/JasonEtco/todo#configuring-for-your-project for docs. --- .github/config.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/config.yml diff --git a/.github/config.yml b/.github/config.yml new file mode 100644 index 0000000..331f9a0 --- /dev/null +++ b/.github/config.yml @@ -0,0 +1,3 @@ +todo: + keyword: "TODO" + caseSensitive: true From 41588742c057d79a3a3a7338f867a9622d84e143 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Thu, 7 Dec 2017 20:24:42 -0500 Subject: [PATCH 12/28] Revert "Add todo configuration" This reverts commit d2e1450ac327ebfba49c7489375bd635e9cfc1bb. --- .github/config.yml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .github/config.yml diff --git a/.github/config.yml b/.github/config.yml deleted file mode 100644 index 331f9a0..0000000 --- a/.github/config.yml +++ /dev/null @@ -1,3 +0,0 @@ -todo: - keyword: "TODO" - caseSensitive: true From dcca85ea152d83cbdcf88d74916a228f40c8c2e1 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Wed, 6 Dec 2017 12:50:23 -0500 Subject: [PATCH 13/28] Iterating on the usage docs. --- README.md | 121 +++++++++++++++++++++--------------------------------- 1 file changed, 47 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 2b6fe4e..30dee45 100644 --- a/README.md +++ b/README.md @@ -102,102 +102,75 @@ commands: ## Guides -1. [Architecture](#architecture) -2. [How to define a cubic bezier animation](#how-to-define-a-cubic-bezier-animation) -3. [How to define a spring animation](#how-to-define-a-spring-animation) -4. [How to define a motion spec](#how-to-define-a-motion-spec) +1. [Animation traits](#animation-traits) +2. [Timing curves](#timing-curves) +3. [How to define a motion spec](#how-to-define-a-motion-spec) -### Architecture +### Animation traits -This library defines a format for representing motion in Objective-C and Swift applications. The -primary data type, `MotionTiming`, allows you to describe the duration, delay, timing curve, and -repetition for an animation. +The primary data type you'll make use of is `MDMAnimationTraits`. This class can store all of +the necessary traits that make up an animation, including: -### How to define a cubic bezier animation +- Delay. +- Duration. +- Timing curve. +- Repetition. -In Objective-C: +In Objective-C, you initialize a simple ease in/out cubic bezier instance like so: ```objc -MDMMotionTiming timing = (MDMMotionTiming){ - .delay = 0.150, - .duration = 0.225, - .curve = _MDMBezier(0.4f, 0.0f, 0.2f, 1.0f) -} +MDMAnimationTraits *traits = [[MDMAnimationTraits alloc] initWithDuration:0.5]; ``` -### How to define a spring animation - -In Objective-C: +And in Swift: -```objc -MDMMotionTiming timing = (MDMMotionTiming){ - .curve = _MDMSpring(1, 100, 10) -} +```swift +let traits = MDMAnimationTraits(duration: 0.5) ``` -### How to define a motion spec +There are many more ways to initialize animation traits. Read the +[header documentation](src/MDMAnimationTraits.h) to see all of the available initializers. -Motion timing structs can be used to represent complex multi-element and multi-property motion -specifications. An example of a common complex motion spec is a transition which has both an -expansion and a collapse variant. If we wanted to represent such a transition we might create a -set of structures like this: +### Timing curves + +A timing curve describes how quickly an animation progresses over time. Two types of timing +curves are supported by Core Animation, and therefore by the MotionInterchange: + +- Cubic bezier +- Spring + +**Cubic beziers** are represented by the CAMediaTimingFunction object. To define an +animation trait with a cubic bezier curve in Objective-C: ```objc -struct MDCMaskedTransitionMotionTiming { - MDMMotionTiming contentFade; - MDMMotionTiming floodBackgroundColor; - MDMMotionTiming maskTransformation; - MDMMotionTiming horizontalMovement; - MDMMotionTiming verticalMovement; - MDMMotionTiming scrimFade; -}; -typedef struct MDCMaskedTransitionMotionTiming MDCMaskedTransitionMotionTiming; - -struct MDCMaskedTransitionMotionSpec { - MDCMaskedTransitionMotionTiming expansion; - MDCMaskedTransitionMotionTiming collapse; - BOOL shouldSlideWhenCollapsed; - BOOL isCentered; -}; -typedef struct MDCMaskedTransitionMotionSpec MDCMaskedTransitionMotionSpec; +CAMediaTimingFunction *timingCurve = + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; +MDMAnimationTraits *traits = + [[MDMAnimationTraits alloc] initWithDelay:0 duration:0.5 timingCurve:timingCurve]; ``` -We can then implement a spec like so: +And in Swift: -```objc -#define MDMEightyForty _MDMBezier(0.4f, 0.0f, 0.2f, 1.0f) -#define MDMFortyOut _MDMBezier(0.4f, 0.0f, 1.0f, 1.0f) - -struct MDCMaskedTransitionMotionSpec fullscreen = { - .expansion = { - .contentFade = { - .delay = 0.150, .duration = 0.225, .curve = MDMEightyForty, - }, - .floodBackgroundColor = { - .delay = 0.000, .duration = 0.075, .curve = MDMEightyForty, - }, - .maskTransformation = { - .delay = 0.000, .duration = 0.105, .curve = MDMFortyOut, - }, - .horizontalMovement = {.curve = { .type = MDMMotionCurveTypeInstant }}, - .verticalMovement = { - .delay = 0.045, .duration = 0.330, .curve = MDMEightyForty, - }, - .scrimFade = { - .delay = 0.000, .duration = 0.150, .curve = MDMEightyForty, - } - }, - .shouldSlideWhenCollapsed = true, - .isCentered = false -}; +```swift +let timingCurve = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) +let traits = MDMAnimationTraits(delay: 0, duration: 0.5, timingCurve: timingCurve) ``` -We can then use this motion spec to implement our animations in a transition like so: +**Springs** are represented with the custom `MDMSpringTimingCurve` type. To define an +animation trait with a spring curve in Objective-C: ```objc -MDCMaskedTransitionMotionTiming timing = isExpanding ? fullscreen.expansion : fullscreen.collapse; +MDMSpringTimingCurve *timingCurve = + [[MDMSpringTimingCurve alloc] initWithMass:1 tension:100 friction:10]; +MDMAnimationTraits *traits = + [[MDMAnimationTraits alloc] initWithDelay:0 duration:0.5 timingCurve:timingCurve]; +``` + +And in Swift: -// Can now use timing's properties to associate animations with views. +```swift +let timingCurve = MDMSpringTimingCurve(mass: 1, tension: 100, friction: 10) +let traits = MDMAnimationTraits(delay: 0, duration: 0.5, timingCurve: timingCurve) ``` ## Contributing From e8af0592da1aa244867089532381f6dbdd801a77 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Thu, 7 Dec 2017 20:40:00 -0500 Subject: [PATCH 14/28] Remove unused header. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 30dee45..1392d74 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,6 @@ commands: 1. [Animation traits](#animation-traits) 2. [Timing curves](#timing-curves) -3. [How to define a motion spec](#how-to-define-a-motion-spec) ### Animation traits From d94246c2b5286d20f92dfc7569b39798394d9422 Mon Sep 17 00:00:00 2001 From: featherless Date: Fri, 8 Dec 2017 12:17:46 -0500 Subject: [PATCH 15/28] Use UIViewAnimationCurve instead of NSString as the easing curve type. (#28) This gives better compile-time enforcement and auto-completion than the Core Animation strings. --- BUILD | 1 + README.md | 13 ++++++++++++ src/MDMAnimationTraits.h | 9 ++++---- src/MDMAnimationTraits.m | 26 +++++++++++++++++------- tests/unit/MDMAnimationTraitsTests.swift | 16 +++++++++++++++ 5 files changed, 54 insertions(+), 11 deletions(-) diff --git a/BUILD b/BUILD index 1b38655..bef9c79 100644 --- a/BUILD +++ b/BUILD @@ -37,6 +37,7 @@ strict_warnings_objc_library( "CoreGraphics", "Foundation", "QuartzCore", + "UIKit", ], enable_modules = 1, includes = ["src"], diff --git a/README.md b/README.md index 1392d74..814f80f 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,19 @@ let timingCurve = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOu let traits = MDMAnimationTraits(delay: 0, duration: 0.5, timingCurve: timingCurve) ``` +You can also use the UIViewAnimationCurve type to initialize a timing curve in Objective-C: + +```objc +MDMAnimationTraits *traits = + [[MDMAnimationTraits alloc] initWithDuration:0.5 animationCurve:UIViewAnimationCurveEaseIn]; +``` + +And in Swift: + +```swift +let traits = MDMAnimationTraits(duration: 0.5, animationCurve: .easeIn) +``` + **Springs** are represented with the custom `MDMSpringTimingCurve` type. To define an animation trait with a spring curve in Objective-C: diff --git a/src/MDMAnimationTraits.h b/src/MDMAnimationTraits.h index f33ce9f..d19ee51 100644 --- a/src/MDMAnimationTraits.h +++ b/src/MDMAnimationTraits.h @@ -16,6 +16,7 @@ #import #import +#import #import "MDMRepetitionTraits.h" #import "MDMTimingCurve.h" @@ -37,10 +38,10 @@ Initializes the instance with the provided duration and named bezier timing curve. @param duration The animation will occur over this length of time, in seconds. - @param timingFunctionName A kCAMediaTimingFunction name representing a cubic bezier timing curve. + @param animationCurve A UIKit bezier animation curve type. */ - (nonnull instancetype)initWithDuration:(NSTimeInterval)duration - timingFunctionName:(nonnull NSString *)timingFunctionName; + animationCurve:(UIViewAnimationCurve)animationCurve; /** Initializes the instance with the provided duration, delay, and @@ -61,11 +62,11 @@ @param delay The amount of time, in seconds, to wait before starting the animation. @param duration The animation will occur over this length of time, in seconds, after the delay time has passed. - @param timingFunctionName A kCAMediaTimingFunction name representing a cubic bezier timing curve. + @param animationCurve A UIKit bezier animation curve type. */ - (nonnull instancetype)initWithDelay:(NSTimeInterval)delay duration:(NSTimeInterval)duration - timingFunctionName:(nonnull NSString *)timingFunctionName; + animationCurve:(UIViewAnimationCurve)animationCurve; /** Initializes the instance with the provided duration, delay, and timing curve. diff --git a/src/MDMAnimationTraits.m b/src/MDMAnimationTraits.m index 167a219..645c06f 100644 --- a/src/MDMAnimationTraits.m +++ b/src/MDMAnimationTraits.m @@ -31,20 +31,32 @@ - (nonnull instancetype)initWithDuration:(NSTimeInterval)duration { } - (instancetype)initWithDuration:(NSTimeInterval)duration - timingFunctionName:(NSString *)timingFunctionName { - return [self initWithDelay:0 duration:duration timingFunctionName:timingFunctionName]; + animationCurve:(UIViewAnimationCurve)animationCurve { + return [self initWithDelay:0 duration:duration animationCurve:animationCurve]; } - (instancetype)initWithDelay:(NSTimeInterval)delay duration:(NSTimeInterval)duration { - return [self initWithDelay:delay - duration:duration - timingFunctionName:kCAMediaTimingFunctionEaseInEaseOut]; + return [self initWithDelay:delay duration:duration animationCurve:UIViewAnimationCurveEaseInOut]; } - (instancetype)initWithDelay:(NSTimeInterval)delay duration:(NSTimeInterval)duration - timingFunctionName:(NSString *)timingFunctionName { - CAMediaTimingFunction *timingCurve = [CAMediaTimingFunction functionWithName:timingFunctionName]; + animationCurve:(UIViewAnimationCurve)animationCurve { + CAMediaTimingFunction *timingCurve; + switch (animationCurve) { + case UIViewAnimationCurveEaseInOut: + timingCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + break; + case UIViewAnimationCurveEaseIn: + timingCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; + break; + case UIViewAnimationCurveEaseOut: + timingCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; + break; + case UIViewAnimationCurveLinear: + timingCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; + break; + } return [self initWithDelay:delay duration:duration timingCurve:timingCurve]; } diff --git a/tests/unit/MDMAnimationTraitsTests.swift b/tests/unit/MDMAnimationTraitsTests.swift index 8755967..9b00965 100644 --- a/tests/unit/MDMAnimationTraitsTests.swift +++ b/tests/unit/MDMAnimationTraitsTests.swift @@ -35,6 +35,22 @@ class MDMAnimationTraitsTests: XCTestCase { XCTAssertNil(traits.repetition) } + func testInitializerValuesWithDurationAndEaseInCurve() { + let traits = MDMAnimationTraits(duration: 0.5, animationCurve: .easeIn) + + XCTAssertEqualWithAccuracy(traits.duration, 0.5, accuracy: 0.001) + XCTAssertEqualWithAccuracy(traits.delay, 0, accuracy: 0.001) + XCTAssertTrue(traits.timingCurve is CAMediaTimingFunction) + if let timingCurve = traits.timingCurve as? CAMediaTimingFunction { + let easeInOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) + XCTAssertEqualWithAccuracy(timingCurve.mdm_point1.x, easeInOut.mdm_point1.x, accuracy: 0.001) + XCTAssertEqualWithAccuracy(timingCurve.mdm_point1.y, easeInOut.mdm_point1.y, accuracy: 0.001) + XCTAssertEqualWithAccuracy(timingCurve.mdm_point2.x, easeInOut.mdm_point2.x, accuracy: 0.001) + XCTAssertEqualWithAccuracy(timingCurve.mdm_point2.y, easeInOut.mdm_point2.y, accuracy: 0.001) + } + XCTAssertNil(traits.repetition) + } + func testInitializerValuesWithDurationDelay() { let traits = MDMAnimationTraits(delay: 0.2, duration: 0.5) From 0730f1a905421f9a5d75e6015e1bf4d242a76988 Mon Sep 17 00:00:00 2001 From: featherless Date: Fri, 8 Dec 2017 12:18:02 -0500 Subject: [PATCH 16/28] Use http_archive instead of git_respository as per the bazel team's recommendations. (#29) Closes https://github.com/material-motion/motion-interchange-objc/issues/14 --- WORKSPACE | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 722a7a4..5cadb37 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -12,14 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -git_repository( +http_archive( name = "build_bazel_rules_apple", - remote = "https://github.com/bazelbuild/rules_apple.git", - commit = "7ea0557", + url = "https://github.com/bazelbuild/rules_apple/archive/0.2.0.zip", + strip_prefix = "rules_apple-0.2.0", + # Generated by running: openssl sha -sha256 + sha256 = "4ae91e15243fbe39a02b7e9a1bba6de50c5554443a30c5587cfeff7404dfdd56" ) -git_repository( +http_archive( name = "bazel_ios_warnings", - remote = "https://github.com/material-foundation/bazel_ios_warnings.git", - tag = "v1.0.1", + url = "https://github.com/material-foundation/bazel_ios_warnings/archive/v2.0.0.zip", + strip_prefix = "bazel_ios_warnings-2.0.0", + # Generated by running: openssl sha -sha256 + sha256 = "199f1b1726ca561d5dedcb3876094a9604f603dc8d737b0e6b8866a70dc8a90d" ) From 9d7352cec750b8c00fe300708647ebb01189b6c2 Mon Sep 17 00:00:00 2001 From: featherless Date: Fri, 8 Dec 2017 12:18:21 -0500 Subject: [PATCH 17/28] Add support for damping ratio initializers on the spring timing curve. (#27) These initializers match the UIKit equivalent APIs. --- README.md | 16 ++++++ src/MDMSpringTimingCurve.h | 31 +++++++++- src/MDMSpringTimingCurve.m | 82 ++++++++++++++++++++++++++- tests/unit/MDMSpringTimingCurve.swift | 32 +++++++++++ 4 files changed, 158 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 814f80f..3173473 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,22 @@ let timingCurve = MDMSpringTimingCurve(mass: 1, tension: 100, friction: 10) let traits = MDMAnimationTraits(delay: 0, duration: 0.5, timingCurve: timingCurve) ``` +Springs can also be initialized using UIKit's [damping ratio concept](https://developer.apple.com/documentation/uikit/uiview/1622594-animatewithduration): + +```objc +MDMSpringTimingCurve *timingCurve = + [[MDMSpringTimingCurve alloc] initWithDuration:0.5 dampingRatio:0.5]; +MDMAnimationTraits *traits = + [[MDMAnimationTraits alloc] initWithDelay:0 duration:0.5 timingCurve:timingCurve]; +``` + +And in Swift: + +```swift +let timingCurve = MDMSpringTimingCurve(duration: 0.5, dampingRatio: 0.5) +let traits = MDMAnimationTraits(delay: 0, duration: 0.5, timingCurve: timingCurve) +``` + ## Contributing We welcome contributions! diff --git a/src/MDMSpringTimingCurve.h b/src/MDMSpringTimingCurve.h index 30f7ca9..9edefac 100644 --- a/src/MDMSpringTimingCurve.h +++ b/src/MDMSpringTimingCurve.h @@ -55,6 +55,35 @@ initialVelocity:(CGFloat)initialVelocity NS_DESIGNATED_INITIALIZER; +/** + Initializes the timing curve with the given UIKit spring damping ratio. + + @param duration The desired duration of the spring animation. + @param dampingRatio From the UIKit documentation: "When `dampingRatio` is 1, the animation will + smoothly decelerate to its final model values without oscillating. Damping ratios less than 1 will + oscillate more and more before coming to a complete stop." + */ +- (nonnull instancetype)initWithDuration:(NSTimeInterval)duration + dampingRatio:(CGFloat)dampingRatio; + +/** + Initializes the timing curve with the given UIKit spring damping ratio and initial velocity. + + @param duration The desired duration of the spring animation. + @param dampingRatio From the UIKit documentation: "When `dampingRatio` is 1, the animation will + smoothly decelerate to its final model values without oscillating. Damping ratios less than 1 will + oscillate more and more before coming to a complete stop." + @param initialVelocity From the UIKit documentation: "You can use the initial spring velocity to + specify how fast the object at the end of the simulated spring was moving before it was attached. + It's a unit coordinate system, where 1 is defined as travelling the total animation distance in a + second. So if you're changing an object's position by 200pt in this animation, and you want the + animation to behave as if the object was moving at 100pt/s before the animation started, you'd + pass 0.5. You'll typically want to pass 0 for the velocity." + */ +- (nonnull instancetype)initWithDuration:(NSTimeInterval)duration + dampingRatio:(CGFloat)dampingRatio + initialVelocity:(CGFloat)initialVelocity; + #pragma mark - Traits /** @@ -91,5 +120,3 @@ - (nonnull instancetype)init NS_UNAVAILABLE; @end - - diff --git a/src/MDMSpringTimingCurve.m b/src/MDMSpringTimingCurve.m index 138a556..2ad626b 100644 --- a/src/MDMSpringTimingCurve.m +++ b/src/MDMSpringTimingCurve.m @@ -16,13 +16,40 @@ #import "MDMSpringTimingCurve.h" -@implementation MDMSpringTimingCurve +#import + +@implementation MDMSpringTimingCurve { + CGFloat _duration; + CGFloat _dampingRatio; + + BOOL _coefficientsAreInvalid; +} + +@synthesize mass = _mass; +@synthesize friction = _friction; +@synthesize tension = _tension; - (instancetype)init { [self doesNotRecognizeSelector:_cmd]; return nil; } +- (instancetype)initWithDuration:(NSTimeInterval)duration dampingRatio:(CGFloat)dampingRatio { + return [self initWithDuration:duration dampingRatio:dampingRatio initialVelocity:0]; +} + +- (nonnull instancetype)initWithDuration:(NSTimeInterval)duration + dampingRatio:(CGFloat)dampingRatio + initialVelocity:(CGFloat)initialVelocity { + self = [self initWithMass:0 tension:0 friction:0 initialVelocity:initialVelocity]; + if (self) { + _duration = duration; + _dampingRatio = dampingRatio; + _coefficientsAreInvalid = YES; + } + return self; +} + - (instancetype)initWithMass:(CGFloat)mass tension:(CGFloat)tension friction:(CGFloat)friction { return [self initWithMass:mass tension:tension friction:friction initialVelocity:0]; } @@ -41,5 +68,58 @@ - (instancetype)initWithMass:(CGFloat)mass return self; } +- (CGFloat)mass { + [self recalculateCoefficientsIfNeeded]; + return _mass; +} + +- (CGFloat)tension { + [self recalculateCoefficientsIfNeeded]; + return _tension; +} + +- (CGFloat)friction { + [self recalculateCoefficientsIfNeeded]; + return _friction; +} + +#pragma mark - Private + +- (void)recalculateCoefficientsIfNeeded { + if (_coefficientsAreInvalid) { + [self recalculateCoefficients]; + } +} + +- (void)recalculateCoefficients { + UIView *view = [[UIView alloc] init]; + [UIView animateWithDuration:_duration + delay:0 + usingSpringWithDamping:_dampingRatio + initialSpringVelocity:self.initialVelocity + options:0 + animations:^{ + view.center = CGPointMake(100, 100); + } completion:nil]; + + NSString *animationKey = [view.layer.animationKeys firstObject]; + NSAssert(animationKey != nil, @"Unable to extract animation timing curve: no animation found."); +#pragma clang diagnostic push + // CASpringAnimation is a private API on iOS 8 - we're able to make use of it because we're + // linking against the public API on iOS 9+. +#pragma clang diagnostic ignored "-Wpartial-availability" + CASpringAnimation *springAnimation = + (CASpringAnimation *)[view.layer animationForKey:animationKey]; + NSAssert([springAnimation isKindOfClass:[CASpringAnimation class]], + @"Unable to extract animation timing curve: unexpected animation type."); +#pragma clang diagnostic pop + + _mass = springAnimation.mass; + _tension = springAnimation.stiffness; + _friction = springAnimation.damping; + + _coefficientsAreInvalid = NO; +} + @end diff --git a/tests/unit/MDMSpringTimingCurve.swift b/tests/unit/MDMSpringTimingCurve.swift index d0115c6..7d3fec2 100644 --- a/tests/unit/MDMSpringTimingCurve.swift +++ b/tests/unit/MDMSpringTimingCurve.swift @@ -34,4 +34,36 @@ class MDMTimingCurveTests: XCTestCase { XCTAssertEqualWithAccuracy(curve.friction, 0.3, accuracy: 0.001) XCTAssertEqualWithAccuracy(curve.initialVelocity, 0.4, accuracy: 0.001) } + + @available(iOS 9.0, *) + func testInitializerValuesWithDampingCoefficient() { + for duration in stride(from: TimeInterval(0.1), to: TimeInterval(3), by: TimeInterval(0.5)) { + for dampingRatio in stride(from: CGFloat(0.1), to: CGFloat(2), by: CGFloat(0.4)) { + for initialVelocity in stride(from: CGFloat(-2), to: CGFloat(2), by: CGFloat(0.8)) { + let curve = MDMSpringTimingCurve(duration: duration, + dampingRatio: dampingRatio, + initialVelocity: initialVelocity) + let view = UIView() + + UIView.animate(withDuration: duration, + delay: 0, + usingSpringWithDamping: dampingRatio, + initialSpringVelocity: initialVelocity, + options: [], + animations: { + view.center = CGPoint(x: initialVelocity * 5, y: dampingRatio * 10) + }, completion: nil) + + if let animationKey = view.layer.animationKeys()?.first, + let animation = view.layer.animation(forKey: animationKey) as? CASpringAnimation { + + XCTAssertEqualWithAccuracy(curve.mass, animation.mass, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.tension, animation.stiffness, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.friction, animation.damping, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.initialVelocity, animation.initialVelocity, accuracy: 0.001) + } + } + } + } + } } From 4f74d24b6d66503fed71af36482665814e10a3a4 Mon Sep 17 00:00:00 2001 From: featherless Date: Tue, 12 Dec 2017 10:43:17 -0500 Subject: [PATCH 18/28] Allow writing of all properties. (#26) --- src/MDMAnimationTraits.h | 13 ++++-- src/MDMRepetition.h | 2 +- src/MDMRepetitionOverTime.h | 2 +- src/MDMRepetitionTraits.h | 2 +- src/MDMSpringTimingCurve.h | 16 ++++--- src/MDMSpringTimingCurve.m | 6 +++ tests/unit/MDMSpringTimingCurve.swift | 65 +++++++++++++++++++++++++++ 7 files changed, 92 insertions(+), 14 deletions(-) diff --git a/src/MDMAnimationTraits.h b/src/MDMAnimationTraits.h index d19ee51..9e75f9d 100644 --- a/src/MDMAnimationTraits.h +++ b/src/MDMAnimationTraits.h @@ -103,22 +103,27 @@ /** The amount of time, in seconds, before this animation's value interpolation should begin. */ -@property(nonatomic, assign, readonly) NSTimeInterval delay; +@property(nonatomic, assign) NSTimeInterval delay; /** The amount of time, in seconds, over which this animation should interpolate between its values. */ -@property(nonatomic, assign, readonly) NSTimeInterval duration; +@property(nonatomic, assign) NSTimeInterval duration; /** The velocity and acceleration of the animation over time. + + If the timing curve is nil then the timing is assumed to be "instant", regardless of duration and + delay. */ -@property(nonatomic, strong, nullable, readonly) id timingCurve; +@property(nonatomic, strong, nullable) id timingCurve; /** The repetition characteristics of the animation. + + If the repetition is nil then no repetition should occur. */ -@property(nonatomic, strong, nullable, readonly) id repetition; +@property(nonatomic, strong, nullable) id repetition; #pragma mark - Unavailable diff --git a/src/MDMRepetition.h b/src/MDMRepetition.h index 523082f..b8589e3 100644 --- a/src/MDMRepetition.h +++ b/src/MDMRepetition.h @@ -49,7 +49,7 @@ /** The number of repetitions that will occur before this animation stops repeating. */ -@property(nonatomic, assign, readonly) double numberOfRepetitions; +@property(nonatomic, assign) double numberOfRepetitions; /** Unavailable. diff --git a/src/MDMRepetitionOverTime.h b/src/MDMRepetitionOverTime.h index 507b422..e5b7d88 100644 --- a/src/MDMRepetitionOverTime.h +++ b/src/MDMRepetitionOverTime.h @@ -44,7 +44,7 @@ /** The amount of time, in seconds, that will pass before this animation stops repeating. */ -@property(nonatomic, assign, readonly) double duration; +@property(nonatomic, assign) double duration; /** Unavailable. diff --git a/src/MDMRepetitionTraits.h b/src/MDMRepetitionTraits.h index 211f227..71fc030 100644 --- a/src/MDMRepetitionTraits.h +++ b/src/MDMRepetitionTraits.h @@ -24,7 +24,7 @@ /** Whether the animation should animate backwards after animating forwards. */ -@property(nonatomic, assign, readonly) BOOL autoreverses; +@property(nonatomic, assign) BOOL autoreverses; @end diff --git a/src/MDMSpringTimingCurve.h b/src/MDMSpringTimingCurve.h index 9edefac..27e6e60 100644 --- a/src/MDMSpringTimingCurve.h +++ b/src/MDMSpringTimingCurve.h @@ -91,32 +91,34 @@ Affects the animation's momentum. This is usually 1. */ -@property(nonatomic, assign, readonly) CGFloat mass; +@property(nonatomic, assign) CGFloat mass; /** The tension of the spring simulation. Affects how quickly the animation moves toward its destination. */ -@property(nonatomic, assign, readonly) CGFloat tension; +@property(nonatomic, assign) CGFloat tension; /** The friction of the spring simulation. Affects how quickly the animation starts and stops. */ -@property(nonatomic, assign, readonly) CGFloat friction; +@property(nonatomic, assign) CGFloat friction; /** The initial velocity of the spring simulation. Measured in units of translation per second. - */ -@property(nonatomic, assign, readonly) CGFloat initialVelocity; -/** - Unavailable. + If this timing curve was initialized using a damping ratio then setting a new initial velocity + will also change the the mass/tension/friction values according to the new UIKit damping + coefficient calculation. */ +@property(nonatomic, assign) CGFloat initialVelocity; + +/** Unavailable. */ - (nonnull instancetype)init NS_UNAVAILABLE; @end diff --git a/src/MDMSpringTimingCurve.m b/src/MDMSpringTimingCurve.m index 2ad626b..3f958e1 100644 --- a/src/MDMSpringTimingCurve.m +++ b/src/MDMSpringTimingCurve.m @@ -83,6 +83,12 @@ - (CGFloat)friction { return _friction; } +- (void)setInitialVelocity:(CGFloat)initialVelocity { + _initialVelocity = initialVelocity; + + _coefficientsAreInvalid = YES; +} + #pragma mark - Private - (void)recalculateCoefficientsIfNeeded { diff --git a/tests/unit/MDMSpringTimingCurve.swift b/tests/unit/MDMSpringTimingCurve.swift index 7d3fec2..0430651 100644 --- a/tests/unit/MDMSpringTimingCurve.swift +++ b/tests/unit/MDMSpringTimingCurve.swift @@ -66,4 +66,69 @@ class MDMTimingCurveTests: XCTestCase { } } } + + func testChangingInitialVelocityInvalidatesTheCoefficients() { + let curve = MDMSpringTimingCurve(duration: 0.5, + dampingRatio: 0.8, + initialVelocity: 0) + + let originalMass = curve.mass + let originalTension = curve.tension + let originalFriction = curve.friction + + curve.initialVelocity = 10 + + // UIKit never appears to change the mass. + XCTAssertEqualWithAccuracy(curve.mass, originalMass, accuracy: 0.001) + XCTAssertNotEqualWithAccuracy(curve.tension, originalTension, 0.001) + XCTAssertNotEqualWithAccuracy(curve.friction, originalFriction, 0.001) + } + + func testWithDampingRatioSettingMassKeepsTheNewMass() { + let curve = MDMSpringTimingCurve(duration: 0.5, + dampingRatio: 0.8, + initialVelocity: 0) + + let originalMass = curve.mass + let originalTension = curve.tension + let originalFriction = curve.friction + + curve.mass = originalMass + 1 + + XCTAssertEqualWithAccuracy(curve.mass, originalMass + 1, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.tension, originalTension, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.friction, originalFriction, accuracy: 0.001) + } + + func testWithDampingRatioSettingTensionKeepsTheNewTension() { + let curve = MDMSpringTimingCurve(duration: 0.5, + dampingRatio: 0.8, + initialVelocity: 0) + + let originalMass = curve.mass + let originalTension = curve.tension + let originalFriction = curve.friction + + curve.tension = originalTension + 1 + + XCTAssertEqualWithAccuracy(curve.mass, originalMass, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.tension, originalTension + 1, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.friction, originalFriction, accuracy: 0.001) + } + + func testWithDampingRatioSettingFrictionKeepsTheNewFriction() { + let curve = MDMSpringTimingCurve(duration: 0.5, + dampingRatio: 0.8, + initialVelocity: 0) + + let originalMass = curve.mass + let originalTension = curve.tension + let originalFriction = curve.friction + + curve.friction = originalFriction + 1 + + XCTAssertEqualWithAccuracy(curve.mass, originalMass, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.tension, originalTension, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.friction, originalFriction + 1, accuracy: 0.001) + } } From f7fb09546d034fcdf8e57509aa157a760aebd398 Mon Sep 17 00:00:00 2001 From: featherless Date: Tue, 12 Dec 2017 10:56:12 -0500 Subject: [PATCH 19/28] Add support for copying animation traits. (#30) --- src/MDMAnimationTraits.h | 2 +- src/MDMAnimationTraits.m | 9 +++++++ src/MDMRepetition.h | 2 +- src/MDMRepetition.m | 7 +++++ src/MDMRepetitionOverTime.h | 2 +- src/MDMRepetitionOverTime.m | 6 +++++ src/MDMRepetitionTraits.h | 2 +- src/MDMSpringTimingCurve.h | 2 +- src/MDMSpringTimingCurve.m | 14 ++++++++++ src/MDMTimingCurve.h | 2 +- tests/unit/MDMAnimationTraitsTests.swift | 34 ++++++++++++++++++++++++ 11 files changed, 76 insertions(+), 6 deletions(-) diff --git a/src/MDMAnimationTraits.h b/src/MDMAnimationTraits.h index 9e75f9d..3f15639 100644 --- a/src/MDMAnimationTraits.h +++ b/src/MDMAnimationTraits.h @@ -24,7 +24,7 @@ /** A generic representation of animation traits. */ -@interface MDMAnimationTraits: NSObject +@interface MDMAnimationTraits: NSObject /** Initializes the instance with the provided duration and kCAMediaTimingFunctionEaseInEaseOut timing diff --git a/src/MDMAnimationTraits.m b/src/MDMAnimationTraits.m index 645c06f..3fd975b 100644 --- a/src/MDMAnimationTraits.m +++ b/src/MDMAnimationTraits.m @@ -80,6 +80,15 @@ - (instancetype)initWithDelay:(NSTimeInterval)delay return self; } +#pragma mark - NSCopying + +- (id)copyWithZone:(NSZone *)zone { + return [[[self class] alloc] initWithDelay:self.delay + duration:self.duration + timingCurve:[self.timingCurve copyWithZone:zone] + repetition:[self.repetition copyWithZone:zone]]; +} + @end @implementation MDMAnimationTraits (SystemTraits) diff --git a/src/MDMRepetition.h b/src/MDMRepetition.h index b8589e3..b785317 100644 --- a/src/MDMRepetition.h +++ b/src/MDMRepetition.h @@ -21,7 +21,7 @@ /** Represents repetition that repeats a specific number of times. */ -@interface MDMRepetition: NSObject +@interface MDMRepetition: NSObject /** Initializes the instance with the given number of repetitions. diff --git a/src/MDMRepetition.m b/src/MDMRepetition.m index e3d5a1c..2522af4 100644 --- a/src/MDMRepetition.m +++ b/src/MDMRepetition.m @@ -39,5 +39,12 @@ - (instancetype)initWithNumberOfRepetitions:(double)numberOfRepetitions return self; } +#pragma mark - NSCopying + +- (id)copyWithZone:(__unused NSZone *)zone { + return [[[self class] alloc] initWithNumberOfRepetitions:self.numberOfRepetitions + autoreverses:self.autoreverses]; +} + @end diff --git a/src/MDMRepetitionOverTime.h b/src/MDMRepetitionOverTime.h index e5b7d88..3bfea4c 100644 --- a/src/MDMRepetitionOverTime.h +++ b/src/MDMRepetitionOverTime.h @@ -21,7 +21,7 @@ /** Represents repetition that repeats until a specific duration has passed. */ -@interface MDMRepetitionOverTime: NSObject +@interface MDMRepetitionOverTime: NSObject /** Initializes the instance with the given duration. diff --git a/src/MDMRepetitionOverTime.m b/src/MDMRepetitionOverTime.m index 55cdcab..2ebce7c 100644 --- a/src/MDMRepetitionOverTime.m +++ b/src/MDMRepetitionOverTime.m @@ -38,5 +38,11 @@ - (instancetype)initWithDuration:(double)duration autoreverses:(BOOL)autoreverse return self; } +#pragma mark - NSCopying + +- (id)copyWithZone:(__unused NSZone *)zone { + return [[[self class] alloc] initWithDuration:self.duration autoreverses:self.autoreverses]; +} + @end diff --git a/src/MDMRepetitionTraits.h b/src/MDMRepetitionTraits.h index 71fc030..05fc947 100644 --- a/src/MDMRepetitionTraits.h +++ b/src/MDMRepetitionTraits.h @@ -19,7 +19,7 @@ /** A generalized representation of a repetition traits. */ -@protocol MDMRepetitionTraits +@protocol MDMRepetitionTraits /** Whether the animation should animate backwards after animating forwards. diff --git a/src/MDMSpringTimingCurve.h b/src/MDMSpringTimingCurve.h index 27e6e60..47e0486 100644 --- a/src/MDMSpringTimingCurve.h +++ b/src/MDMSpringTimingCurve.h @@ -22,7 +22,7 @@ /** A timing curve that represents the motion of a single-dimensional attached spring. */ -@interface MDMSpringTimingCurve: NSObject +@interface MDMSpringTimingCurve: NSObject /** Initializes the timing curve with the given parameters and an initial velocity of zero. diff --git a/src/MDMSpringTimingCurve.m b/src/MDMSpringTimingCurve.m index 3f958e1..801d976 100644 --- a/src/MDMSpringTimingCurve.m +++ b/src/MDMSpringTimingCurve.m @@ -89,6 +89,20 @@ - (void)setInitialVelocity:(CGFloat)initialVelocity { _coefficientsAreInvalid = YES; } +#pragma mark - NSCopying + +- (id)copyWithZone:(NSZone *)zone { + MDMSpringTimingCurve *copy = + [[[self class] allocWithZone:zone] initWithMass:self.mass + tension:self.tension + friction:self.friction + initialVelocity:self.initialVelocity]; + copy->_coefficientsAreInvalid = _coefficientsAreInvalid; + copy->_duration = _duration; + copy->_dampingRatio = _dampingRatio; + return copy; +} + #pragma mark - Private - (void)recalculateCoefficientsIfNeeded { diff --git a/src/MDMTimingCurve.h b/src/MDMTimingCurve.h index 3a628ec..7849ff5 100644 --- a/src/MDMTimingCurve.h +++ b/src/MDMTimingCurve.h @@ -20,5 +20,5 @@ /** A generalized representation of a timing curve. */ -@protocol MDMTimingCurve +@protocol MDMTimingCurve @end diff --git a/tests/unit/MDMAnimationTraitsTests.swift b/tests/unit/MDMAnimationTraitsTests.swift index 9b00965..879a24e 100644 --- a/tests/unit/MDMAnimationTraitsTests.swift +++ b/tests/unit/MDMAnimationTraitsTests.swift @@ -126,4 +126,38 @@ class MDMAnimationTraitsTests: XCTestCase { XCTAssertEqual(setRepetition.autoreverses, repetition.autoreverses) } } + + func testModifyingACopyDoesNotModifyTheOriginal() { + let spring = MDMSpringTimingCurve(mass: 0.7, tension: 0.8, friction: 0.9) + let repetition = MDMRepetition(numberOfRepetitions: 5) + let traits = MDMAnimationTraits(delay: 0.2, + duration: 0.5, + timingCurve: spring, + repetition: repetition) + + let copy = traits.copy() as! MDMAnimationTraits + copy.delay = copy.delay + 1 + copy.duration = copy.duration + 1 + if let springCopy = copy.timingCurve as? MDMSpringTimingCurve { + springCopy.friction = springCopy.friction + 1 + springCopy.tension = springCopy.tension + 1 + springCopy.mass = springCopy.mass + 1 + springCopy.initialVelocity = springCopy.initialVelocity + 1 + + XCTAssertNotEqualWithAccuracy(springCopy.friction, spring.friction, 0.001) + XCTAssertNotEqualWithAccuracy(springCopy.tension, spring.tension, 0.001) + XCTAssertNotEqualWithAccuracy(springCopy.mass, spring.mass, 0.001) + XCTAssertNotEqualWithAccuracy(springCopy.initialVelocity, spring.initialVelocity, 0.001) + } + if let repetitionCopy = copy.repetition as? MDMRepetition { + repetitionCopy.autoreverses = !repetitionCopy.autoreverses + repetitionCopy.numberOfRepetitions = repetitionCopy.numberOfRepetitions + 1 + + XCTAssertNotEqual(repetitionCopy.autoreverses, repetition.autoreverses) + XCTAssertNotEqual(repetitionCopy.numberOfRepetitions, repetition.numberOfRepetitions) + } + + XCTAssertNotEqualWithAccuracy(copy.duration, traits.duration, 0.001) + XCTAssertNotEqualWithAccuracy(copy.delay, traits.delay, 0.001) + } } From a4301e2eb55594408452446b37dec9a13d93ca53 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Wed, 13 Dec 2017 09:42:49 -0500 Subject: [PATCH 20/28] Fixing travis builds. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7dca0e7..549e7b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,6 @@ before_install: - pod install --repo-update script: - set -o pipefail - - xcodebuild test -workspace MotionInterchange.xcworkspace -scheme MotionInterchangeCatalog -sdk "iphonesimulator10.3" -destination "name=iPhone 6s,OS=10.1" -enableCodeCoverage YES ONLY_ACTIVE_ARCH=YES | xcpretty -c; + - xcodebuild test -workspace MotionInterchange.xcworkspace -scheme MotionInterchangeCatalog -sdk "iphonesimulator10.3" -destination "name=iPhone SE,OS=10.3" -enableCodeCoverage YES ONLY_ACTIVE_ARCH=YES | xcpretty -c; after_success: - bash <(curl -s https://codecov.io/bash) From e9226a45892cf1a3f783232e6e7fc81751aaeb1d Mon Sep 17 00:00:00 2001 From: featherless Date: Wed, 13 Dec 2017 09:43:20 -0500 Subject: [PATCH 21/28] Extract the UIKit damping ratio APIs to their own class. (#31) It was becoming difficult to document and test the behavior of the spring timing curve when it had both the explicit APIs and the UIKit dampingRatio variant in the same class. Making the UIKit API a generator simplifies the implementation. --- src/MDMSpringTimingCurve.h | 29 -------- src/MDMSpringTimingCurve.m | 99 ++------------------------- src/MDMSpringTimingCurveGenerator.h | 91 ++++++++++++++++++++++++ src/MDMSpringTimingCurveGenerator.m | 78 +++++++++++++++++++++ src/MotionInterchange.h | 1 + tests/unit/MDMSpringTimingCurve.swift | 72 ++----------------- 6 files changed, 179 insertions(+), 191 deletions(-) create mode 100644 src/MDMSpringTimingCurveGenerator.h create mode 100644 src/MDMSpringTimingCurveGenerator.m diff --git a/src/MDMSpringTimingCurve.h b/src/MDMSpringTimingCurve.h index 47e0486..cafaf1f 100644 --- a/src/MDMSpringTimingCurve.h +++ b/src/MDMSpringTimingCurve.h @@ -55,35 +55,6 @@ initialVelocity:(CGFloat)initialVelocity NS_DESIGNATED_INITIALIZER; -/** - Initializes the timing curve with the given UIKit spring damping ratio. - - @param duration The desired duration of the spring animation. - @param dampingRatio From the UIKit documentation: "When `dampingRatio` is 1, the animation will - smoothly decelerate to its final model values without oscillating. Damping ratios less than 1 will - oscillate more and more before coming to a complete stop." - */ -- (nonnull instancetype)initWithDuration:(NSTimeInterval)duration - dampingRatio:(CGFloat)dampingRatio; - -/** - Initializes the timing curve with the given UIKit spring damping ratio and initial velocity. - - @param duration The desired duration of the spring animation. - @param dampingRatio From the UIKit documentation: "When `dampingRatio` is 1, the animation will - smoothly decelerate to its final model values without oscillating. Damping ratios less than 1 will - oscillate more and more before coming to a complete stop." - @param initialVelocity From the UIKit documentation: "You can use the initial spring velocity to - specify how fast the object at the end of the simulated spring was moving before it was attached. - It's a unit coordinate system, where 1 is defined as travelling the total animation distance in a - second. So if you're changing an object's position by 200pt in this animation, and you want the - animation to behave as if the object was moving at 100pt/s before the animation started, you'd - pass 0.5. You'll typically want to pass 0 for the velocity." - */ -- (nonnull instancetype)initWithDuration:(NSTimeInterval)duration - dampingRatio:(CGFloat)dampingRatio - initialVelocity:(CGFloat)initialVelocity; - #pragma mark - Traits /** diff --git a/src/MDMSpringTimingCurve.m b/src/MDMSpringTimingCurve.m index 801d976..14311ab 100644 --- a/src/MDMSpringTimingCurve.m +++ b/src/MDMSpringTimingCurve.m @@ -16,40 +16,13 @@ #import "MDMSpringTimingCurve.h" -#import - -@implementation MDMSpringTimingCurve { - CGFloat _duration; - CGFloat _dampingRatio; - - BOOL _coefficientsAreInvalid; -} - -@synthesize mass = _mass; -@synthesize friction = _friction; -@synthesize tension = _tension; +@implementation MDMSpringTimingCurve - (instancetype)init { [self doesNotRecognizeSelector:_cmd]; return nil; } -- (instancetype)initWithDuration:(NSTimeInterval)duration dampingRatio:(CGFloat)dampingRatio { - return [self initWithDuration:duration dampingRatio:dampingRatio initialVelocity:0]; -} - -- (nonnull instancetype)initWithDuration:(NSTimeInterval)duration - dampingRatio:(CGFloat)dampingRatio - initialVelocity:(CGFloat)initialVelocity { - self = [self initWithMass:0 tension:0 friction:0 initialVelocity:initialVelocity]; - if (self) { - _duration = duration; - _dampingRatio = dampingRatio; - _coefficientsAreInvalid = YES; - } - return self; -} - - (instancetype)initWithMass:(CGFloat)mass tension:(CGFloat)tension friction:(CGFloat)friction { return [self initWithMass:mass tension:tension friction:friction initialVelocity:0]; } @@ -68,78 +41,16 @@ - (instancetype)initWithMass:(CGFloat)mass return self; } -- (CGFloat)mass { - [self recalculateCoefficientsIfNeeded]; - return _mass; -} - -- (CGFloat)tension { - [self recalculateCoefficientsIfNeeded]; - return _tension; -} - -- (CGFloat)friction { - [self recalculateCoefficientsIfNeeded]; - return _friction; -} - -- (void)setInitialVelocity:(CGFloat)initialVelocity { - _initialVelocity = initialVelocity; - - _coefficientsAreInvalid = YES; -} - #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { - MDMSpringTimingCurve *copy = - [[[self class] allocWithZone:zone] initWithMass:self.mass - tension:self.tension - friction:self.friction - initialVelocity:self.initialVelocity]; - copy->_coefficientsAreInvalid = _coefficientsAreInvalid; - copy->_duration = _duration; - copy->_dampingRatio = _dampingRatio; - return copy; + return [[[self class] allocWithZone:zone] initWithMass:self.mass + tension:self.tension + friction:self.friction + initialVelocity:self.initialVelocity];; } #pragma mark - Private -- (void)recalculateCoefficientsIfNeeded { - if (_coefficientsAreInvalid) { - [self recalculateCoefficients]; - } -} - -- (void)recalculateCoefficients { - UIView *view = [[UIView alloc] init]; - [UIView animateWithDuration:_duration - delay:0 - usingSpringWithDamping:_dampingRatio - initialSpringVelocity:self.initialVelocity - options:0 - animations:^{ - view.center = CGPointMake(100, 100); - } completion:nil]; - - NSString *animationKey = [view.layer.animationKeys firstObject]; - NSAssert(animationKey != nil, @"Unable to extract animation timing curve: no animation found."); -#pragma clang diagnostic push - // CASpringAnimation is a private API on iOS 8 - we're able to make use of it because we're - // linking against the public API on iOS 9+. -#pragma clang diagnostic ignored "-Wpartial-availability" - CASpringAnimation *springAnimation = - (CASpringAnimation *)[view.layer animationForKey:animationKey]; - NSAssert([springAnimation isKindOfClass:[CASpringAnimation class]], - @"Unable to extract animation timing curve: unexpected animation type."); -#pragma clang diagnostic pop - - _mass = springAnimation.mass; - _tension = springAnimation.stiffness; - _friction = springAnimation.damping; - - _coefficientsAreInvalid = NO; -} - @end diff --git a/src/MDMSpringTimingCurveGenerator.h b/src/MDMSpringTimingCurveGenerator.h new file mode 100644 index 0000000..fee9abd --- /dev/null +++ b/src/MDMSpringTimingCurveGenerator.h @@ -0,0 +1,91 @@ +/* + 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 + +#import "MDMTimingCurve.h" + +@class MDMSpringTimingCurve; + +/** + A spring timing curve generator based on UIKit duration/dampingRatio-based coefficients. + */ +@interface MDMSpringTimingCurveGenerator : NSObject + +/** + Initializes the timing curve with the given UIKit spring damping ratio. + + @param duration The desired duration of the spring animation. + @param dampingRatio From the UIKit documentation: "When `dampingRatio` is 1, the animation will + smoothly decelerate to its final model values without oscillating. Damping ratios less than 1 will + oscillate more and more before coming to a complete stop." + */ +- (nonnull instancetype)initWithDuration:(NSTimeInterval)duration + dampingRatio:(CGFloat)dampingRatio; + +/** + Initializes the timing curve with the given UIKit spring damping ratio and initial velocity. + + @param duration The desired duration of the spring animation. + @param dampingRatio From the UIKit documentation: "When `dampingRatio` is 1, the animation will + smoothly decelerate to its final model values without oscillating. Damping ratios less than 1 will + oscillate more and more before coming to a complete stop." + @param initialVelocity From the UIKit documentation: "You can use the initial spring velocity to + specify how fast the object at the end of the simulated spring was moving before it was attached. + It's a unit coordinate system, where 1 is defined as travelling the total animation distance in a + second. So if you're changing an object's position by 200pt in this animation, and you want the + animation to behave as if the object was moving at 100pt/s before the animation started, you'd + pass 0.5. You'll typically want to pass 0 for the velocity." + */ +- (nonnull instancetype)initWithDuration:(NSTimeInterval)duration + dampingRatio:(CGFloat)dampingRatio + initialVelocity:(CGFloat)initialVelocity + NS_DESIGNATED_INITIALIZER; + +#pragma mark - Traits + +/** + The desired duration of the spring animation. + */ +@property(nonatomic, assign) NSTimeInterval duration; + +/** + From the UIKit documentation: "When `dampingRatio` is 1, the animation will + smoothly decelerate to its final model values without oscillating. Damping ratios less than 1 will + oscillate more and more before coming to a complete stop." + */ +@property(nonatomic, assign) CGFloat dampingRatio; + +/** + From the UIKit documentation: "You can use the initial spring velocity to + specify how fast the object at the end of the simulated spring was moving before it was attached. + It's a unit coordinate system, where 1 is defined as travelling the total animation distance in a + second. So if you're changing an object's position by 200pt in this animation, and you want the + animation to behave as if the object was moving at 100pt/s before the animation started, you'd + pass 0.5. You'll typically want to pass 0 for the velocity." + */ +@property(nonatomic, assign) CGFloat initialVelocity; + +/** + Creates and returns a new spring timing curve instance with the current configuration. + */ +- (nonnull MDMSpringTimingCurve *)springTimingCurve; + +/** Unavailable. */ +- (nonnull instancetype)init NS_UNAVAILABLE; + +@end diff --git a/src/MDMSpringTimingCurveGenerator.m b/src/MDMSpringTimingCurveGenerator.m new file mode 100644 index 0000000..62af3e7 --- /dev/null +++ b/src/MDMSpringTimingCurveGenerator.m @@ -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 "MDMSpringTimingCurveGenerator.h" + +#import "MDMSpringTimingCurve.h" + +#import + +@implementation MDMSpringTimingCurveGenerator + +- (instancetype)initWithDuration:(NSTimeInterval)duration dampingRatio:(CGFloat)dampingRatio { + return [self initWithDuration:duration dampingRatio:dampingRatio initialVelocity:0]; +} + +- (nonnull instancetype)initWithDuration:(NSTimeInterval)duration + dampingRatio:(CGFloat)dampingRatio + initialVelocity:(CGFloat)initialVelocity { + self = [super init]; + if (self) { + _duration = duration; + _dampingRatio = dampingRatio; + _initialVelocity = initialVelocity; + } + return self; +} + +#pragma mark - NSCopying + +- (id)copyWithZone:(NSZone *)zone { + return [[[self class] allocWithZone:zone] initWithDuration:self.duration + dampingRatio:self.dampingRatio + initialVelocity:self.initialVelocity]; +} + +- (MDMSpringTimingCurve *)springTimingCurve { + UIView *view = [[UIView alloc] init]; + [UIView animateWithDuration:self.duration + delay:0 + usingSpringWithDamping:self.dampingRatio + initialSpringVelocity:self.initialVelocity + options:0 + animations:^{ + view.center = CGPointMake(100, 100); + } completion:nil]; + + NSString *animationKey = [view.layer.animationKeys firstObject]; + NSAssert(animationKey != nil, @"Unable to extract animation timing curve: no animation found."); +#pragma clang diagnostic push + // CASpringAnimation is a private API on iOS 8 - we're able to make use of it because we're + // linking against the public API on iOS 9+. +#pragma clang diagnostic ignored "-Wpartial-availability" + CASpringAnimation *springAnimation = + (CASpringAnimation *)[view.layer animationForKey:animationKey]; + NSAssert([springAnimation isKindOfClass:[CASpringAnimation class]], + @"Unable to extract animation timing curve: unexpected animation type."); +#pragma clang diagnostic pop + + return [[MDMSpringTimingCurve alloc] initWithMass:springAnimation.mass + tension:springAnimation.stiffness + friction:springAnimation.damping + initialVelocity:self.initialVelocity]; +} + +@end diff --git a/src/MotionInterchange.h b/src/MotionInterchange.h index 540e559..5552b11 100644 --- a/src/MotionInterchange.h +++ b/src/MotionInterchange.h @@ -21,3 +21,4 @@ #import "MDMRepetitionOverTime.h" #import "MDMTimingCurve.h" #import "MDMSpringTimingCurve.h" +#import "MDMSpringTimingCurveGenerator.h" diff --git a/tests/unit/MDMSpringTimingCurve.swift b/tests/unit/MDMSpringTimingCurve.swift index 0430651..8d20f36 100644 --- a/tests/unit/MDMSpringTimingCurve.swift +++ b/tests/unit/MDMSpringTimingCurve.swift @@ -40,9 +40,9 @@ class MDMTimingCurveTests: XCTestCase { for duration in stride(from: TimeInterval(0.1), to: TimeInterval(3), by: TimeInterval(0.5)) { for dampingRatio in stride(from: CGFloat(0.1), to: CGFloat(2), by: CGFloat(0.4)) { for initialVelocity in stride(from: CGFloat(-2), to: CGFloat(2), by: CGFloat(0.8)) { - let curve = MDMSpringTimingCurve(duration: duration, - dampingRatio: dampingRatio, - initialVelocity: initialVelocity) + let generator = MDMSpringTimingCurveGenerator(duration: duration, + dampingRatio: dampingRatio, + initialVelocity: initialVelocity) let view = UIView() UIView.animate(withDuration: duration, @@ -57,6 +57,7 @@ class MDMTimingCurveTests: XCTestCase { if let animationKey = view.layer.animationKeys()?.first, let animation = view.layer.animation(forKey: animationKey) as? CASpringAnimation { + let curve = generator.springTimingCurve() XCTAssertEqualWithAccuracy(curve.mass, animation.mass, accuracy: 0.001) XCTAssertEqualWithAccuracy(curve.tension, animation.stiffness, accuracy: 0.001) XCTAssertEqualWithAccuracy(curve.friction, animation.damping, accuracy: 0.001) @@ -66,69 +67,4 @@ class MDMTimingCurveTests: XCTestCase { } } } - - func testChangingInitialVelocityInvalidatesTheCoefficients() { - let curve = MDMSpringTimingCurve(duration: 0.5, - dampingRatio: 0.8, - initialVelocity: 0) - - let originalMass = curve.mass - let originalTension = curve.tension - let originalFriction = curve.friction - - curve.initialVelocity = 10 - - // UIKit never appears to change the mass. - XCTAssertEqualWithAccuracy(curve.mass, originalMass, accuracy: 0.001) - XCTAssertNotEqualWithAccuracy(curve.tension, originalTension, 0.001) - XCTAssertNotEqualWithAccuracy(curve.friction, originalFriction, 0.001) - } - - func testWithDampingRatioSettingMassKeepsTheNewMass() { - let curve = MDMSpringTimingCurve(duration: 0.5, - dampingRatio: 0.8, - initialVelocity: 0) - - let originalMass = curve.mass - let originalTension = curve.tension - let originalFriction = curve.friction - - curve.mass = originalMass + 1 - - XCTAssertEqualWithAccuracy(curve.mass, originalMass + 1, accuracy: 0.001) - XCTAssertEqualWithAccuracy(curve.tension, originalTension, accuracy: 0.001) - XCTAssertEqualWithAccuracy(curve.friction, originalFriction, accuracy: 0.001) - } - - func testWithDampingRatioSettingTensionKeepsTheNewTension() { - let curve = MDMSpringTimingCurve(duration: 0.5, - dampingRatio: 0.8, - initialVelocity: 0) - - let originalMass = curve.mass - let originalTension = curve.tension - let originalFriction = curve.friction - - curve.tension = originalTension + 1 - - XCTAssertEqualWithAccuracy(curve.mass, originalMass, accuracy: 0.001) - XCTAssertEqualWithAccuracy(curve.tension, originalTension + 1, accuracy: 0.001) - XCTAssertEqualWithAccuracy(curve.friction, originalFriction, accuracy: 0.001) - } - - func testWithDampingRatioSettingFrictionKeepsTheNewFriction() { - let curve = MDMSpringTimingCurve(duration: 0.5, - dampingRatio: 0.8, - initialVelocity: 0) - - let originalMass = curve.mass - let originalTension = curve.tension - let originalFriction = curve.friction - - curve.friction = originalFriction + 1 - - XCTAssertEqualWithAccuracy(curve.mass, originalMass, accuracy: 0.001) - XCTAssertEqualWithAccuracy(curve.tension, originalTension, accuracy: 0.001) - XCTAssertEqualWithAccuracy(curve.friction, originalFriction + 1, accuracy: 0.001) - } } From 4a857a0393d55e01d1b282d12a814cec197111c0 Mon Sep 17 00:00:00 2001 From: featherless Date: Wed, 13 Dec 2017 09:56:26 -0500 Subject: [PATCH 22/28] Re-introduce v1 APIs. (#33) This is a partial revert of commit 3a28e221cfe9fccc2c46523adbb817334e7e918a. This will allow us to gradually migrate clients to the new v2 APIs without immediately introducing a breaking change. --- src/MDMMotionCurve.h | 194 +++++++++++++++++++++++++++ src/MDMMotionCurve.m | 51 +++++++ src/MDMMotionRepetition.h | 70 ++++++++++ src/MDMMotionTiming.h | 48 +++++++ src/MotionInterchange.h | 6 + tests/unit/MDMMotionCurveTests.m | 75 +++++++++++ tests/unit/MDMMotionCurveTests.swift | 65 +++++++++ 7 files changed, 509 insertions(+) create mode 100644 src/MDMMotionCurve.h create mode 100644 src/MDMMotionCurve.m create mode 100644 src/MDMMotionRepetition.h create mode 100644 src/MDMMotionTiming.h create mode 100644 tests/unit/MDMMotionCurveTests.m create mode 100644 tests/unit/MDMMotionCurveTests.swift diff --git a/src/MDMMotionCurve.h b/src/MDMMotionCurve.h new file mode 100644 index 0000000..0b233eb --- /dev/null +++ b/src/MDMMotionCurve.h @@ -0,0 +1,194 @@ +/* + 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 +#import + +/** + The possible kinds of motion curves that can be used to describe an animation. + */ +typedef NS_ENUM(NSUInteger, MDMMotionCurveType) { + /** + The value will be instantly set with no animation. + */ + MDMMotionCurveTypeInstant, + + /** + The value will be animated using a cubic bezier curve to model its velocity. + */ + MDMMotionCurveTypeBezier, + + /** + The value will be animated using a spring simulation. + + A spring will treat the duration property of the motion timing as a suggestion and may choose to + ignore it altogether. + */ + MDMMotionCurveTypeSpring, + + /** + The default curve will be used. + */ + MDMMotionCurveTypeDefault __deprecated_enum_msg("Use MDMMotionCurveTypeBezier instead."), + +} NS_SWIFT_NAME(MotionCurveType); + +/** + A generalized representation of a motion curve. + */ +struct MDMMotionCurve { + /** + The type defines how to interpret the data values. + */ + MDMMotionCurveType type; + + /** + The data values corresponding with this curve. + */ + CGFloat data[4]; +} NS_SWIFT_NAME(MotionCurve); +typedef struct MDMMotionCurve MDMMotionCurve; + +/** + Creates a bezier motion curve with the provided control points. + + A cubic bezier has four control points in total. We assume that the first control point is 0, 0 and + the last control point is 1, 1. This method requires that you provide the second and third control + points. + + See the documentation for CAMediaTimingFunction for more information. + */ +// clang-format off +FOUNDATION_EXTERN +MDMMotionCurve MDMMotionCurveMakeBezier(CGFloat p1x, CGFloat p1y, CGFloat p2x, CGFloat p2y) + NS_SWIFT_NAME(MotionCurveMakeBezier(p1x:p1y:p2x:p2y:)); +// clang-format on + +// clang-format off +FOUNDATION_EXTERN +MDMMotionCurve MDMMotionCurveFromTimingFunction(CAMediaTimingFunction * _Nonnull timingFunction) + NS_SWIFT_NAME(MotionCurve(fromTimingFunction:)); +// clang-format on + +/** + Creates a spring curve with the provided configuration. + + Tension and friction map to Core Animation's stiffness and damping, respectively. + + See the documentation for CASpringAnimation for more information. + */ +// clang-format off +FOUNDATION_EXTERN MDMMotionCurve MDMMotionCurveMakeSpring(CGFloat mass, + CGFloat tension, + CGFloat friction) + NS_SWIFT_NAME(MotionCurveMakeSpring(mass:tension:friction:)); +// clang-format on + +/** + Creates a spring curve with the provided configuration. + + Tension and friction map to Core Animation's stiffness and damping, respectively. + + See the documentation for CASpringAnimation for more information. + */ +// clang-format off +FOUNDATION_EXTERN +MDMMotionCurve MDMMotionCurveMakeSpringWithInitialVelocity(CGFloat mass, + CGFloat tension, + CGFloat friction, + CGFloat initialVelocity) + NS_SWIFT_NAME(MotionCurveMakeSpring(mass:tension:friction:initialVelocity:)); +// clang-format on + +/** + For cubic bezier curves, returns a reversed cubic bezier curve. For all other curve types, a copy + of the original curve is returned. + */ +// clang-format off +FOUNDATION_EXTERN MDMMotionCurve MDMMotionCurveReversedBezier(MDMMotionCurve motionCurve) + NS_SWIFT_NAME(MotionCurveReversedBezier(fromMotionCurve:)); +// clang-format on + +/** + Named indices for the bezier motion curve's data array. + */ +typedef NS_ENUM(NSUInteger, MDMBezierMotionCurveDataIndex) { + MDMBezierMotionCurveDataIndexP1X, + MDMBezierMotionCurveDataIndexP1Y, + MDMBezierMotionCurveDataIndexP2X, + MDMBezierMotionCurveDataIndexP2Y +} NS_SWIFT_NAME(BezierMotionCurveDataIndex); + +/** + Named indices for the spring motion curve's data array. + */ +typedef NS_ENUM(NSUInteger, MDMSpringMotionCurveDataIndex) { + MDMSpringMotionCurveDataIndexMass, + MDMSpringMotionCurveDataIndexTension, + MDMSpringMotionCurveDataIndexFriction, + + /** + The initial velocity of the animation. + + A value of zero indicates no initial velocity. + A positive value indicates movement toward the destination. + A negative value indicates movement away from the destination. + + The value's units are dependent on the context and the value being animated. + */ + MDMSpringMotionCurveDataIndexInitialVelocity +} NS_SWIFT_NAME(SpringMotionCurveDataIndex); + +// Objective-C-specific macros + +#define _MDMBezier(p1x, p1y, p2x, p2y) \ + ((MDMMotionCurve) { \ + .type = MDMMotionCurveTypeBezier, \ + .data = { p1x, \ + p1y, \ + p2x, \ + p2y } \ + }) + +#define _MDMSpring(mass, tension, friction) \ + ((MDMMotionCurve) { \ + .type = MDMMotionCurveTypeSpring, \ + .data = { mass, \ + tension, \ + friction } \ + }) + +#define _MDMSpringWithInitialVelocity(mass, tension, friction, initialVelocity) \ + ((MDMMotionCurve) { \ + .type = MDMMotionCurveTypeSpring, \ + .data = { mass, \ + tension, \ + friction, \ + initialVelocity } \ + }) + +/** + A linear bezier motion curve. + */ +#define MDMLinearMotionCurve _MDMBezier(0, 0, 1, 1) + +/** + Timing information for an iOS modal presentation slide animation. + */ +#define MDMModalMovementTiming { \ + .delay = 0.000, .duration = 0.500, .curve = _MDMSpring(3, 1000, 500) \ +} diff --git a/src/MDMMotionCurve.m b/src/MDMMotionCurve.m new file mode 100644 index 0000000..2cc57c8 --- /dev/null +++ b/src/MDMMotionCurve.m @@ -0,0 +1,51 @@ +/* + 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 "MDMMotionCurve.h" + +MDMMotionCurve MDMMotionCurveMakeBezier(CGFloat p1x, CGFloat p1y, CGFloat p2x, CGFloat p2y) { + return _MDMBezier(p1x, p1y, p2x, p2y); +} + +MDMMotionCurve MDMMotionCurveMakeSpring(CGFloat mass, CGFloat tension, CGFloat friction) { + return MDMMotionCurveMakeSpringWithInitialVelocity(mass, tension, friction, 0); +} + +MDMMotionCurve MDMMotionCurveMakeSpringWithInitialVelocity(CGFloat mass, + CGFloat tension, + CGFloat friction, + CGFloat initialVelocity) { + return _MDMSpringWithInitialVelocity(mass, tension, friction, initialVelocity); +} + +MDMMotionCurve MDMMotionCurveFromTimingFunction(CAMediaTimingFunction *timingFunction) { + float pt1[2]; + float pt2[2]; + [timingFunction getControlPointAtIndex:1 values:pt1]; + [timingFunction getControlPointAtIndex:2 values:pt2]; + return MDMMotionCurveMakeBezier(pt1[0], pt1[1], pt2[0], pt2[1]); +} + +MDMMotionCurve MDMMotionCurveReversedBezier(MDMMotionCurve motionCurve) { + MDMMotionCurve reversed = motionCurve; + if (motionCurve.type == MDMMotionCurveTypeBezier) { + reversed.data[0] = 1 - motionCurve.data[2]; + reversed.data[1] = 1 - motionCurve.data[3]; + reversed.data[2] = 1 - motionCurve.data[0]; + reversed.data[3] = 1 - motionCurve.data[1]; + } + return reversed; +} diff --git a/src/MDMMotionRepetition.h b/src/MDMMotionRepetition.h new file mode 100644 index 0000000..94fc266 --- /dev/null +++ b/src/MDMMotionRepetition.h @@ -0,0 +1,70 @@ +/* + 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 + +/** + The possible kinds of repetition that can be used to describe an animation. + */ +typedef NS_ENUM(NSUInteger, MDMMotionRepetitionType) { + /** + The animation will be not be repeated. + */ + MDMMotionRepetitionTypeNone, + + /** + The animation will be repeated a given number of times. + */ + MDMMotionRepetitionTypeCount, + + /** + The animation will be repeated for a given number of seconds. + */ + MDMMotionRepetitionTypeDuration, + +} NS_SWIFT_NAME(MotionReptitionType); + +/** + A generalized representation of a motion curve. + */ +struct MDMMotionRepetition { + /** + The type defines how to interpret the amount. + */ + MDMMotionRepetitionType type; + + /** + The amount of repetition. + */ + double amount; + + /** + Whether the animation should animate backwards after animating forwards. + */ + BOOL autoreverses; + +} NS_SWIFT_NAME(MotionRepetition); +typedef struct MDMMotionRepetition MDMMotionRepetition; + +// Objective-C-specific macros + +#define _MDMNoRepetition \ + (MDMMotionRepetition) { \ + .type = MDMMotionRepetitionTypeNone, \ + .amount = 0, \ + .autoreverses = false \ + } diff --git a/src/MDMMotionTiming.h b/src/MDMMotionTiming.h new file mode 100644 index 0000000..b225570 --- /dev/null +++ b/src/MDMMotionTiming.h @@ -0,0 +1,48 @@ +/* + 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 + +#import "MDMMotionCurve.h" +#import "MDMMotionRepetition.h" + +/** + A representation of timing for an animation. + */ +struct MDMMotionTiming { + /** + The amount of time, in seconds, before this animation's value interpolation should begin. + */ + CFTimeInterval delay; + + /** + The amount of time, in seconds, over which this animation should interpolate between its values. + */ + CFTimeInterval duration; + + /** + The velocity and acceleration of the animation over time. + */ + MDMMotionCurve curve; + + /** + The repetition characteristics of the animation. + */ + MDMMotionRepetition repetition; + +} NS_SWIFT_NAME(MotionTiming); +typedef struct MDMMotionTiming MDMMotionTiming; diff --git a/src/MotionInterchange.h b/src/MotionInterchange.h index 5552b11..be49d07 100644 --- a/src/MotionInterchange.h +++ b/src/MotionInterchange.h @@ -14,6 +14,7 @@ limitations under the License. */ +// V2 APIs #import "CAMediaTimingFunction+MDMTimingCurve.h" #import "MDMAnimationTraits.h" #import "MDMRepetitionTraits.h" @@ -22,3 +23,8 @@ #import "MDMTimingCurve.h" #import "MDMSpringTimingCurve.h" #import "MDMSpringTimingCurveGenerator.h" + +// V1 APIs +#import "MDMMotionCurve.h" +#import "MDMMotionRepetition.h" +#import "MDMMotionTiming.h" diff --git a/tests/unit/MDMMotionCurveTests.m b/tests/unit/MDMMotionCurveTests.m new file mode 100644 index 0000000..0cf4941 --- /dev/null +++ b/tests/unit/MDMMotionCurveTests.m @@ -0,0 +1,75 @@ +/* + 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 "MotionInterchange.h" + +@interface MDMMotionCurveTests : XCTestCase +@end + +@implementation MDMMotionCurveTests + +- (void)testLinearCurveConstantMatchesSystemLinearCurve { + MDMMotionCurve curve = MDMLinearMotionCurve; + CAMediaTimingFunction *linearTimingFunction = + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; + MDMMotionCurve systemLinearCurve = MDMMotionCurveFromTimingFunction(linearTimingFunction); + XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP1X], + systemLinearCurve.data[MDMBezierMotionCurveDataIndexP1X], + 0.001); + XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP1Y], + systemLinearCurve.data[MDMBezierMotionCurveDataIndexP1Y], + 0.001); + XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP2X], + systemLinearCurve.data[MDMBezierMotionCurveDataIndexP2X], + 0.001); + XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP2Y], + systemLinearCurve.data[MDMBezierMotionCurveDataIndexP2Y], + 0.001); +} + +- (void)testBezierCurveData { + MDMMotionCurve curve = MDMMotionCurveMakeBezier(0.1f, 0.2f, 0.3f, 0.4f); + XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP1X], 0.1, 0.001); + XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP1Y], 0.2, 0.001); + XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP2X], 0.3, 0.001); + XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP2Y], 0.4, 0.001); +} + +- (void)testSpringCurveData { + MDMMotionCurve curve = MDMMotionCurveMakeSpring(0.1f, 0.2f, 0.3f); + XCTAssertEqualWithAccuracy(curve.data[MDMSpringMotionCurveDataIndexMass], 0.1, 0.001); + XCTAssertEqualWithAccuracy(curve.data[MDMSpringMotionCurveDataIndexTension], 0.2, 0.001); + XCTAssertEqualWithAccuracy(curve.data[MDMSpringMotionCurveDataIndexFriction], 0.3, 0.001); +} + +- (void)testBezierCurveDataWithMacro { + MDMMotionCurve curve = _MDMBezier(0.1, 0.2, 0.3, 0.4); + XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP1X], 0.1, 0.001); + XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP1Y], 0.2, 0.001); + XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP2X], 0.3, 0.001); + XCTAssertEqualWithAccuracy(curve.data[MDMBezierMotionCurveDataIndexP2Y], 0.4, 0.001); +} + +- (void)testSpringCurveDataWithMacro { + MDMMotionCurve curve = _MDMSpring(0.1, 0.2, 0.3); + XCTAssertEqualWithAccuracy(curve.data[MDMSpringMotionCurveDataIndexMass], 0.1, 0.001); + XCTAssertEqualWithAccuracy(curve.data[MDMSpringMotionCurveDataIndexTension], 0.2, 0.001); + XCTAssertEqualWithAccuracy(curve.data[MDMSpringMotionCurveDataIndexFriction], 0.3, 0.001); +} + +@end diff --git a/tests/unit/MDMMotionCurveTests.swift b/tests/unit/MDMMotionCurveTests.swift new file mode 100644 index 0000000..0f1fb27 --- /dev/null +++ b/tests/unit/MDMMotionCurveTests.swift @@ -0,0 +1,65 @@ +/* + 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 MotionInterchange + +class MDMMotionCurveTests: XCTestCase { + + func testBezierCurveData() { + let curve = MotionCurveMakeBezier(p1x: 0.1, p1y: 0.2, p2x: 0.3, p2y: 0.4) + XCTAssertEqualWithAccuracy(curve.data.0, 0.1, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.data.1, 0.2, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.data.2, 0.3, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.data.3, 0.4, accuracy: 0.001) + } + + func testBezierCurveFromTimingFunction() { + let timingFunction = CAMediaTimingFunction(controlPoints: 0.1, 0.2, 0.3, 0.4) + let curve = MotionCurve(fromTimingFunction: timingFunction) + XCTAssertEqualWithAccuracy(curve.data.0, 0.1, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.data.1, 0.2, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.data.2, 0.3, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.data.3, 0.4, accuracy: 0.001) + } + + func testSpringCurveData() { + let curve = MotionCurveMakeSpring(mass: 0.1, tension: 0.2, friction: 0.3) + XCTAssertEqualWithAccuracy(curve.data.0, 0.1, accuracy: 0.001) // mass + XCTAssertEqualWithAccuracy(curve.data.1, 0.2, accuracy: 0.001) // tension + XCTAssertEqualWithAccuracy(curve.data.2, 0.3, accuracy: 0.001) // friction + XCTAssertEqualWithAccuracy(curve.data.3, 0.0, accuracy: 0.001) + } + + func testReversedBezierCurve() { + let curve = MotionCurveMakeBezier(p1x: 0.1, p1y: 0.2, p2x: 0.3, p2y: 0.4) + let reversed = MotionCurveReversedBezier(fromMotionCurve: curve) + XCTAssertEqualWithAccuracy(curve.data.0, 1 - reversed.data.2, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.data.1, 1 - reversed.data.3, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.data.2, 1 - reversed.data.0, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.data.3, 1 - reversed.data.1, accuracy: 0.001) + } + + func testReversingBezierCurveTwiceGivesSameResult() { + let curve = MotionCurveMakeBezier(p1x: 0.1, p1y: 0.2, p2x: 0.3, p2y: 0.4) + let reversed = MotionCurveReversedBezier(fromMotionCurve: curve) + let reversedAgain = MotionCurveReversedBezier(fromMotionCurve: reversed) + XCTAssertEqualWithAccuracy(curve.data.0, reversedAgain.data.0, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.data.1, reversedAgain.data.1, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.data.2, reversedAgain.data.2, accuracy: 0.001) + XCTAssertEqualWithAccuracy(curve.data.3, reversedAgain.data.3, accuracy: 0.001) + } +} From 0530ba355d8af640fd9fd51df137c6aed494f63c Mon Sep 17 00:00:00 2001 From: featherless Date: Wed, 13 Dec 2017 10:00:24 -0500 Subject: [PATCH 23/28] Restrict subclassing on all types. (#32) --- .../project.pbxproj | 10 ++++++- src/MDMAnimationTraits.h | 2 ++ src/MDMRepetition.h | 2 ++ src/MDMRepetitionOverTime.h | 2 ++ src/MDMSpringTimingCurve.h | 2 ++ src/MDMSpringTimingCurveGenerator.h | 2 ++ src/MDMSubclassingRestricted.h | 26 +++++++++++++++++++ 7 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 src/MDMSubclassingRestricted.h diff --git a/examples/apps/Catalog/MotionInterchangeCatalog.xcodeproj/project.pbxproj b/examples/apps/Catalog/MotionInterchangeCatalog.xcodeproj/project.pbxproj index 899b7e7..df857cb 100644 --- a/examples/apps/Catalog/MotionInterchangeCatalog.xcodeproj/project.pbxproj +++ b/examples/apps/Catalog/MotionInterchangeCatalog.xcodeproj/project.pbxproj @@ -25,6 +25,8 @@ 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 */; }; + 66C6FB9E1FE175CA0039F790 /* MDMMotionCurveTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C6FB9A1FE175B80039F790 /* MDMMotionCurveTests.swift */; }; + 66C6FB9F1FE175D00039F790 /* MDMMotionCurveTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 66C6FB9B1FE175B80039F790 /* MDMMotionCurveTests.m */; }; 8867B13812C3FF5D47DAB6A6 /* Pods_UnitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F7FF982F18E05B85C840EA24 /* Pods_UnitTests.framework */; }; A433D59338049BD05D945199 /* Pods_MotionInterchangeCatalog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB060E9F4ECA42A5E69FB42A /* Pods_MotionInterchangeCatalog.framework */; }; /* End PBXBuildFile section */ @@ -74,6 +76,8 @@ 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 = ""; }; + 66C6FB9A1FE175B80039F790 /* MDMMotionCurveTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MDMMotionCurveTests.swift; sourceTree = ""; }; + 66C6FB9B1FE175B80039F790 /* MDMMotionCurveTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MDMMotionCurveTests.m; sourceTree = ""; }; CF426CCC8068036D56265374 /* 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 = ""; }; EB060E9F4ECA42A5E69FB42A /* Pods_MotionInterchangeCatalog.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MotionInterchangeCatalog.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F7FF982F18E05B85C840EA24 /* Pods_UnitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_UnitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -174,6 +178,8 @@ 664C8C541FD5A7B7004ED471 /* MDMRepetitionTests.swift */, 664C8C561FD5A831004ED471 /* MDMRepetitionOverTimeTests.swift */, 660248AC1FD1EE78004C0147 /* MDMSpringTimingCurve.swift */, + 66C6FB9B1FE175B80039F790 /* MDMMotionCurveTests.m */, + 66C6FB9A1FE175B80039F790 /* MDMMotionCurveTests.swift */, ); name = tests; path = ../../../tests/unit; @@ -300,7 +306,7 @@ TargetAttributes = { 666FAA7F1D384A6B000363DA = { CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 0830; + LastSwiftMigration = 0920; }; 666FAA931D384A6B000363DA = { CreatedOnToolsVersion = 7.3.1; @@ -489,7 +495,9 @@ files = ( 664C8C531FD5A555004ED471 /* CAMediaTimingFunctionTests.swift in Sources */, 660248AE1FD1EE78004C0147 /* MDMSpringTimingCurve.swift in Sources */, + 66C6FB9E1FE175CA0039F790 /* MDMMotionCurveTests.swift in Sources */, 6619E1D91FA0ED0300F3AB25 /* MDMModalMovementTimingTests.m in Sources */, + 66C6FB9F1FE175D00039F790 /* MDMMotionCurveTests.m in Sources */, 664C8C571FD5A831004ED471 /* MDMRepetitionOverTimeTests.swift in Sources */, 664C8C591FD5A8AC004ED471 /* MDMAnimationTraitsTests.swift in Sources */, 664C8C551FD5A7B7004ED471 /* MDMRepetitionTests.swift in Sources */, diff --git a/src/MDMAnimationTraits.h b/src/MDMAnimationTraits.h index 3f15639..f31c776 100644 --- a/src/MDMAnimationTraits.h +++ b/src/MDMAnimationTraits.h @@ -19,11 +19,13 @@ #import #import "MDMRepetitionTraits.h" +#import "MDMSubclassingRestricted.h" #import "MDMTimingCurve.h" /** A generic representation of animation traits. */ +MDM_SUBCLASSING_RESTRICTED @interface MDMAnimationTraits: NSObject /** diff --git a/src/MDMRepetition.h b/src/MDMRepetition.h index b785317..9098b75 100644 --- a/src/MDMRepetition.h +++ b/src/MDMRepetition.h @@ -17,10 +17,12 @@ #import #import "MDMRepetitionTraits.h" +#import "MDMSubclassingRestricted.h" /** Represents repetition that repeats a specific number of times. */ +MDM_SUBCLASSING_RESTRICTED @interface MDMRepetition: NSObject /** diff --git a/src/MDMRepetitionOverTime.h b/src/MDMRepetitionOverTime.h index 3bfea4c..d1d1bda 100644 --- a/src/MDMRepetitionOverTime.h +++ b/src/MDMRepetitionOverTime.h @@ -17,10 +17,12 @@ #import #import "MDMRepetitionTraits.h" +#import "MDMSubclassingRestricted.h" /** Represents repetition that repeats until a specific duration has passed. */ +MDM_SUBCLASSING_RESTRICTED @interface MDMRepetitionOverTime: NSObject /** diff --git a/src/MDMSpringTimingCurve.h b/src/MDMSpringTimingCurve.h index cafaf1f..9406c34 100644 --- a/src/MDMSpringTimingCurve.h +++ b/src/MDMSpringTimingCurve.h @@ -17,11 +17,13 @@ #import #import +#import "MDMSubclassingRestricted.h" #import "MDMTimingCurve.h" /** A timing curve that represents the motion of a single-dimensional attached spring. */ +MDM_SUBCLASSING_RESTRICTED @interface MDMSpringTimingCurve: NSObject /** diff --git a/src/MDMSpringTimingCurveGenerator.h b/src/MDMSpringTimingCurveGenerator.h index fee9abd..440d1aa 100644 --- a/src/MDMSpringTimingCurveGenerator.h +++ b/src/MDMSpringTimingCurveGenerator.h @@ -17,6 +17,7 @@ #import #import +#import "MDMSubclassingRestricted.h" #import "MDMTimingCurve.h" @class MDMSpringTimingCurve; @@ -24,6 +25,7 @@ /** A spring timing curve generator based on UIKit duration/dampingRatio-based coefficients. */ +MDM_SUBCLASSING_RESTRICTED @interface MDMSpringTimingCurveGenerator : NSObject /** diff --git a/src/MDMSubclassingRestricted.h b/src/MDMSubclassingRestricted.h new file mode 100644 index 0000000..05812a7 --- /dev/null +++ b/src/MDMSubclassingRestricted.h @@ -0,0 +1,26 @@ +/* + 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 + +#ifndef MDM_SUBCLASSING_RESTRICTED +#if defined(__has_attribute) && __has_attribute(objc_subclassing_restricted) +#define MDM_SUBCLASSING_RESTRICTED __attribute__((objc_subclassing_restricted)) +#else +#define MDM_SUBCLASSING_RESTRICTED +#endif +#endif // #ifndef MDM_SUBCLASSING_RESTRICTED + From c30d90457c7bf6901e684ba932a5dd3b8ecd5fe5 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Wed, 13 Dec 2017 10:03:46 -0500 Subject: [PATCH 24/28] Update docs with new API. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3173473..aa97440 100644 --- a/README.md +++ b/README.md @@ -185,11 +185,11 @@ let timingCurve = MDMSpringTimingCurve(mass: 1, tension: 100, friction: 10) let traits = MDMAnimationTraits(delay: 0, duration: 0.5, timingCurve: timingCurve) ``` -Springs can also be initialized using UIKit's [damping ratio concept](https://developer.apple.com/documentation/uikit/uiview/1622594-animatewithduration): +Springs can also be initialized using UIKit's [damping ratio concept](https://developer.apple.com/documentation/uikit/uiview/1622594-animatewithduration). The `MDMSpringTimingCurveGenerator` type generates `MDMSpringTimingCurve` instances when needed. A spring timing curve generator can be stored as the `timingCurve` of an `MDMAnimationTraits` instance. ```objc -MDMSpringTimingCurve *timingCurve = - [[MDMSpringTimingCurve alloc] initWithDuration:0.5 dampingRatio:0.5]; +MDMSpringTimingCurveGenerator *timingCurve = + [[MDMSpringTimingCurveGenerator alloc] initWithDuration:<#(NSTimeInterval)#> dampingRatio:<#(CGFloat)#>]; MDMAnimationTraits *traits = [[MDMAnimationTraits alloc] initWithDelay:0 duration:0.5 timingCurve:timingCurve]; ``` @@ -197,7 +197,7 @@ MDMAnimationTraits *traits = And in Swift: ```swift -let timingCurve = MDMSpringTimingCurve(duration: 0.5, dampingRatio: 0.5) +let timingCurve = MDMSpringTimingCurveGenerator(duration: 0.5, dampingRatio: 0.5) let traits = MDMAnimationTraits(delay: 0, duration: 0.5, timingCurve: timingCurve) ``` From 4d28d6f5ac937fd0cbb656ed21c04e1fd54bc195 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Wed, 13 Dec 2017 10:04:20 -0500 Subject: [PATCH 25/28] Automatic changelog preparation for release. --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7357ec..497fa0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# #develop# + + TODO: Enumerate changes. + + # 1.4.0 This minor release introduces new APIs for creating springs that have an initial velocity. From 50bb66a8ed46187d2d7d5fa3779a07aaaecbeea6 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Wed, 13 Dec 2017 10:11:01 -0500 Subject: [PATCH 26/28] Update CHANGELOG API diff. --- CHANGELOG.md | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 204 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 497fa0b..356f5a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,210 @@ # #develop# - TODO: Enumerate changes. +## Breaking changes +## New deprecations + +## New features + +## Source changes + +* [Restrict subclassing on all types. (#32)](https://github.com/material-motion/motion-interchange-objc/commit/0530ba355d8af640fd9fd51df137c6aed494f63c) (featherless) +* [Re-introduce v1 APIs. (#33)](https://github.com/material-motion/motion-interchange-objc/commit/4a857a0393d55e01d1b282d12a814cec197111c0) (featherless) +* [Extract the UIKit damping ratio APIs to their own class. (#31)](https://github.com/material-motion/motion-interchange-objc/commit/e9226a45892cf1a3f783232e6e7fc81751aaeb1d) (featherless) +* [Add support for copying animation traits. (#30)](https://github.com/material-motion/motion-interchange-objc/commit/f7fb09546d034fcdf8e57509aa157a760aebd398) (featherless) +* [Allow writing of all properties. (#26)](https://github.com/material-motion/motion-interchange-objc/commit/4f74d24b6d66503fed71af36482665814e10a3a4) (featherless) +* [Add support for damping ratio initializers on the spring timing curve. (#27)](https://github.com/material-motion/motion-interchange-objc/commit/9d7352cec750b8c00fe300708647ebb01189b6c2) (featherless) +* [Use UIViewAnimationCurve instead of NSString as the easing curve type. (#28)](https://github.com/material-motion/motion-interchange-objc/commit/d94246c2b5286d20f92dfc7569b39798394d9422) (featherless) +* [Add APIs for initializing an animation trait with a named timing function. (#25)](https://github.com/material-motion/motion-interchange-objc/commit/72e75e44a940e815a06ee516237451be0646b688) (featherless) +* [Implement v2 APIs (#22)](https://github.com/material-motion/motion-interchange-objc/commit/3a28e221cfe9fccc2c46523adbb817334e7e918a) (featherless) +* [Standardize the timing curve creation methods on CGFloat. (#21)](https://github.com/material-motion/motion-interchange-objc/commit/75f0d3515bda6cb9770c96ddb787a3da50a2b7c6) (featherless) + +## API changes + +Auto-generated by running: + + apidiff origin/stable release-candidate objc src/MotionInterchange.h + +#### MDMRepetitionOverTime + +*new* class: `MDMRepetitionOverTime` + +*new* method: `-initWithDuration:autoreverses:` in `MDMRepetitionOverTime` + +*new* method: `-init` in `MDMRepetitionOverTime` + +*new* property: `duration` in `MDMRepetitionOverTime` + +*new* method: `-initWithDuration:` in `MDMRepetitionOverTime` + +#### MDMTimingCurve + +*new* protocol: `MDMTimingCurve` + +#### CAMediaTimingFunction() + +*new* category: `CAMediaTimingFunction()` + +#### MDMRepetitionTraits + +*new* property: `autoreverses` in `MDMRepetitionTraits` + +*new* protocol: `MDMRepetitionTraits` + +#### MDMMotionCurveMakeSpringWithInitialVelocity + +*modified* function: `MDMMotionCurveMakeSpringWithInitialVelocity` + +| Type of change: | Swift declaration | +|---|---| +| From: | `func MotionCurveMakeSpring(mass: Float, tension: Float, friction: Float, initialVelocity: Float) -> MotionCurve` | +| To: | `func MotionCurveMakeSpring(mass: CGFloat, tension: CGFloat, friction: CGFloat, initialVelocity: CGFloat) -> MotionCurve` | + +*modified* function: `MDMMotionCurveMakeSpringWithInitialVelocity` + +| Type of change: | Declaration | +|---|---| +| From: | `extern MDMMotionCurve MDMMotionCurveMakeSpringWithInitialVelocity( float mass, float tension, float friction, float initialVelocity)` | +| To: | `extern MDMMotionCurve MDMMotionCurveMakeSpringWithInitialVelocity( CGFloat mass, CGFloat tension, CGFloat friction, CGFloat initialVelocity)` | + +#### MDMSpringTimingCurve + +*new* property: `tension` in `MDMSpringTimingCurve` + +*new* method: `-init` in `MDMSpringTimingCurve` + +*new* property: `friction` in `MDMSpringTimingCurve` + +*new* property: `initialVelocity` in `MDMSpringTimingCurve` + +*new* property: `mass` in `MDMSpringTimingCurve` + +*new* class: `MDMSpringTimingCurve` + +*new* method: `-initWithMass:tension:friction:` in `MDMSpringTimingCurve` + +*new* method: `-initWithMass:tension:friction:initialVelocity:` in `MDMSpringTimingCurve` + +#### MDMAnimationTraits + +*new* class: `MDMAnimationTraits` + +*new* property: `repetition` in `MDMAnimationTraits` + +*new* property: `delay` in `MDMAnimationTraits` + +*new* property: `duration` in `MDMAnimationTraits` + +*new* method: `-initWithDelay:duration:animationCurve:` in `MDMAnimationTraits` + +*new* method: `-initWithDelay:duration:timingCurve:repetition:` in `MDMAnimationTraits` + +*new* method: `-initWithDuration:animationCurve:` in `MDMAnimationTraits` + +*new* method: `-initWithDelay:duration:` in `MDMAnimationTraits` + +*new* method: `-init` in `MDMAnimationTraits` + +*new* method: `-initWithDelay:duration:timingCurve:` in `MDMAnimationTraits` + +*new* method: `-initWithDuration:` in `MDMAnimationTraits` + +*new* property: `timingCurve` in `MDMAnimationTraits` + +#### CAMediaTimingFunction(MotionInterchangeExtension) + +*new* method: `-mdm_reversed` in `CAMediaTimingFunction(MotionInterchangeExtension)` + +*new* property: `mdm_point1` in `CAMediaTimingFunction(MotionInterchangeExtension)` + +*new* category: `CAMediaTimingFunction(MotionInterchangeExtension)` + +*new* property: `mdm_point2` in `CAMediaTimingFunction(MotionInterchangeExtension)` + +#### MDMMotionCurveMakeSpring + +*modified* function: `MDMMotionCurveMakeSpring` + +| Type of change: | Swift declaration | +|---|---| +| From: | `func MotionCurveMakeSpring(mass: Float, tension: Float, friction: Float) -> MotionCurve` | +| To: | `func MotionCurveMakeSpring(mass: CGFloat, tension: CGFloat, friction: CGFloat) -> MotionCurve` | + +*modified* function: `MDMMotionCurveMakeSpring` + +| Type of change: | Declaration | +|---|---| +| From: | `extern MDMMotionCurve MDMMotionCurveMakeSpring(float mass, float tension, float friction)` | +| To: | `extern MDMMotionCurve MDMMotionCurveMakeSpring(CGFloat mass, CGFloat tension, CGFloat friction)` | + +#### MDMAnimationTraits(SystemTraits) + +*new* category: `MDMAnimationTraits(SystemTraits)` + +*new* property: `systemModalMovement` in `MDMAnimationTraits(SystemTraits)` + +#### MDMSpringTimingCurveGenerator + +*new* property: `initialVelocity` in `MDMSpringTimingCurveGenerator` + +*new* class: `MDMSpringTimingCurveGenerator` + +*new* method: `-init` in `MDMSpringTimingCurveGenerator` + +*new* method: `-initWithDuration:dampingRatio:initialVelocity:` in `MDMSpringTimingCurveGenerator` + +*new* property: `dampingRatio` in `MDMSpringTimingCurveGenerator` + +*new* method: `-springTimingCurve` in `MDMSpringTimingCurveGenerator` + +*new* method: `-initWithDuration:dampingRatio:` in `MDMSpringTimingCurveGenerator` + +*new* property: `duration` in `MDMSpringTimingCurveGenerator` + +#### MDMMotionCurveMakeBezier + +*modified* function: `MDMMotionCurveMakeBezier` + +| Type of change: | Swift declaration | +|---|---| +| From: | `func MotionCurveMakeBezier(p1x: Float, p1y: Float, p2x: Float, p2y: Float) -> MotionCurve` | +| To: | `func MotionCurveMakeBezier(p1x: CGFloat, p1y: CGFloat, p2x: CGFloat, p2y: CGFloat) -> MotionCurve` | + +*modified* function: `MDMMotionCurveMakeBezier` + +| Type of change: | Declaration | +|---|---| +| From: | `extern MDMMotionCurve MDMMotionCurveMakeBezier(float p1x, float p1y, float p2x, float p2y)` | +| To: | `extern MDMMotionCurve MDMMotionCurveMakeBezier(CGFloat p1x, CGFloat p1y, CGFloat p2x, CGFloat p2y)` | + +#### MDMRepetition + +*new* method: `-initWithNumberOfRepetitions:autoreverses:` in `MDMRepetition` + +*new* method: `-initWithNumberOfRepetitions:` in `MDMRepetition` + +*new* class: `MDMRepetition` + +*new* property: `numberOfRepetitions` in `MDMRepetition` + +*new* method: `-init` in `MDMRepetition` + +## Non-source changes + +* [Update docs with new API.](https://github.com/material-motion/motion-interchange-objc/commit/c30d90457c7bf6901e684ba932a5dd3b8ecd5fe5) (Jeff Verkoeyen) +* [Fixing travis builds.](https://github.com/material-motion/motion-interchange-objc/commit/a4301e2eb55594408452446b37dec9a13d93ca53) (Jeff Verkoeyen) +* [Use http_archive instead of git_respository as per the bazel team's recommendations. (#29)](https://github.com/material-motion/motion-interchange-objc/commit/0730f1a905421f9a5d75e6015e1bf4d242a76988) (featherless) +* [Remove unused header.](https://github.com/material-motion/motion-interchange-objc/commit/e8af0592da1aa244867089532381f6dbdd801a77) (Jeff Verkoeyen) +* [Iterating on the usage docs.](https://github.com/material-motion/motion-interchange-objc/commit/dcca85ea152d83cbdcf88d74916a228f40c8c2e1) (Jeff Verkoeyen) +* [Revert "Add todo configuration"](https://github.com/material-motion/motion-interchange-objc/commit/41588742c057d79a3a3a7338f867a9622d84e143) (Jeff Verkoeyen) +* [Add todo configuration](https://github.com/material-motion/motion-interchange-objc/commit/d2e1450ac327ebfba49c7489375bd635e9cfc1bb) (featherless) +* [Enable code coverage on travis](https://github.com/material-motion/motion-interchange-objc/commit/111d91ea2da6ee2a8acd73c89664f4b10b6232c1) (featherless) +* [Run tests on Travis.](https://github.com/material-motion/motion-interchange-objc/commit/f56f8a7a75e93a087aadc0f0d5759be13fd43cb7) (Jeff Verkoeyen) +* [Update README.md](https://github.com/material-motion/motion-interchange-objc/commit/3f9405eac1445d0f12cb4fad4ad0a89631ded920) (featherless) +* [Fix minor typo.](https://github.com/material-motion/motion-interchange-objc/commit/671ab1d579fd1d1c228ec266396145bbaf70f996) (Jeff Verkoeyen) +* [Initial pass at fleshing out the readme (#24)](https://github.com/material-motion/motion-interchange-objc/commit/4e0a2e7ad5f258bc450a9dde3281fa7c3752bae6) (featherless) +* [Fix travis.](https://github.com/material-motion/motion-interchange-objc/commit/866ec18cf2353c2682f89d1350af57c05ce25839) (Jeff Verkoeyen) +* [Add missing Info.plist. (#20)](https://github.com/material-motion/motion-interchange-objc/commit/0080128a6846d2eda8538cab260cbecbbe32b9a1) (Sylvain Defresne) # 1.4.0 From fd4f33e9dcfd0150c403a35137557662c8d81fc3 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Wed, 13 Dec 2017 10:13:18 -0500 Subject: [PATCH 27/28] Update CHANGELOG notes. --- CHANGELOG.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 356f5a6..4874616 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,21 @@ -# #develop# +# 1.5.0 -## Breaking changes +This minor release introduces new Objective-C APIs for creating and storing animation traits. ## New deprecations +All of the original C-style APIs for animation timing are now informally deprecated. We will remove these APIs in the future. + ## New features +New Objective-C APIs for storing animation traits. + +| Old API | New API | Rationale | +|:------- |:-------- |:-----------| +| MotionTiming | AnimationTraits | This structure is intended to describe animations only, not motion in general. | +| MotionCurve | TimingCurve | This brings the API name closer to the similarly-purposed `CAMediaTimingFunction`. MotionCurve could also be easily confused with motion through x/y space rather than through time (e.g. ArcMove), which will be problematic as we start defining paths of motion through space. | +| MotionRepetition | RepetitionTraits | This aligns the naming with AnimationTraits. | + ## Source changes * [Restrict subclassing on all types. (#32)](https://github.com/material-motion/motion-interchange-objc/commit/0530ba355d8af640fd9fd51df137c6aed494f63c) (featherless) From a5531c58695b13cffb62ed3a1813def5b2edc818 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Wed, 13 Dec 2017 12:14:43 -0500 Subject: [PATCH 28/28] Bump the release. --- .jazzy.yaml | 4 ++-- MotionInterchange.podspec | 2 +- Podfile.lock | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.jazzy.yaml b/.jazzy.yaml index fb3f97d..5109d14 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -1,8 +1,8 @@ module: MotionInterchange -module_version: 1.4.0 +module_version: 1.5.0 sdk: iphonesimulator umbrella_header: src/MotionInterchange.h objc: true github_url: https://github.com/material-motion/motion-interchange-objc -github_file_prefix: https://github.com/material-motion/motion-interchange-objc/tree/v1.4.0 +github_file_prefix: https://github.com/material-motion/motion-interchange-objc/tree/v1.5.0 diff --git a/MotionInterchange.podspec b/MotionInterchange.podspec index b22cd8f..0c293a4 100644 --- a/MotionInterchange.podspec +++ b/MotionInterchange.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "MotionInterchange" s.summary = "Motion interchange format." - s.version = "1.4.0" + s.version = "1.5.0" s.authors = "The Material Motion Authors" s.license = "Apache 2.0" s.homepage = "https://github.com/material-motion/motion-interchange-objc" diff --git a/Podfile.lock b/Podfile.lock index a5f15b2..5fd06b9 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,6 +1,6 @@ PODS: - CatalogByConvention (2.1.1) - - MotionInterchange (1.4.0) + - MotionInterchange (1.5.0) DEPENDENCIES: - CatalogByConvention @@ -12,7 +12,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: CatalogByConvention: c3a5319de04250a7cd4649127fcfca5fe3322a43 - MotionInterchange: 35e0fd286ceab53dd4ee03494b3fcafa6a70637a + MotionInterchange: 27bef5bbb1e7a59242c38cf2f1ccab83ca569f55 PODFILE CHECKSUM: 09090d12db5aab00a13fe82da94f338ebd03f5dc