Permalink
Please sign in to comment.
Showing
with
8,126 additions
and 0 deletions.
- +65 −0 .gitignore
- +931 −0 Advance.xcodeproj/project.pbxproj
- +7 −0 Advance.xcodeproj/project.xcworkspace/contents.xcworkspacedata
- +99 −0 Advance.xcodeproj/xcshareddata/xcschemes/Advance.xcscheme
- +92 −0 Advance.xcodeproj/xcshareddata/xcschemes/AdvanceSample.xcscheme
- +152 −0 Advance/Animatable/Animatable.swift
- +38 −0 Advance/Animation/Advanceable.swift
- +40 −0 Advance/Animation/AnimationType.swift
- +67 −0 Advance/Animation/AnyValueAnimation.swift
- +141 −0 Advance/Animation/Basic/BasicAnimation.swift
- +115 −0 Advance/Animation/Basic/TimingFunctionType.swift
- +196 −0 Advance/Animation/Basic/UnitBezier.swift
- +120 −0 Advance/Animation/Decay/DecayAnimation.swift
- +131 −0 Advance/Animation/Spring/SpringAnimation.swift
- +43 −0 Advance/Animation/ValueAnimationType.swift
- +197 −0 Advance/Animator/Animator.swift
- +85 −0 Advance/Animator/AnimatorContext.swift
- +145 −0 Advance/Events/Event.swift
- +51 −0 Advance/Extensions/CAMediaTimingFunction.swift
- +26 −0 Advance/Info.plist
- +169 −0 Advance/Loop/Loop.swift
- +58 −0 Advance/Simulation/DecayFunction.swift
- +53 −0 Advance/Simulation/DynamicFunctionType.swift
- +216 −0 Advance/Simulation/DynamicSimulation.swift
- +126 −0 Advance/Simulation/Spring.swift
- +93 −0 Advance/Simulation/SpringFunction.swift
- +46 −0 Advance/VectorConvertible/CGFloat+VectorConvertible.swift
- +46 −0 Advance/VectorConvertible/CGPoint+VectorConvertible.swift
- +47 −0 Advance/VectorConvertible/CGRect+VectorConvertible.swift
- +46 −0 Advance/VectorConvertible/CGSize+VectorConvertible.swift
- +46 −0 Advance/VectorConvertible/CGVector+VectorConvertible.swift
- +44 −0 Advance/VectorConvertible/Double+VectorConvertible.swift
- +73 −0 Advance/VectorConvertible/VectorConvertible.swift
- +43 −0 Advance/Vectors/Interpolatable.swift
- +71 −0 Advance/Vectors/Vector1.swift
- +153 −0 Advance/Vectors/Vector2.swift
- +164 −0 Advance/Vectors/Vector3.swift
- +175 −0 Advance/Vectors/Vector4.swift
- +60 −0 Advance/Vectors/VectorMathCapable.swift
- +79 −0 Advance/Vectors/VectorType.swift
- +300 −0 AdvanceSample/ActivityView.swift
- +86 −0 AdvanceSample/ActivityViewController.swift
- +77 −0 AdvanceSample/AppDelegate.swift
- +38 −0 AdvanceSample/Assets.xcassets/AppIcon.appiconset/Contents.json
- +6 −0 AdvanceSample/Assets.xcassets/Contents.json
- +21 −0 AdvanceSample/Assets.xcassets/background-blurred.imageset/Contents.json
- BIN AdvanceSample/Assets.xcassets/background-blurred.imageset/background-blurred.jpg
- +21 −0 AdvanceSample/Assets.xcassets/background.imageset/Contents.json
- BIN AdvanceSample/Assets.xcassets/background.imageset/background.jpg
- +15 −0 AdvanceSample/Assets.xcassets/logo.imageset/Contents.json
- BIN AdvanceSample/Assets.xcassets/logo.imageset/logo.pdf
- +27 −0 AdvanceSample/Base.lproj/LaunchScreen.storyboard
- +509 −0 AdvanceSample/BrowserView.swift
- +150 −0 AdvanceSample/BrowserViewController.swift
- +76 −0 AdvanceSample/CoverView.swift
- +128 −0 AdvanceSample/DemoViewController.swift
- +360 −0 AdvanceSample/DirectManipulationGestureRecognizer.swift
- +126 −0 AdvanceSample/GestureView.swift
- +84 −0 AdvanceSample/GesturesViewController.swift
- +81 −0 AdvanceSample/GravityFunction.swift
- +181 −0 AdvanceSample/GravitySimulation.swift
- +144 −0 AdvanceSample/GravityViewController.swift
- +38 −0 AdvanceSample/Info.plist
- +68 −0 AdvanceSample/SimpleTransform.swift
- +166 −0 AdvanceSample/SpringConfigurationView.swift
- +61 −0 AdvanceSample/SpringView.swift
- +113 −0 AdvanceSample/SpringsViewController.swift
- +34 −0 AdvanceTests/BasicAnimationTests.swift
- +20 −0 AdvanceTests/CAMediaTimingFunctionTests.swift
- +48 −0 AdvanceTests/EventTests.swift
- +24 −0 AdvanceTests/Info.plist
- +29 −0 AdvanceTests/UnitBezierTests.swift
- +47 −0 AdvanceTests/VectorConvenienceTests.swift
- +113 −0 AdvanceTests/VectorTypeTests.swift
- BIN Assets/logo.gif
- BIN Assets/logo.png
- BIN Assets/nav.gif
- +23 −0 LICENSE
- +3 −0 Package.swift
- +23 −0 Playground.playground/Pages/Animatable.xcplaygroundpage/Contents.swift
- +19 −0 Playground.playground/Pages/BasicAnimation.xcplaygroundpage/Contents.swift
- +33 −0 Playground.playground/Pages/Dynamics.xcplaygroundpage/Contents.swift
- +16 −0 Playground.playground/Pages/Simple.xcplaygroundpage/Contents.swift
- +24 −0 Playground.playground/Pages/Spring.xcplaygroundpage/Contents.swift
- +10 −0 Playground.playground/contents.xcplayground
- +235 −0 README.md
65
.gitignore
@@ -0,0 +1,65 @@ | ||
+# Xcode | ||
+# | ||
+# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore | ||
+ | ||
+.DS_Store | ||
+ | ||
+## Build generated | ||
+build/ | ||
+DerivedData | ||
+ | ||
+## Various settings | ||
+*.pbxuser | ||
+!default.pbxuser | ||
+*.mode1v3 | ||
+!default.mode1v3 | ||
+*.mode2v3 | ||
+!default.mode2v3 | ||
+*.perspectivev3 | ||
+!default.perspectivev3 | ||
+xcuserdata | ||
+ | ||
+## Other | ||
+*.xccheckout | ||
+*.moved-aside | ||
+*.xcuserstate | ||
+*.xcscmblueprint | ||
+ | ||
+## Obj-C/Swift specific | ||
+*.hmap | ||
+*.ipa | ||
+ | ||
+## Playgrounds | ||
+timeline.xctimeline | ||
+playground.xcworkspace | ||
+ | ||
+# Swift Package Manager | ||
+# | ||
+# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. | ||
+# Packages/ | ||
+.build/ | ||
+ | ||
+# CocoaPods | ||
+# | ||
+# We recommend against adding the Pods directory to your .gitignore. However | ||
+# you should judge for yourself, the pros and cons are mentioned at: | ||
+# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control | ||
+# | ||
+# Pods/ | ||
+ | ||
+# Carthage | ||
+# | ||
+# Add this line if you want to avoid checking in source code from Carthage dependencies. | ||
+# Carthage/Checkouts | ||
+ | ||
+Carthage/Build | ||
+ | ||
+# fastlane | ||
+# | ||
+# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the | ||
+# screenshots whenever they are needed. | ||
+# For more information about the recommended setup visit: | ||
+# https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md | ||
+ | ||
+fastlane/report.xml | ||
+fastlane/screenshots |
931
Advance.xcodeproj/project.pbxproj
@@ -0,0 +1,931 @@ | ||
+// !$*UTF8*$! | ||
+{ | ||
+ archiveVersion = 1; | ||
+ classes = { | ||
+ }; | ||
+ objectVersion = 46; | ||
+ objects = { | ||
+ | ||
+/* Begin PBXBuildFile section */ | ||
+ 2212A2F11C82702500BB79F4 /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2212A2F01C82702500BB79F4 /* ActivityView.swift */; }; | ||
+ 22142E651C810D86000C6DEB /* ActivityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22142E641C810D86000C6DEB /* ActivityViewController.swift */; }; | ||
+ 22142E671C810DA7000C6DEB /* GravityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22142E661C810DA7000C6DEB /* GravityViewController.swift */; }; | ||
+ 22168B5B1C7A547300BAE519 /* VectorTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22168B5A1C7A547300BAE519 /* VectorTypeTests.swift */; }; | ||
+ 22168B5D1C7A5E3500BAE519 /* BasicAnimationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22168B5C1C7A5E3500BAE519 /* BasicAnimationTests.swift */; }; | ||
+ 222E211D1C7D4C300063FAA2 /* SimpleTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 222E211C1C7D4C300063FAA2 /* SimpleTransform.swift */; }; | ||
+ 222E211F1C7D8CF00063FAA2 /* SpringConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 222E211E1C7D8CF00063FAA2 /* SpringConfigurationView.swift */; }; | ||
+ 222E21211C7E44660063FAA2 /* BrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 222E21201C7E44660063FAA2 /* BrowserViewController.swift */; }; | ||
+ 222E21231C7E448D0063FAA2 /* DemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 222E21221C7E448D0063FAA2 /* DemoViewController.swift */; }; | ||
+ 222E21251C7E45420063FAA2 /* BrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 222E21241C7E45420063FAA2 /* BrowserView.swift */; }; | ||
+ 22319B721C8110A300D4E027 /* CoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22319B711C8110A300D4E027 /* CoverView.swift */; }; | ||
+ 223E49731C76C9430048CAB6 /* SpringsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223E49721C76C9430048CAB6 /* SpringsViewController.swift */; }; | ||
+ 223E49751C76C9510048CAB6 /* SpringView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223E49741C76C9510048CAB6 /* SpringView.swift */; }; | ||
+ 226A752C1C84153E00A7F1FB /* GravitySimulation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226A752B1C84153E00A7F1FB /* GravitySimulation.swift */; }; | ||
+ 226A752E1C84206D00A7F1FB /* GravityFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226A752D1C84206D00A7F1FB /* GravityFunction.swift */; }; | ||
+ 2299A6831C7D35FE009DE78C /* DirectManipulationGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2299A6821C7D35FE009DE78C /* DirectManipulationGestureRecognizer.swift */; }; | ||
+ 22B8E9831C7713FF0010EF5A /* Advance.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5D39CEE11C6E983100DFCF86 /* Advance.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; | ||
+ 22F74AA81C7674BB004C6E4C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22F74AA71C7674BB004C6E4C /* AppDelegate.swift */; }; | ||
+ 22F74AAF1C7674BB004C6E4C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 22F74AAE1C7674BB004C6E4C /* Assets.xcassets */; }; | ||
+ 22F74AB21C7674BB004C6E4C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 22F74AB01C7674BB004C6E4C /* LaunchScreen.storyboard */; }; | ||
+ 22F74AB81C76769C004C6E4C /* GesturesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22F74AB71C76769C004C6E4C /* GesturesViewController.swift */; }; | ||
+ 22F74ABB1C767757004C6E4C /* GestureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22F74ABA1C767757004C6E4C /* GestureView.swift */; }; | ||
+ 22F74ABC1C76778E004C6E4C /* Advance.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D39CEE11C6E983100DFCF86 /* Advance.framework */; }; | ||
+ 5D39CEEC1C6E983100DFCF86 /* Advance.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D39CEE11C6E983100DFCF86 /* Advance.framework */; }; | ||
+ 5DA94F481C80DAA00039B9BB /* VectorConvenienceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA94F471C80DAA00039B9BB /* VectorConvenienceTests.swift */; }; | ||
+ 5DA94F4B1C80E2700039B9BB /* UnitBezierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA94F4A1C80E2700039B9BB /* UnitBezierTests.swift */; }; | ||
+ 5DA94F4D1C8112810039B9BB /* CAMediaTimingFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA94F4C1C8112810039B9BB /* CAMediaTimingFunctionTests.swift */; }; | ||
+ 5DA94F4F1C8114680039B9BB /* EventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA94F4E1C8114680039B9BB /* EventTests.swift */; }; | ||
+ 5DB1A4911C876AD1000CF9EE /* Animatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4631C876AD1000CF9EE /* Animatable.swift */; }; | ||
+ 5DB1A4921C876AD1000CF9EE /* Advanceable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4651C876AD1000CF9EE /* Advanceable.swift */; }; | ||
+ 5DB1A4931C876AD1000CF9EE /* AnimationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4661C876AD1000CF9EE /* AnimationType.swift */; }; | ||
+ 5DB1A4941C876AD1000CF9EE /* AnyValueAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4671C876AD1000CF9EE /* AnyValueAnimation.swift */; }; | ||
+ 5DB1A4951C876AD1000CF9EE /* BasicAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4691C876AD1000CF9EE /* BasicAnimation.swift */; }; | ||
+ 5DB1A4961C876AD1000CF9EE /* TimingFunctionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A46A1C876AD1000CF9EE /* TimingFunctionType.swift */; }; | ||
+ 5DB1A4971C876AD1000CF9EE /* UnitBezier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A46B1C876AD1000CF9EE /* UnitBezier.swift */; }; | ||
+ 5DB1A4981C876AD1000CF9EE /* DecayAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A46D1C876AD1000CF9EE /* DecayAnimation.swift */; }; | ||
+ 5DB1A4991C876AD1000CF9EE /* SpringAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A46F1C876AD1000CF9EE /* SpringAnimation.swift */; }; | ||
+ 5DB1A49A1C876AD1000CF9EE /* ValueAnimationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4701C876AD1000CF9EE /* ValueAnimationType.swift */; }; | ||
+ 5DB1A49B1C876AD1000CF9EE /* Animator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4721C876AD1000CF9EE /* Animator.swift */; }; | ||
+ 5DB1A49C1C876AD1000CF9EE /* AnimatorContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4731C876AD1000CF9EE /* AnimatorContext.swift */; }; | ||
+ 5DB1A49D1C876AD1000CF9EE /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4751C876AD1000CF9EE /* Event.swift */; }; | ||
+ 5DB1A49E1C876AD1000CF9EE /* CAMediaTimingFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4771C876AD1000CF9EE /* CAMediaTimingFunction.swift */; }; | ||
+ 5DB1A4A01C876AD1000CF9EE /* Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A47A1C876AD1000CF9EE /* Loop.swift */; }; | ||
+ 5DB1A4A11C876AD1000CF9EE /* DecayFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A47C1C876AD1000CF9EE /* DecayFunction.swift */; }; | ||
+ 5DB1A4A21C876AD1000CF9EE /* DynamicFunctionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A47D1C876AD1000CF9EE /* DynamicFunctionType.swift */; }; | ||
+ 5DB1A4A31C876AD1000CF9EE /* DynamicSimulation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A47E1C876AD1000CF9EE /* DynamicSimulation.swift */; }; | ||
+ 5DB1A4A41C876AD1000CF9EE /* Spring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A47F1C876AD1000CF9EE /* Spring.swift */; }; | ||
+ 5DB1A4A51C876AD1000CF9EE /* SpringFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4801C876AD1000CF9EE /* SpringFunction.swift */; }; | ||
+ 5DB1A4A61C876AD1000CF9EE /* CGFloat+VectorConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4821C876AD1000CF9EE /* CGFloat+VectorConvertible.swift */; }; | ||
+ 5DB1A4A71C876AD1000CF9EE /* CGPoint+VectorConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4831C876AD1000CF9EE /* CGPoint+VectorConvertible.swift */; }; | ||
+ 5DB1A4A81C876AD1000CF9EE /* CGRect+VectorConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4841C876AD1000CF9EE /* CGRect+VectorConvertible.swift */; }; | ||
+ 5DB1A4A91C876AD1000CF9EE /* CGSize+VectorConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4851C876AD1000CF9EE /* CGSize+VectorConvertible.swift */; }; | ||
+ 5DB1A4AA1C876AD1000CF9EE /* CGVector+VectorConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4861C876AD1000CF9EE /* CGVector+VectorConvertible.swift */; }; | ||
+ 5DB1A4AB1C876AD1000CF9EE /* Double+VectorConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4871C876AD1000CF9EE /* Double+VectorConvertible.swift */; }; | ||
+ 5DB1A4AC1C876AD1000CF9EE /* VectorConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4881C876AD1000CF9EE /* VectorConvertible.swift */; }; | ||
+ 5DB1A4AD1C876AD1000CF9EE /* Interpolatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A48A1C876AD1000CF9EE /* Interpolatable.swift */; }; | ||
+ 5DB1A4AE1C876AD1000CF9EE /* Vector1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A48B1C876AD1000CF9EE /* Vector1.swift */; }; | ||
+ 5DB1A4AF1C876AD1000CF9EE /* Vector2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A48C1C876AD1000CF9EE /* Vector2.swift */; }; | ||
+ 5DB1A4B01C876AD1000CF9EE /* Vector3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A48D1C876AD1000CF9EE /* Vector3.swift */; }; | ||
+ 5DB1A4B11C876AD1000CF9EE /* Vector4.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A48E1C876AD1000CF9EE /* Vector4.swift */; }; | ||
+ 5DB1A4B21C876AD1000CF9EE /* VectorMathCapable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A48F1C876AD1000CF9EE /* VectorMathCapable.swift */; }; | ||
+ 5DB1A4B31C876AD1000CF9EE /* VectorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB1A4901C876AD1000CF9EE /* VectorType.swift */; }; | ||
+/* End PBXBuildFile section */ | ||
+ | ||
+/* Begin PBXContainerItemProxy section */ | ||
+ 22168B501C7A42B100BAE519 /* PBXContainerItemProxy */ = { | ||
+ isa = PBXContainerItemProxy; | ||
+ containerPortal = 5D39CED81C6E983100DFCF86 /* Project object */; | ||
+ proxyType = 1; | ||
+ remoteGlobalIDString = 22F74AA41C7674BB004C6E4C; | ||
+ remoteInfo = AnimationSample; | ||
+ }; | ||
+ 22F74ABD1C767797004C6E4C /* PBXContainerItemProxy */ = { | ||
+ isa = PBXContainerItemProxy; | ||
+ containerPortal = 5D39CED81C6E983100DFCF86 /* Project object */; | ||
+ proxyType = 1; | ||
+ remoteGlobalIDString = 5D39CEE01C6E983100DFCF86; | ||
+ remoteInfo = Animation; | ||
+ }; | ||
+ 5D39CEED1C6E983100DFCF86 /* PBXContainerItemProxy */ = { | ||
+ isa = PBXContainerItemProxy; | ||
+ containerPortal = 5D39CED81C6E983100DFCF86 /* Project object */; | ||
+ proxyType = 1; | ||
+ remoteGlobalIDString = 5D39CEE01C6E983100DFCF86; | ||
+ remoteInfo = Advance; | ||
+ }; | ||
+/* End PBXContainerItemProxy section */ | ||
+ | ||
+/* Begin PBXCopyFilesBuildPhase section */ | ||
+ 22B8E9821C7713F40010EF5A /* CopyFiles */ = { | ||
+ isa = PBXCopyFilesBuildPhase; | ||
+ buildActionMask = 2147483647; | ||
+ dstPath = ""; | ||
+ dstSubfolderSpec = 10; | ||
+ files = ( | ||
+ 22B8E9831C7713FF0010EF5A /* Advance.framework in CopyFiles */, | ||
+ ); | ||
+ runOnlyForDeploymentPostprocessing = 0; | ||
+ }; | ||
+/* End PBXCopyFilesBuildPhase section */ | ||
+ | ||
+/* Begin PBXFileReference section */ | ||
+ 2212A2F01C82702500BB79F4 /* ActivityView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = "<group>"; }; | ||
+ 22142E641C810D86000C6DEB /* ActivityViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityViewController.swift; sourceTree = "<group>"; }; | ||
+ 22142E661C810DA7000C6DEB /* GravityViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GravityViewController.swift; sourceTree = "<group>"; }; | ||
+ 22168B5A1C7A547300BAE519 /* VectorTypeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VectorTypeTests.swift; sourceTree = "<group>"; }; | ||
+ 22168B5C1C7A5E3500BAE519 /* BasicAnimationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicAnimationTests.swift; sourceTree = "<group>"; }; | ||
+ 222E211C1C7D4C300063FAA2 /* SimpleTransform.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleTransform.swift; sourceTree = "<group>"; }; | ||
+ 222E211E1C7D8CF00063FAA2 /* SpringConfigurationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpringConfigurationView.swift; sourceTree = "<group>"; }; | ||
+ 222E21201C7E44660063FAA2 /* BrowserViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserViewController.swift; sourceTree = "<group>"; }; | ||
+ 222E21221C7E448D0063FAA2 /* DemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoViewController.swift; sourceTree = "<group>"; }; | ||
+ 222E21241C7E45420063FAA2 /* BrowserView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserView.swift; sourceTree = "<group>"; }; | ||
+ 22319B711C8110A300D4E027 /* CoverView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoverView.swift; sourceTree = "<group>"; }; | ||
+ 223E49721C76C9430048CAB6 /* SpringsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpringsViewController.swift; sourceTree = "<group>"; }; | ||
+ 223E49741C76C9510048CAB6 /* SpringView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpringView.swift; sourceTree = "<group>"; }; | ||
+ 226A752B1C84153E00A7F1FB /* GravitySimulation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GravitySimulation.swift; sourceTree = "<group>"; }; | ||
+ 226A752D1C84206D00A7F1FB /* GravityFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GravityFunction.swift; sourceTree = "<group>"; }; | ||
+ 2299A6821C7D35FE009DE78C /* DirectManipulationGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectManipulationGestureRecognizer.swift; sourceTree = "<group>"; }; | ||
+ 22F74AA51C7674BB004C6E4C /* AdvanceSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AdvanceSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; | ||
+ 22F74AA71C7674BB004C6E4C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; | ||
+ 22F74AAE1C7674BB004C6E4C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; | ||
+ 22F74AB11C7674BB004C6E4C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; | ||
+ 22F74AB31C7674BB004C6E4C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; | ||
+ 22F74AB71C76769C004C6E4C /* GesturesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GesturesViewController.swift; sourceTree = "<group>"; }; | ||
+ 22F74ABA1C767757004C6E4C /* GestureView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GestureView.swift; sourceTree = "<group>"; }; | ||
+ 5D39CEE11C6E983100DFCF86 /* Advance.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Advance.framework; sourceTree = BUILT_PRODUCTS_DIR; }; | ||
+ 5D39CEEB1C6E983100DFCF86 /* Advance.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Advance.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; | ||
+ 5D39CEF21C6E983100DFCF86 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; | ||
+ 5D39CF101C6E9B1F00DFCF86 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; | ||
+ 5DA94F471C80DAA00039B9BB /* VectorConvenienceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VectorConvenienceTests.swift; sourceTree = "<group>"; }; | ||
+ 5DA94F491C80DEEB0039B9BB /* Playground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Playground.playground; sourceTree = "<group>"; }; | ||
+ 5DA94F4A1C80E2700039B9BB /* UnitBezierTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnitBezierTests.swift; sourceTree = "<group>"; }; | ||
+ 5DA94F4C1C8112810039B9BB /* CAMediaTimingFunctionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAMediaTimingFunctionTests.swift; sourceTree = "<group>"; }; | ||
+ 5DA94F4E1C8114680039B9BB /* EventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventTests.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A4631C876AD1000CF9EE /* Animatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Animatable.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A4651C876AD1000CF9EE /* Advanceable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Advanceable.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A4661C876AD1000CF9EE /* AnimationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationType.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A4671C876AD1000CF9EE /* AnyValueAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyValueAnimation.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A4691C876AD1000CF9EE /* BasicAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicAnimation.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A46A1C876AD1000CF9EE /* TimingFunctionType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimingFunctionType.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A46B1C876AD1000CF9EE /* UnitBezier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnitBezier.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A46D1C876AD1000CF9EE /* DecayAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DecayAnimation.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A46F1C876AD1000CF9EE /* SpringAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpringAnimation.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A4701C876AD1000CF9EE /* ValueAnimationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueAnimationType.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A4721C876AD1000CF9EE /* Animator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Animator.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A4731C876AD1000CF9EE /* AnimatorContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimatorContext.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A4751C876AD1000CF9EE /* Event.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Event.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A4771C876AD1000CF9EE /* CAMediaTimingFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAMediaTimingFunction.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A47A1C876AD1000CF9EE /* Loop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Loop.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A47C1C876AD1000CF9EE /* DecayFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DecayFunction.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A47D1C876AD1000CF9EE /* DynamicFunctionType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicFunctionType.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A47E1C876AD1000CF9EE /* DynamicSimulation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicSimulation.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A47F1C876AD1000CF9EE /* Spring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Spring.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A4801C876AD1000CF9EE /* SpringFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpringFunction.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A4821C876AD1000CF9EE /* CGFloat+VectorConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGFloat+VectorConvertible.swift"; sourceTree = "<group>"; }; | ||
+ 5DB1A4831C876AD1000CF9EE /* CGPoint+VectorConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGPoint+VectorConvertible.swift"; sourceTree = "<group>"; }; | ||
+ 5DB1A4841C876AD1000CF9EE /* CGRect+VectorConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGRect+VectorConvertible.swift"; sourceTree = "<group>"; }; | ||
+ 5DB1A4851C876AD1000CF9EE /* CGSize+VectorConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGSize+VectorConvertible.swift"; sourceTree = "<group>"; }; | ||
+ 5DB1A4861C876AD1000CF9EE /* CGVector+VectorConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGVector+VectorConvertible.swift"; sourceTree = "<group>"; }; | ||
+ 5DB1A4871C876AD1000CF9EE /* Double+VectorConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Double+VectorConvertible.swift"; sourceTree = "<group>"; }; | ||
+ 5DB1A4881C876AD1000CF9EE /* VectorConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VectorConvertible.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A48A1C876AD1000CF9EE /* Interpolatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Interpolatable.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A48B1C876AD1000CF9EE /* Vector1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vector1.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A48C1C876AD1000CF9EE /* Vector2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vector2.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A48D1C876AD1000CF9EE /* Vector3.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vector3.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A48E1C876AD1000CF9EE /* Vector4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vector4.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A48F1C876AD1000CF9EE /* VectorMathCapable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VectorMathCapable.swift; sourceTree = "<group>"; }; | ||
+ 5DB1A4901C876AD1000CF9EE /* VectorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VectorType.swift; sourceTree = "<group>"; }; | ||
+/* End PBXFileReference section */ | ||
+ | ||
+/* Begin PBXFrameworksBuildPhase section */ | ||
+ 22F74AA21C7674BB004C6E4C /* Frameworks */ = { | ||
+ isa = PBXFrameworksBuildPhase; | ||
+ buildActionMask = 2147483647; | ||
+ files = ( | ||
+ 22F74ABC1C76778E004C6E4C /* Advance.framework in Frameworks */, | ||
+ ); | ||
+ runOnlyForDeploymentPostprocessing = 0; | ||
+ }; | ||
+ 5D39CEDD1C6E983100DFCF86 /* Frameworks */ = { | ||
+ isa = PBXFrameworksBuildPhase; | ||
+ buildActionMask = 2147483647; | ||
+ files = ( | ||
+ ); | ||
+ runOnlyForDeploymentPostprocessing = 0; | ||
+ }; | ||
+ 5D39CEE81C6E983100DFCF86 /* Frameworks */ = { | ||
+ isa = PBXFrameworksBuildPhase; | ||
+ buildActionMask = 2147483647; | ||
+ files = ( | ||
+ 5D39CEEC1C6E983100DFCF86 /* Advance.framework in Frameworks */, | ||
+ ); | ||
+ runOnlyForDeploymentPostprocessing = 0; | ||
+ }; | ||
+/* End PBXFrameworksBuildPhase section */ | ||
+ | ||
+/* Begin PBXGroup section */ | ||
+ 22142E631C810D6C000C6DEB /* Activity */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 22142E641C810D86000C6DEB /* ActivityViewController.swift */, | ||
+ 2212A2F01C82702500BB79F4 /* ActivityView.swift */, | ||
+ ); | ||
+ name = Activity; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 22142E681C810DAC000C6DEB /* Gravity */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 22142E661C810DA7000C6DEB /* GravityViewController.swift */, | ||
+ 226A752B1C84153E00A7F1FB /* GravitySimulation.swift */, | ||
+ 226A752D1C84206D00A7F1FB /* GravityFunction.swift */, | ||
+ ); | ||
+ name = Gravity; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 223E49711C76C9350048CAB6 /* Springs */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 223E49721C76C9430048CAB6 /* SpringsViewController.swift */, | ||
+ 222E211E1C7D8CF00063FAA2 /* SpringConfigurationView.swift */, | ||
+ 223E49741C76C9510048CAB6 /* SpringView.swift */, | ||
+ ); | ||
+ name = Springs; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 22F74AA61C7674BB004C6E4C /* AdvanceSample */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 22142E681C810DAC000C6DEB /* Gravity */, | ||
+ 22142E631C810D6C000C6DEB /* Activity */, | ||
+ 223E49711C76C9350048CAB6 /* Springs */, | ||
+ 22F74AB91C767740004C6E4C /* Gestures */, | ||
+ 22F74AA71C7674BB004C6E4C /* AppDelegate.swift */, | ||
+ 222E21201C7E44660063FAA2 /* BrowserViewController.swift */, | ||
+ 222E21241C7E45420063FAA2 /* BrowserView.swift */, | ||
+ 222E21221C7E448D0063FAA2 /* DemoViewController.swift */, | ||
+ 22319B711C8110A300D4E027 /* CoverView.swift */, | ||
+ 22F74AAE1C7674BB004C6E4C /* Assets.xcassets */, | ||
+ 22F74AB01C7674BB004C6E4C /* LaunchScreen.storyboard */, | ||
+ 22F74AB31C7674BB004C6E4C /* Info.plist */, | ||
+ ); | ||
+ path = AdvanceSample; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 22F74AB91C767740004C6E4C /* Gestures */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 22F74AB71C76769C004C6E4C /* GesturesViewController.swift */, | ||
+ 22F74ABA1C767757004C6E4C /* GestureView.swift */, | ||
+ 2299A6821C7D35FE009DE78C /* DirectManipulationGestureRecognizer.swift */, | ||
+ 222E211C1C7D4C300063FAA2 /* SimpleTransform.swift */, | ||
+ ); | ||
+ name = Gestures; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 5D39CED71C6E983100DFCF86 = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 5DA94F491C80DEEB0039B9BB /* Playground.playground */, | ||
+ 5DB1A4611C876AD1000CF9EE /* Advance */, | ||
+ 5D39CF281C6E9B2E00DFCF86 /* Resources */, | ||
+ 5D39CEEF1C6E983100DFCF86 /* AdvanceTests */, | ||
+ 22F74AA61C7674BB004C6E4C /* AdvanceSample */, | ||
+ 5D39CEE21C6E983100DFCF86 /* Products */, | ||
+ ); | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 5D39CEE21C6E983100DFCF86 /* Products */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 5D39CEE11C6E983100DFCF86 /* Advance.framework */, | ||
+ 5D39CEEB1C6E983100DFCF86 /* Advance.xctest */, | ||
+ 22F74AA51C7674BB004C6E4C /* AdvanceSample.app */, | ||
+ ); | ||
+ name = Products; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 5D39CEEF1C6E983100DFCF86 /* AdvanceTests */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 5DA94F4E1C8114680039B9BB /* EventTests.swift */, | ||
+ 22168B5A1C7A547300BAE519 /* VectorTypeTests.swift */, | ||
+ 5DA94F471C80DAA00039B9BB /* VectorConvenienceTests.swift */, | ||
+ 22168B5C1C7A5E3500BAE519 /* BasicAnimationTests.swift */, | ||
+ 5DA94F4A1C80E2700039B9BB /* UnitBezierTests.swift */, | ||
+ 5DA94F4C1C8112810039B9BB /* CAMediaTimingFunctionTests.swift */, | ||
+ 5D39CEF21C6E983100DFCF86 /* Info.plist */, | ||
+ ); | ||
+ path = AdvanceTests; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 5D39CF281C6E9B2E00DFCF86 /* Resources */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 5D39CF101C6E9B1F00DFCF86 /* Info.plist */, | ||
+ ); | ||
+ name = Resources; | ||
+ path = Advance; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 5DB1A4611C876AD1000CF9EE /* Advance */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 5DB1A4621C876AD1000CF9EE /* Animatable */, | ||
+ 5DB1A4641C876AD1000CF9EE /* Animation */, | ||
+ 5DB1A4711C876AD1000CF9EE /* Animator */, | ||
+ 5DB1A4741C876AD1000CF9EE /* Events */, | ||
+ 5DB1A4761C876AD1000CF9EE /* Extensions */, | ||
+ 5DB1A4791C876AD1000CF9EE /* Loop */, | ||
+ 5DB1A47B1C876AD1000CF9EE /* Simulation */, | ||
+ 5DB1A4811C876AD1000CF9EE /* VectorConvertible */, | ||
+ 5DB1A4891C876AD1000CF9EE /* Vectors */, | ||
+ ); | ||
+ path = Advance; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 5DB1A4621C876AD1000CF9EE /* Animatable */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 5DB1A4631C876AD1000CF9EE /* Animatable.swift */, | ||
+ ); | ||
+ path = Animatable; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 5DB1A4641C876AD1000CF9EE /* Animation */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 5DB1A4681C876AD1000CF9EE /* Basic */, | ||
+ 5DB1A46C1C876AD1000CF9EE /* Decay */, | ||
+ 5DB1A46E1C876AD1000CF9EE /* Spring */, | ||
+ 5DB1A4661C876AD1000CF9EE /* AnimationType.swift */, | ||
+ 5DB1A4671C876AD1000CF9EE /* AnyValueAnimation.swift */, | ||
+ 5DB1A4701C876AD1000CF9EE /* ValueAnimationType.swift */, | ||
+ 5DB1A4651C876AD1000CF9EE /* Advanceable.swift */, | ||
+ ); | ||
+ path = Animation; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 5DB1A4681C876AD1000CF9EE /* Basic */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 5DB1A4691C876AD1000CF9EE /* BasicAnimation.swift */, | ||
+ 5DB1A46A1C876AD1000CF9EE /* TimingFunctionType.swift */, | ||
+ 5DB1A46B1C876AD1000CF9EE /* UnitBezier.swift */, | ||
+ ); | ||
+ path = Basic; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 5DB1A46C1C876AD1000CF9EE /* Decay */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 5DB1A46D1C876AD1000CF9EE /* DecayAnimation.swift */, | ||
+ ); | ||
+ path = Decay; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 5DB1A46E1C876AD1000CF9EE /* Spring */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 5DB1A46F1C876AD1000CF9EE /* SpringAnimation.swift */, | ||
+ ); | ||
+ path = Spring; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 5DB1A4711C876AD1000CF9EE /* Animator */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 5DB1A4721C876AD1000CF9EE /* Animator.swift */, | ||
+ 5DB1A4731C876AD1000CF9EE /* AnimatorContext.swift */, | ||
+ ); | ||
+ path = Animator; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 5DB1A4741C876AD1000CF9EE /* Events */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 5DB1A4751C876AD1000CF9EE /* Event.swift */, | ||
+ ); | ||
+ path = Events; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 5DB1A4761C876AD1000CF9EE /* Extensions */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 5DB1A4771C876AD1000CF9EE /* CAMediaTimingFunction.swift */, | ||
+ ); | ||
+ path = Extensions; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 5DB1A4791C876AD1000CF9EE /* Loop */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 5DB1A47A1C876AD1000CF9EE /* Loop.swift */, | ||
+ ); | ||
+ path = Loop; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 5DB1A47B1C876AD1000CF9EE /* Simulation */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 5DB1A47C1C876AD1000CF9EE /* DecayFunction.swift */, | ||
+ 5DB1A47D1C876AD1000CF9EE /* DynamicFunctionType.swift */, | ||
+ 5DB1A47E1C876AD1000CF9EE /* DynamicSimulation.swift */, | ||
+ 5DB1A47F1C876AD1000CF9EE /* Spring.swift */, | ||
+ 5DB1A4801C876AD1000CF9EE /* SpringFunction.swift */, | ||
+ ); | ||
+ path = Simulation; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 5DB1A4811C876AD1000CF9EE /* VectorConvertible */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 5DB1A4881C876AD1000CF9EE /* VectorConvertible.swift */, | ||
+ 5DB1A4871C876AD1000CF9EE /* Double+VectorConvertible.swift */, | ||
+ 5DB1A4821C876AD1000CF9EE /* CGFloat+VectorConvertible.swift */, | ||
+ 5DB1A4831C876AD1000CF9EE /* CGPoint+VectorConvertible.swift */, | ||
+ 5DB1A4841C876AD1000CF9EE /* CGRect+VectorConvertible.swift */, | ||
+ 5DB1A4851C876AD1000CF9EE /* CGSize+VectorConvertible.swift */, | ||
+ 5DB1A4861C876AD1000CF9EE /* CGVector+VectorConvertible.swift */, | ||
+ ); | ||
+ path = VectorConvertible; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+ 5DB1A4891C876AD1000CF9EE /* Vectors */ = { | ||
+ isa = PBXGroup; | ||
+ children = ( | ||
+ 5DB1A48B1C876AD1000CF9EE /* Vector1.swift */, | ||
+ 5DB1A48C1C876AD1000CF9EE /* Vector2.swift */, | ||
+ 5DB1A48D1C876AD1000CF9EE /* Vector3.swift */, | ||
+ 5DB1A48E1C876AD1000CF9EE /* Vector4.swift */, | ||
+ 5DB1A48F1C876AD1000CF9EE /* VectorMathCapable.swift */, | ||
+ 5DB1A4901C876AD1000CF9EE /* VectorType.swift */, | ||
+ 5DB1A48A1C876AD1000CF9EE /* Interpolatable.swift */, | ||
+ ); | ||
+ path = Vectors; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+/* End PBXGroup section */ | ||
+ | ||
+/* Begin PBXHeadersBuildPhase section */ | ||
+ 5D39CEDE1C6E983100DFCF86 /* Headers */ = { | ||
+ isa = PBXHeadersBuildPhase; | ||
+ buildActionMask = 2147483647; | ||
+ files = ( | ||
+ ); | ||
+ runOnlyForDeploymentPostprocessing = 0; | ||
+ }; | ||
+/* End PBXHeadersBuildPhase section */ | ||
+ | ||
+/* Begin PBXNativeTarget section */ | ||
+ 22F74AA41C7674BB004C6E4C /* AdvanceSample */ = { | ||
+ isa = PBXNativeTarget; | ||
+ buildConfigurationList = 22F74AB61C7674BB004C6E4C /* Build configuration list for PBXNativeTarget "AdvanceSample" */; | ||
+ buildPhases = ( | ||
+ 22F74AA11C7674BB004C6E4C /* Sources */, | ||
+ 22F74AA21C7674BB004C6E4C /* Frameworks */, | ||
+ 22F74AA31C7674BB004C6E4C /* Resources */, | ||
+ 22B8E9821C7713F40010EF5A /* CopyFiles */, | ||
+ ); | ||
+ buildRules = ( | ||
+ ); | ||
+ dependencies = ( | ||
+ 22F74ABE1C767797004C6E4C /* PBXTargetDependency */, | ||
+ ); | ||
+ name = AdvanceSample; | ||
+ productName = AnimationSample; | ||
+ productReference = 22F74AA51C7674BB004C6E4C /* AdvanceSample.app */; | ||
+ productType = "com.apple.product-type.application"; | ||
+ }; | ||
+ 5D39CEE01C6E983100DFCF86 /* Advance */ = { | ||
+ isa = PBXNativeTarget; | ||
+ buildConfigurationList = 5D39CEF51C6E983100DFCF86 /* Build configuration list for PBXNativeTarget "Advance" */; | ||
+ buildPhases = ( | ||
+ 5D39CEDC1C6E983100DFCF86 /* Sources */, | ||
+ 5D39CEDD1C6E983100DFCF86 /* Frameworks */, | ||
+ 5D39CEDE1C6E983100DFCF86 /* Headers */, | ||
+ 5D39CEDF1C6E983100DFCF86 /* Resources */, | ||
+ ); | ||
+ buildRules = ( | ||
+ ); | ||
+ dependencies = ( | ||
+ ); | ||
+ name = Advance; | ||
+ productName = Advance; | ||
+ productReference = 5D39CEE11C6E983100DFCF86 /* Advance.framework */; | ||
+ productType = "com.apple.product-type.framework"; | ||
+ }; | ||
+ 5D39CEEA1C6E983100DFCF86 /* AdvanceTests */ = { | ||
+ isa = PBXNativeTarget; | ||
+ buildConfigurationList = 5D39CEF81C6E983100DFCF86 /* Build configuration list for PBXNativeTarget "AdvanceTests" */; | ||
+ buildPhases = ( | ||
+ 5D39CEE71C6E983100DFCF86 /* Sources */, | ||
+ 5D39CEE81C6E983100DFCF86 /* Frameworks */, | ||
+ 5D39CEE91C6E983100DFCF86 /* Resources */, | ||
+ ); | ||
+ buildRules = ( | ||
+ ); | ||
+ dependencies = ( | ||
+ 5D39CEEE1C6E983100DFCF86 /* PBXTargetDependency */, | ||
+ 22168B511C7A42B100BAE519 /* PBXTargetDependency */, | ||
+ ); | ||
+ name = AdvanceTests; | ||
+ productName = AdvanceTests; | ||
+ productReference = 5D39CEEB1C6E983100DFCF86 /* Advance.xctest */; | ||
+ productType = "com.apple.product-type.bundle.unit-test"; | ||
+ }; | ||
+/* End PBXNativeTarget section */ | ||
+ | ||
+/* Begin PBXProject section */ | ||
+ 5D39CED81C6E983100DFCF86 /* Project object */ = { | ||
+ isa = PBXProject; | ||
+ attributes = { | ||
+ LastSwiftUpdateCheck = 0720; | ||
+ LastUpgradeCheck = 0730; | ||
+ ORGANIZATIONNAME = "Storehouse Media Inc"; | ||
+ TargetAttributes = { | ||
+ 22F74AA41C7674BB004C6E4C = { | ||
+ CreatedOnToolsVersion = 7.2.1; | ||
+ DevelopmentTeam = HV4J834MC8; | ||
+ }; | ||
+ 5D39CEE01C6E983100DFCF86 = { | ||
+ CreatedOnToolsVersion = 7.3; | ||
+ DevelopmentTeam = HV4J834MC8; | ||
+ }; | ||
+ 5D39CEEA1C6E983100DFCF86 = { | ||
+ CreatedOnToolsVersion = 7.3; | ||
+ }; | ||
+ }; | ||
+ }; | ||
+ buildConfigurationList = 5D39CEDB1C6E983100DFCF86 /* Build configuration list for PBXProject "Advance" */; | ||
+ compatibilityVersion = "Xcode 3.2"; | ||
+ developmentRegion = English; | ||
+ hasScannedForEncodings = 0; | ||
+ knownRegions = ( | ||
+ en, | ||
+ Base, | ||
+ ); | ||
+ mainGroup = 5D39CED71C6E983100DFCF86; | ||
+ productRefGroup = 5D39CEE21C6E983100DFCF86 /* Products */; | ||
+ projectDirPath = ""; | ||
+ projectRoot = ""; | ||
+ targets = ( | ||
+ 5D39CEE01C6E983100DFCF86 /* Advance */, | ||
+ 5D39CEEA1C6E983100DFCF86 /* AdvanceTests */, | ||
+ 22F74AA41C7674BB004C6E4C /* AdvanceSample */, | ||
+ ); | ||
+ }; | ||
+/* End PBXProject section */ | ||
+ | ||
+/* Begin PBXResourcesBuildPhase section */ | ||
+ 22F74AA31C7674BB004C6E4C /* Resources */ = { | ||
+ isa = PBXResourcesBuildPhase; | ||
+ buildActionMask = 2147483647; | ||
+ files = ( | ||
+ 22F74AB21C7674BB004C6E4C /* LaunchScreen.storyboard in Resources */, | ||
+ 22F74AAF1C7674BB004C6E4C /* Assets.xcassets in Resources */, | ||
+ ); | ||
+ runOnlyForDeploymentPostprocessing = 0; | ||
+ }; | ||
+ 5D39CEDF1C6E983100DFCF86 /* Resources */ = { | ||
+ isa = PBXResourcesBuildPhase; | ||
+ buildActionMask = 2147483647; | ||
+ files = ( | ||
+ ); | ||
+ runOnlyForDeploymentPostprocessing = 0; | ||
+ }; | ||
+ 5D39CEE91C6E983100DFCF86 /* Resources */ = { | ||
+ isa = PBXResourcesBuildPhase; | ||
+ buildActionMask = 2147483647; | ||
+ files = ( | ||
+ ); | ||
+ runOnlyForDeploymentPostprocessing = 0; | ||
+ }; | ||
+/* End PBXResourcesBuildPhase section */ | ||
+ | ||
+/* Begin PBXSourcesBuildPhase section */ | ||
+ 22F74AA11C7674BB004C6E4C /* Sources */ = { | ||
+ isa = PBXSourcesBuildPhase; | ||
+ buildActionMask = 2147483647; | ||
+ files = ( | ||
+ 223E49751C76C9510048CAB6 /* SpringView.swift in Sources */, | ||
+ 222E21211C7E44660063FAA2 /* BrowserViewController.swift in Sources */, | ||
+ 2299A6831C7D35FE009DE78C /* DirectManipulationGestureRecognizer.swift in Sources */, | ||
+ 22F74AA81C7674BB004C6E4C /* AppDelegate.swift in Sources */, | ||
+ 22142E671C810DA7000C6DEB /* GravityViewController.swift in Sources */, | ||
+ 2212A2F11C82702500BB79F4 /* ActivityView.swift in Sources */, | ||
+ 222E21251C7E45420063FAA2 /* BrowserView.swift in Sources */, | ||
+ 222E211D1C7D4C300063FAA2 /* SimpleTransform.swift in Sources */, | ||
+ 22F74ABB1C767757004C6E4C /* GestureView.swift in Sources */, | ||
+ 22319B721C8110A300D4E027 /* CoverView.swift in Sources */, | ||
+ 22142E651C810D86000C6DEB /* ActivityViewController.swift in Sources */, | ||
+ 22F74AB81C76769C004C6E4C /* GesturesViewController.swift in Sources */, | ||
+ 222E211F1C7D8CF00063FAA2 /* SpringConfigurationView.swift in Sources */, | ||
+ 226A752E1C84206D00A7F1FB /* GravityFunction.swift in Sources */, | ||
+ 226A752C1C84153E00A7F1FB /* GravitySimulation.swift in Sources */, | ||
+ 223E49731C76C9430048CAB6 /* SpringsViewController.swift in Sources */, | ||
+ 222E21231C7E448D0063FAA2 /* DemoViewController.swift in Sources */, | ||
+ ); | ||
+ runOnlyForDeploymentPostprocessing = 0; | ||
+ }; | ||
+ 5D39CEDC1C6E983100DFCF86 /* Sources */ = { | ||
+ isa = PBXSourcesBuildPhase; | ||
+ buildActionMask = 2147483647; | ||
+ files = ( | ||
+ 5DB1A4A01C876AD1000CF9EE /* Loop.swift in Sources */, | ||
+ 5DB1A4AD1C876AD1000CF9EE /* Interpolatable.swift in Sources */, | ||
+ 5DB1A4A61C876AD1000CF9EE /* CGFloat+VectorConvertible.swift in Sources */, | ||
+ 5DB1A49C1C876AD1000CF9EE /* AnimatorContext.swift in Sources */, | ||
+ 5DB1A4AC1C876AD1000CF9EE /* VectorConvertible.swift in Sources */, | ||
+ 5DB1A4B01C876AD1000CF9EE /* Vector3.swift in Sources */, | ||
+ 5DB1A4971C876AD1000CF9EE /* UnitBezier.swift in Sources */, | ||
+ 5DB1A4921C876AD1000CF9EE /* Advanceable.swift in Sources */, | ||
+ 5DB1A4A51C876AD1000CF9EE /* SpringFunction.swift in Sources */, | ||
+ 5DB1A4911C876AD1000CF9EE /* Animatable.swift in Sources */, | ||
+ 5DB1A4951C876AD1000CF9EE /* BasicAnimation.swift in Sources */, | ||
+ 5DB1A49A1C876AD1000CF9EE /* ValueAnimationType.swift in Sources */, | ||
+ 5DB1A4A91C876AD1000CF9EE /* CGSize+VectorConvertible.swift in Sources */, | ||
+ 5DB1A4A41C876AD1000CF9EE /* Spring.swift in Sources */, | ||
+ 5DB1A4AF1C876AD1000CF9EE /* Vector2.swift in Sources */, | ||
+ 5DB1A4B11C876AD1000CF9EE /* Vector4.swift in Sources */, | ||
+ 5DB1A4931C876AD1000CF9EE /* AnimationType.swift in Sources */, | ||
+ 5DB1A4981C876AD1000CF9EE /* DecayAnimation.swift in Sources */, | ||
+ 5DB1A4991C876AD1000CF9EE /* SpringAnimation.swift in Sources */, | ||
+ 5DB1A4A11C876AD1000CF9EE /* DecayFunction.swift in Sources */, | ||
+ 5DB1A4B31C876AD1000CF9EE /* VectorType.swift in Sources */, | ||
+ 5DB1A4AB1C876AD1000CF9EE /* Double+VectorConvertible.swift in Sources */, | ||
+ 5DB1A4941C876AD1000CF9EE /* AnyValueAnimation.swift in Sources */, | ||
+ 5DB1A4A81C876AD1000CF9EE /* CGRect+VectorConvertible.swift in Sources */, | ||
+ 5DB1A4B21C876AD1000CF9EE /* VectorMathCapable.swift in Sources */, | ||
+ 5DB1A4A71C876AD1000CF9EE /* CGPoint+VectorConvertible.swift in Sources */, | ||
+ 5DB1A4AE1C876AD1000CF9EE /* Vector1.swift in Sources */, | ||
+ 5DB1A4A21C876AD1000CF9EE /* DynamicFunctionType.swift in Sources */, | ||
+ 5DB1A49B1C876AD1000CF9EE /* Animator.swift in Sources */, | ||
+ 5DB1A4A31C876AD1000CF9EE /* DynamicSimulation.swift in Sources */, | ||
+ 5DB1A4AA1C876AD1000CF9EE /* CGVector+VectorConvertible.swift in Sources */, | ||
+ 5DB1A4961C876AD1000CF9EE /* TimingFunctionType.swift in Sources */, | ||
+ 5DB1A49E1C876AD1000CF9EE /* CAMediaTimingFunction.swift in Sources */, | ||
+ 5DB1A49D1C876AD1000CF9EE /* Event.swift in Sources */, | ||
+ ); | ||
+ runOnlyForDeploymentPostprocessing = 0; | ||
+ }; | ||
+ 5D39CEE71C6E983100DFCF86 /* Sources */ = { | ||
+ isa = PBXSourcesBuildPhase; | ||
+ buildActionMask = 2147483647; | ||
+ files = ( | ||
+ 5DA94F4D1C8112810039B9BB /* CAMediaTimingFunctionTests.swift in Sources */, | ||
+ 5DA94F4B1C80E2700039B9BB /* UnitBezierTests.swift in Sources */, | ||
+ 22168B5B1C7A547300BAE519 /* VectorTypeTests.swift in Sources */, | ||
+ 22168B5D1C7A5E3500BAE519 /* BasicAnimationTests.swift in Sources */, | ||
+ 5DA94F4F1C8114680039B9BB /* EventTests.swift in Sources */, | ||
+ 5DA94F481C80DAA00039B9BB /* VectorConvenienceTests.swift in Sources */, | ||
+ ); | ||
+ runOnlyForDeploymentPostprocessing = 0; | ||
+ }; | ||
+/* End PBXSourcesBuildPhase section */ | ||
+ | ||
+/* Begin PBXTargetDependency section */ | ||
+ 22168B511C7A42B100BAE519 /* PBXTargetDependency */ = { | ||
+ isa = PBXTargetDependency; | ||
+ target = 22F74AA41C7674BB004C6E4C /* AdvanceSample */; | ||
+ targetProxy = 22168B501C7A42B100BAE519 /* PBXContainerItemProxy */; | ||
+ }; | ||
+ 22F74ABE1C767797004C6E4C /* PBXTargetDependency */ = { | ||
+ isa = PBXTargetDependency; | ||
+ target = 5D39CEE01C6E983100DFCF86 /* Advance */; | ||
+ targetProxy = 22F74ABD1C767797004C6E4C /* PBXContainerItemProxy */; | ||
+ }; | ||
+ 5D39CEEE1C6E983100DFCF86 /* PBXTargetDependency */ = { | ||
+ isa = PBXTargetDependency; | ||
+ target = 5D39CEE01C6E983100DFCF86 /* Advance */; | ||
+ targetProxy = 5D39CEED1C6E983100DFCF86 /* PBXContainerItemProxy */; | ||
+ }; | ||
+/* End PBXTargetDependency section */ | ||
+ | ||
+/* Begin PBXVariantGroup section */ | ||
+ 22F74AB01C7674BB004C6E4C /* LaunchScreen.storyboard */ = { | ||
+ isa = PBXVariantGroup; | ||
+ children = ( | ||
+ 22F74AB11C7674BB004C6E4C /* Base */, | ||
+ ); | ||
+ name = LaunchScreen.storyboard; | ||
+ sourceTree = "<group>"; | ||
+ }; | ||
+/* End PBXVariantGroup section */ | ||
+ | ||
+/* Begin XCBuildConfiguration section */ | ||
+ 22F74AB41C7674BB004C6E4C /* Debug */ = { | ||
+ isa = XCBuildConfiguration; | ||
+ buildSettings = { | ||
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | ||
+ CODE_SIGN_IDENTITY = "iPhone Developer"; | ||
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; | ||
+ EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; | ||
+ INFOPLIST_FILE = "$(SRCROOT)/AdvanceSample/Info.plist"; | ||
+ IPHONEOS_DEPLOYMENT_TARGET = 8.2; | ||
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; | ||
+ PRODUCT_BUNDLE_IDENTIFIER = co.storehouse.AdvanceSample; | ||
+ PRODUCT_NAME = AdvanceSample; | ||
+ PROVISIONING_PROFILE = "410f269e-a8f2-4643-9cc3-7627924a8277"; | ||
+ }; | ||
+ name = Debug; | ||
+ }; | ||
+ 22F74AB51C7674BB004C6E4C /* Release */ = { | ||
+ isa = XCBuildConfiguration; | ||
+ buildSettings = { | ||
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | ||
+ CODE_SIGN_IDENTITY = "iPhone Developer"; | ||
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; | ||
+ EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; | ||
+ INFOPLIST_FILE = "$(SRCROOT)/AdvanceSample/Info.plist"; | ||
+ IPHONEOS_DEPLOYMENT_TARGET = 8.2; | ||
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; | ||
+ PRODUCT_BUNDLE_IDENTIFIER = co.storehouse.AdvanceSample; | ||
+ PRODUCT_NAME = AdvanceSample; | ||
+ PROVISIONING_PROFILE = "410f269e-a8f2-4643-9cc3-7627924a8277"; | ||
+ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; | ||
+ }; | ||
+ name = Release; | ||
+ }; | ||
+ 5D39CEF31C6E983100DFCF86 /* Debug */ = { | ||
+ isa = XCBuildConfiguration; | ||
+ buildSettings = { | ||
+ ALWAYS_SEARCH_USER_PATHS = NO; | ||
+ CLANG_ANALYZER_NONNULL = YES; | ||
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; | ||
+ CLANG_CXX_LIBRARY = "libc++"; | ||
+ CLANG_ENABLE_MODULES = YES; | ||
+ CLANG_ENABLE_OBJC_ARC = YES; | ||
+ CLANG_WARN_BOOL_CONVERSION = YES; | ||
+ CLANG_WARN_CONSTANT_CONVERSION = YES; | ||
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; | ||
+ CLANG_WARN_EMPTY_BODY = YES; | ||
+ CLANG_WARN_ENUM_CONVERSION = YES; | ||
+ CLANG_WARN_INT_CONVERSION = YES; | ||
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; | ||
+ CLANG_WARN_UNREACHABLE_CODE = YES; | ||
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; | ||
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; | ||
+ COPY_PHASE_STRIP = NO; | ||
+ CURRENT_PROJECT_VERSION = 1; | ||
+ DEBUG_INFORMATION_FORMAT = dwarf; | ||
+ ENABLE_STRICT_OBJC_MSGSEND = YES; | ||
+ ENABLE_TESTABILITY = YES; | ||
+ GCC_C_LANGUAGE_STANDARD = gnu99; | ||
+ GCC_DYNAMIC_NO_PIC = NO; | ||
+ GCC_NO_COMMON_BLOCKS = YES; | ||
+ GCC_OPTIMIZATION_LEVEL = 0; | ||
+ GCC_PREPROCESSOR_DEFINITIONS = ( | ||
+ "DEBUG=1", | ||
+ "$(inherited)", | ||
+ ); | ||
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; | ||
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; | ||
+ GCC_WARN_UNDECLARED_SELECTOR = YES; | ||
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | ||
+ GCC_WARN_UNUSED_FUNCTION = YES; | ||
+ GCC_WARN_UNUSED_VARIABLE = YES; | ||
+ IPHONEOS_DEPLOYMENT_TARGET = 9.3; | ||
+ MTL_ENABLE_DEBUG_INFO = YES; | ||
+ ONLY_ACTIVE_ARCH = YES; | ||
+ SDKROOT = iphoneos; | ||
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; | ||
+ TARGETED_DEVICE_FAMILY = "1,2"; | ||
+ VERSIONING_SYSTEM = "apple-generic"; | ||
+ VERSION_INFO_PREFIX = ""; | ||
+ }; | ||
+ name = Debug; | ||
+ }; | ||
+ 5D39CEF41C6E983100DFCF86 /* Release */ = { | ||
+ isa = XCBuildConfiguration; | ||
+ buildSettings = { | ||
+ ALWAYS_SEARCH_USER_PATHS = NO; | ||
+ CLANG_ANALYZER_NONNULL = YES; | ||
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; | ||
+ CLANG_CXX_LIBRARY = "libc++"; | ||
+ CLANG_ENABLE_MODULES = YES; | ||
+ CLANG_ENABLE_OBJC_ARC = YES; | ||
+ CLANG_WARN_BOOL_CONVERSION = YES; | ||
+ CLANG_WARN_CONSTANT_CONVERSION = YES; | ||
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; | ||
+ CLANG_WARN_EMPTY_BODY = YES; | ||
+ CLANG_WARN_ENUM_CONVERSION = YES; | ||
+ CLANG_WARN_INT_CONVERSION = YES; | ||
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; | ||
+ CLANG_WARN_UNREACHABLE_CODE = YES; | ||
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; | ||
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; | ||
+ COPY_PHASE_STRIP = NO; | ||
+ CURRENT_PROJECT_VERSION = 1; | ||
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; | ||
+ ENABLE_NS_ASSERTIONS = NO; | ||
+ ENABLE_STRICT_OBJC_MSGSEND = YES; | ||
+ GCC_C_LANGUAGE_STANDARD = gnu99; | ||
+ GCC_NO_COMMON_BLOCKS = YES; | ||
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; | ||
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; | ||
+ GCC_WARN_UNDECLARED_SELECTOR = YES; | ||
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | ||
+ GCC_WARN_UNUSED_FUNCTION = YES; | ||
+ GCC_WARN_UNUSED_VARIABLE = YES; | ||
+ IPHONEOS_DEPLOYMENT_TARGET = 9.3; | ||
+ MTL_ENABLE_DEBUG_INFO = NO; | ||
+ SDKROOT = iphoneos; | ||
+ TARGETED_DEVICE_FAMILY = "1,2"; | ||
+ VALIDATE_PRODUCT = YES; | ||
+ VERSIONING_SYSTEM = "apple-generic"; | ||
+ VERSION_INFO_PREFIX = ""; | ||
+ }; | ||
+ name = Release; | ||
+ }; | ||
+ 5D39CEF61C6E983100DFCF86 /* Debug */ = { | ||
+ isa = XCBuildConfiguration; | ||
+ buildSettings = { | ||
+ CLANG_ENABLE_MODULES = YES; | ||
+ CODE_SIGN_IDENTITY = "iPhone Developer"; | ||
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; | ||
+ DEFINES_MODULE = YES; | ||
+ DYLIB_COMPATIBILITY_VERSION = 1; | ||
+ DYLIB_CURRENT_VERSION = 1; | ||
+ DYLIB_INSTALL_NAME_BASE = "@rpath"; | ||
+ INFOPLIST_FILE = "$(SRCROOT)/Advance/Info.plist"; | ||
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; | ||
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0; | ||
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; | ||
+ PRODUCT_BUNDLE_IDENTIFIER = co.storehouse.Advance; | ||
+ PRODUCT_NAME = Advance; | ||
+ PROVISIONING_PROFILE = ""; | ||
+ SKIP_INSTALL = YES; | ||
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; | ||
+ }; | ||
+ name = Debug; | ||
+ }; | ||
+ 5D39CEF71C6E983100DFCF86 /* Release */ = { | ||
+ isa = XCBuildConfiguration; | ||
+ buildSettings = { | ||
+ CLANG_ENABLE_MODULES = YES; | ||
+ CODE_SIGN_IDENTITY = "iPhone Developer"; | ||
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; | ||
+ DEFINES_MODULE = YES; | ||
+ DYLIB_COMPATIBILITY_VERSION = 1; | ||
+ DYLIB_CURRENT_VERSION = 1; | ||
+ DYLIB_INSTALL_NAME_BASE = "@rpath"; | ||
+ INFOPLIST_FILE = "$(SRCROOT)/Advance/Info.plist"; | ||
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; | ||
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0; | ||
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; | ||
+ PRODUCT_BUNDLE_IDENTIFIER = co.storehouse.Advance; | ||
+ PRODUCT_NAME = Advance; | ||
+ PROVISIONING_PROFILE = ""; | ||
+ SKIP_INSTALL = YES; | ||
+ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; | ||
+ }; | ||
+ name = Release; | ||
+ }; | ||
+ 5D39CEF91C6E983100DFCF86 /* Debug */ = { | ||
+ isa = XCBuildConfiguration; | ||
+ buildSettings = { | ||
+ INFOPLIST_FILE = Advance/Info.plist; | ||
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0; | ||
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; | ||
+ PRODUCT_BUNDLE_IDENTIFIER = co.storehouse.Advance; | ||
+ PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)Tests"; | ||
+ PRODUCT_NAME = Advance; | ||
+ }; | ||
+ name = Debug; | ||
+ }; | ||
+ 5D39CEFA1C6E983100DFCF86 /* Release */ = { | ||
+ isa = XCBuildConfiguration; | ||
+ buildSettings = { | ||
+ INFOPLIST_FILE = Advance/Info.plist; | ||
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0; | ||
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; | ||
+ PRODUCT_BUNDLE_IDENTIFIER = co.storehouse.Advance; | ||
+ PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)Tests"; | ||
+ PRODUCT_NAME = Advance; | ||
+ }; | ||
+ name = Release; | ||
+ }; | ||
+/* End XCBuildConfiguration section */ | ||
+ | ||
+/* Begin XCConfigurationList section */ | ||
+ 22F74AB61C7674BB004C6E4C /* Build configuration list for PBXNativeTarget "AdvanceSample" */ = { | ||
+ isa = XCConfigurationList; | ||
+ buildConfigurations = ( | ||
+ 22F74AB41C7674BB004C6E4C /* Debug */, | ||
+ 22F74AB51C7674BB004C6E4C /* Release */, | ||
+ ); | ||
+ defaultConfigurationIsVisible = 0; | ||
+ defaultConfigurationName = Release; | ||
+ }; | ||
+ 5D39CEDB1C6E983100DFCF86 /* Build configuration list for PBXProject "Advance" */ = { | ||
+ isa = XCConfigurationList; | ||
+ buildConfigurations = ( | ||
+ 5D39CEF31C6E983100DFCF86 /* Debug */, | ||
+ 5D39CEF41C6E983100DFCF86 /* Release */, | ||
+ ); | ||
+ defaultConfigurationIsVisible = 0; | ||
+ defaultConfigurationName = Release; | ||
+ }; | ||
+ 5D39CEF51C6E983100DFCF86 /* Build configuration list for PBXNativeTarget "Advance" */ = { | ||
+ isa = XCConfigurationList; | ||
+ buildConfigurations = ( | ||
+ 5D39CEF61C6E983100DFCF86 /* Debug */, | ||
+ 5D39CEF71C6E983100DFCF86 /* Release */, | ||
+ ); | ||
+ defaultConfigurationIsVisible = 0; | ||
+ defaultConfigurationName = Release; | ||
+ }; | ||
+ 5D39CEF81C6E983100DFCF86 /* Build configuration list for PBXNativeTarget "AdvanceTests" */ = { | ||
+ isa = XCConfigurationList; | ||
+ buildConfigurations = ( | ||
+ 5D39CEF91C6E983100DFCF86 /* Debug */, | ||
+ 5D39CEFA1C6E983100DFCF86 /* Release */, | ||
+ ); | ||
+ defaultConfigurationIsVisible = 0; | ||
+ defaultConfigurationName = Release; | ||
+ }; | ||
+/* End XCConfigurationList section */ | ||
+ }; | ||
+ rootObject = 5D39CED81C6E983100DFCF86 /* Project object */; | ||
+} |
7
Advance.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@ | ||
+<?xml version="1.0" encoding="UTF-8"?> | ||
+<Workspace | ||
+ version = "1.0"> | ||
+ <FileRef | ||
+ location = "self:/Users/joel/Desktop/Storehouse/Animation/Advance.xcodeproj"> | ||
+ </FileRef> | ||
+</Workspace> |
99
Advance.xcodeproj/xcshareddata/xcschemes/Advance.xcscheme
@@ -0,0 +1,99 @@ | ||
+<?xml version="1.0" encoding="UTF-8"?> | ||
+<Scheme | ||
+ LastUpgradeVersion = "0720" | ||
+ version = "1.3"> | ||
+ <BuildAction | ||
+ parallelizeBuildables = "YES" | ||
+ buildImplicitDependencies = "YES"> | ||
+ <BuildActionEntries> | ||
+ <BuildActionEntry | ||
+ buildForTesting = "YES" | ||
+ buildForRunning = "YES" | ||
+ buildForProfiling = "YES" | ||
+ buildForArchiving = "YES" | ||
+ buildForAnalyzing = "YES"> | ||
+ <BuildableReference | ||
+ BuildableIdentifier = "primary" | ||
+ BlueprintIdentifier = "5D39CEE01C6E983100DFCF86" | ||
+ BuildableName = "Advance.framework" | ||
+ BlueprintName = "Advance" | ||
+ ReferencedContainer = "container:Advance.xcodeproj"> | ||
+ </BuildableReference> | ||
+ </BuildActionEntry> | ||
+ </BuildActionEntries> | ||
+ </BuildAction> | ||
+ <TestAction | ||
+ buildConfiguration = "Debug" | ||
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||
+ shouldUseLaunchSchemeArgsEnv = "YES"> | ||
+ <Testables> | ||
+ <TestableReference | ||
+ skipped = "NO"> | ||
+ <BuildableReference | ||
+ BuildableIdentifier = "primary" | ||
+ BlueprintIdentifier = "5D39CEEA1C6E983100DFCF86" | ||
+ BuildableName = "Advance.xctest" | ||
+ BlueprintName = "AdvanceTests" | ||
+ ReferencedContainer = "container:Advance.xcodeproj"> | ||
+ </BuildableReference> | ||
+ </TestableReference> | ||
+ </Testables> | ||
+ <MacroExpansion> | ||
+ <BuildableReference | ||
+ BuildableIdentifier = "primary" | ||
+ BlueprintIdentifier = "5D39CEE01C6E983100DFCF86" | ||
+ BuildableName = "Advance.framework" | ||
+ BlueprintName = "Advance" | ||
+ ReferencedContainer = "container:Advance.xcodeproj"> | ||
+ </BuildableReference> | ||
+ </MacroExpansion> | ||
+ <AdditionalOptions> | ||
+ </AdditionalOptions> | ||
+ </TestAction> | ||
+ <LaunchAction | ||
+ buildConfiguration = "Debug" | ||
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||
+ launchStyle = "0" | ||
+ useCustomWorkingDirectory = "NO" | ||
+ ignoresPersistentStateOnLaunch = "NO" | ||
+ debugDocumentVersioning = "YES" | ||
+ debugServiceExtension = "internal" | ||
+ allowLocationSimulation = "YES"> | ||
+ <MacroExpansion> | ||
+ <BuildableReference | ||
+ BuildableIdentifier = "primary" | ||
+ BlueprintIdentifier = "5D39CEE01C6E983100DFCF86" | ||
+ BuildableName = "Advance.framework" | ||
+ BlueprintName = "Advance" | ||
+ ReferencedContainer = "container:Advance.xcodeproj"> | ||
+ </BuildableReference> | ||
+ </MacroExpansion> | ||
+ <AdditionalOptions> | ||
+ </AdditionalOptions> | ||
+ </LaunchAction> | ||
+ <ProfileAction | ||
+ buildConfiguration = "Release" | ||
+ shouldUseLaunchSchemeArgsEnv = "YES" | ||
+ savedToolIdentifier = "" | ||
+ useCustomWorkingDirectory = "NO" | ||
+ debugDocumentVersioning = "YES"> | ||
+ <MacroExpansion> | ||
+ <BuildableReference | ||
+ BuildableIdentifier = "primary" | ||
+ BlueprintIdentifier = "5D39CEE01C6E983100DFCF86" | ||
+ BuildableName = "Advance.framework" | ||
+ BlueprintName = "Advance" | ||
+ ReferencedContainer = "container:Advance.xcodeproj"> | ||
+ </BuildableReference> | ||
+ </MacroExpansion> | ||
+ </ProfileAction> | ||
+ <AnalyzeAction | ||
+ buildConfiguration = "Debug"> | ||
+ </AnalyzeAction> | ||
+ <ArchiveAction | ||
+ buildConfiguration = "Release" | ||
+ revealArchiveInOrganizer = "YES"> | ||
+ </ArchiveAction> | ||
+</Scheme> |
92
Advance.xcodeproj/xcshareddata/xcschemes/AdvanceSample.xcscheme
@@ -0,0 +1,92 @@ | ||
+<?xml version="1.0" encoding="UTF-8"?> | ||
+<Scheme | ||
+ LastUpgradeVersion = "0720" | ||
+ version = "1.3"> | ||
+ <BuildAction | ||
+ parallelizeBuildables = "YES" | ||
+ buildImplicitDependencies = "YES"> | ||
+ <BuildActionEntries> | ||
+ <BuildActionEntry | ||
+ buildForTesting = "YES" | ||
+ buildForRunning = "YES" | ||
+ buildForProfiling = "YES" | ||
+ buildForArchiving = "YES" | ||
+ buildForAnalyzing = "YES" | ||
+ hideIssues = "NO"> | ||
+ <BuildableReference | ||
+ BuildableIdentifier = "primary" | ||
+ BlueprintIdentifier = "22F74AA41C7674BB004C6E4C" | ||
+ BuildableName = "AdvanceSample.app" | ||
+ BlueprintName = "AdvanceSample" | ||
+ ReferencedContainer = "container:Advance.xcodeproj"> | ||
+ </BuildableReference> | ||
+ </BuildActionEntry> | ||
+ </BuildActionEntries> | ||
+ </BuildAction> | ||
+ <TestAction | ||
+ buildConfiguration = "Debug" | ||
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||
+ shouldUseLaunchSchemeArgsEnv = "YES"> | ||
+ <Testables> | ||
+ </Testables> | ||
+ <MacroExpansion> | ||
+ <BuildableReference | ||
+ BuildableIdentifier = "primary" | ||
+ BlueprintIdentifier = "22F74AA41C7674BB004C6E4C" | ||
+ BuildableName = "AdvanceSample.app" | ||
+ BlueprintName = "AdvanceSample" | ||
+ ReferencedContainer = "container:Advance.xcodeproj"> | ||
+ </BuildableReference> | ||
+ </MacroExpansion> | ||
+ <AdditionalOptions> | ||
+ </AdditionalOptions> | ||
+ </TestAction> | ||
+ <LaunchAction | ||
+ buildConfiguration = "Debug" | ||
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||
+ launchStyle = "0" | ||
+ useCustomWorkingDirectory = "NO" | ||
+ ignoresPersistentStateOnLaunch = "NO" | ||
+ debugDocumentVersioning = "YES" | ||
+ debugServiceExtension = "internal" | ||
+ allowLocationSimulation = "YES"> | ||
+ <BuildableProductRunnable | ||
+ runnableDebuggingMode = "0"> | ||
+ <BuildableReference | ||
+ BuildableIdentifier = "primary" | ||
+ BlueprintIdentifier = "22F74AA41C7674BB004C6E4C" | ||
+ BuildableName = "AdvanceSample.app" | ||
+ BlueprintName = "AdvanceSample" | ||
+ ReferencedContainer = "container:Advance.xcodeproj"> | ||
+ </BuildableReference> | ||
+ </BuildableProductRunnable> | ||
+ <AdditionalOptions> | ||
+ </AdditionalOptions> | ||
+ </LaunchAction> | ||
+ <ProfileAction | ||
+ buildConfiguration = "Release" | ||
+ shouldUseLaunchSchemeArgsEnv = "YES" | ||
+ savedToolIdentifier = "" | ||
+ useCustomWorkingDirectory = "NO" | ||
+ debugDocumentVersioning = "YES"> | ||
+ <BuildableProductRunnable | ||
+ runnableDebuggingMode = "0"> | ||
+ <BuildableReference | ||
+ BuildableIdentifier = "primary" | ||
+ BlueprintIdentifier = "22F74AA41C7674BB004C6E4C" | ||
+ BuildableName = "AdvanceSample.app" | ||
+ BlueprintName = "AdvanceSample" | ||
+ ReferencedContainer = "container:Advance.xcodeproj"> | ||
+ </BuildableReference> | ||
+ </BuildableProductRunnable> | ||
+ </ProfileAction> | ||
+ <AnalyzeAction | ||
+ buildConfiguration = "Debug"> | ||
+ </AnalyzeAction> | ||
+ <ArchiveAction | ||
+ buildConfiguration = "Release" | ||
+ revealArchiveInOrganizer = "YES"> | ||
+ </ArchiveAction> | ||
+</Scheme> |
152
Advance/Animatable/Animatable.swift
@@ -0,0 +1,152 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+ | ||
+/// Instances of `Animatable` wrap, and manage animated change to, a value | ||
+/// conforming to `VectorConvertible`. | ||
+/// | ||
+/// Using this class to represent a value is often cleaner than setting | ||
+/// up and managing animations independently, as `Animatable` conveniently | ||
+/// channels all changes to the value into the `changed` event. For example: | ||
+/// | ||
+/// ``` | ||
+/// class Foo { | ||
+/// let size: Animatable<CGSize> | ||
+/// | ||
+/// (...) | ||
+/// | ||
+/// init() { | ||
+/// size.changed.observe { [weak self] (val) in | ||
+/// self?.doSomethingWithSize(val) | ||
+/// } | ||
+/// } | ||
+/// } | ||
+/// | ||
+/// let f = Foo() | ||
+/// f.size.animateTo(CGSize(width: 200.0, height: 200.0)) | ||
+/// ``` | ||
+/// | ||
+public final class Animatable<Value: VectorConvertible> { | ||
+ | ||
+ /// `finished` will be true if the animation finished uninterrupted, or | ||
+ /// false if it was cancelled. | ||
+ public typealias Completion = (finished: Bool) -> Void | ||
+ | ||
+ /// Fires each time the `value` property changes. | ||
+ public let changed = Event<Value>() | ||
+ | ||
+ // The animator that is driving the current animation, if any. | ||
+ private var animator: Animator<AnyValueAnimation<Value>>? = nil | ||
+ | ||
+ // Tracks the last publicly notified value – this lets us control when | ||
+ // events are fired (we always want to wait until the end of the | ||
+ // animation loop). | ||
+ private var currentValue: Value { | ||
+ didSet { | ||
+ guard currentValue != oldValue else { return } | ||
+ changed.fire(value) | ||
+ } | ||
+ } | ||
+ | ||
+ /// Returns `true` if an animation is in progress. | ||
+ public var animating: Bool { | ||
+ return animator != nil | ||
+ } | ||
+ | ||
+ /// The current value of this `Animatable`. | ||
+ /// | ||
+ /// Setting this property will cancel any animation that is in | ||
+ /// progress, and this `Animatable` will assume the new value immediately. | ||
+ public var value: Value { | ||
+ get { | ||
+ return currentValue | ||
+ } | ||
+ set { | ||
+ cancelAnimation() | ||
+ currentValue = newValue | ||
+ } | ||
+ } | ||
+ | ||
+ /// The current velocity reported by the in-flight animation, if any. If no | ||
+ /// animation is in progress, the returned value will be equivalent to | ||
+ /// `T.Vector.zero` | ||
+ public var velocity: Value { | ||
+ return animator?.animation.velocity ?? Value.zero | ||
+ } | ||
+ | ||
+ /// Creates an `Animatable` of T initialized to an initial value. | ||
+ /// | ||
+ /// - parameter value: The initial value of this animatable. | ||
+ public required init(value: Value) { | ||
+ currentValue = value | ||
+ } | ||
+ | ||
+ deinit { | ||
+ // Make sure we property fire the completion block for the in-flight | ||
+ // animation. | ||
+ cancelAnimation() | ||
+ } | ||
+ | ||
+ /// Runs the given animation until is either completes or is removed (by | ||
+ /// starting another animation or by directly setting the value). | ||
+ /// | ||
+ /// - parameter animation: The animation to be run. | ||
+ /// - parameter completion: An optional closure that will be called when this | ||
+ /// animation has completed. Its only argument is a `Boolean`, which will | ||
+ /// be `true` if the animation completed uninterrupted, or `false` if it | ||
+ /// was removed for any other reason. | ||
+ public func animate<A: ValueAnimationType where A.Value == Value>(animation: A, completion: Completion? = nil) { | ||
+ | ||
+ // Cancel any in-flight animation. We observe the cancelled event of | ||
+ // animators that we create in order to clean up, so this will have | ||
+ // the side effect of nilling the `animator` property. | ||
+ cancelAnimation() | ||
+ assert(animator == nil) | ||
+ | ||
+ animator = AnimatorContext.shared.animate(AnyValueAnimation(animation: animation)) | ||
+ | ||
+ animator?.changed.observe({ [unowned self] (a) -> Void in | ||
+ self.currentValue = a.value | ||
+ }) | ||
+ | ||
+ animator?.cancelled.observe({ [unowned self] (a) -> Void in | ||
+ self.animator = nil | ||
+ completion?(finished: false) | ||
+ }) | ||
+ | ||
+ animator?.finished.observe({ [unowned self] (a) -> Void in | ||
+ self.animator = nil | ||
+ completion?(finished: true) | ||
+ }) | ||
+ } | ||
+ | ||
+ /// Cancels an in-flight animation, if present. | ||
+ public func cancelAnimation() { | ||
+ animator?.cancel() | ||
+ } | ||
+} |
38
Advance/Animation/Advanceable.swift
@@ -0,0 +1,38 @@ | ||
+/* | ||
+ | ||
+ Copyright (c) 2016, Storehouse Media Inc. | ||
+ All rights reserved. | ||
+ | ||
+ Redistribution and use in source and binary forms, with or without | ||
+ modification, are permitted provided that the following conditions are met: | ||
+ | ||
+ * Redistributions of source code must retain the above copyright notice, this | ||
+ list of conditions and the following disclaimer. | ||
+ | ||
+ * Redistributions in binary form must reproduce the above copyright notice, | ||
+ this list of conditions and the following disclaimer in the documentation | ||
+ and/or other materials provided with the distribution. | ||
+ | ||
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+ */ | ||
+ | ||
+/// Conforming types support advancing their state by a time interval. | ||
+public protocol Advanceable { | ||
+ | ||
+ /// Advance the state of the receiver by the given time interval in seconds. | ||
+ /// | ||
+ /// - parameter elapsed: The length of time that the animation should | ||
+ /// be advanced by. | ||
+ mutating func advance(elapsed: Double) | ||
+ | ||
+} |
40
Advance/Animation/AnimationType.swift
@@ -0,0 +1,40 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+/// A protocol which defines the basic requirements to function as a | ||
+/// time-advancable animation. | ||
+public protocol AnimationType: Advanceable { | ||
+ | ||
+ /// Returns `True` if the animation has completed. | ||
+ /// | ||
+ /// After the animation finishes, it should not return to an unfinished | ||
+ /// state. Doing so may result in undefined behavior. | ||
+ var finished: Bool { get } | ||
+ | ||
+} | ||
+ |
67
Advance/Animation/AnyValueAnimation.swift
@@ -0,0 +1,67 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+ | ||
+/// Provides type erasure for an animation conforming to ValueAnimationType | ||
+/// | ||
+/// - parameter Value: The type of value to be animated. | ||
+public struct AnyValueAnimation<Value: VectorConvertible>: ValueAnimationType { | ||
+ | ||
+ /// The current value of the animation. | ||
+ public let value: Value | ||
+ | ||
+ /// The current value of the animation. | ||
+ public let velocity: Value | ||
+ | ||
+ /// `true` if the animation has finished. | ||
+ public let finished: Bool | ||
+ | ||
+ // Captures the underlying animation and allows us to advance it. | ||
+ private let advanceFunction: (Double) -> AnyValueAnimation<Value> | ||
+ | ||
+ /// Creates a new type-erased animation. | ||
+ /// | ||
+ /// - parameter animation: The animation to be type erased. | ||
+ public init<A: ValueAnimationType where A.Value == Value>(animation: A) { | ||
+ value = animation.value | ||
+ velocity = animation.velocity | ||
+ finished = animation.finished | ||
+ advanceFunction = { (time: Double) -> AnyValueAnimation<Value> in | ||
+ var a = animation | ||
+ a.advance(time) | ||
+ return AnyValueAnimation(animation: a) | ||
+ } | ||
+ } | ||
+ | ||
+ /// Advances the animation. | ||
+ /// | ||
+ /// - parameter elapsed: The time (in seconds) to advance the animation. | ||
+ public mutating func advance(time: Double) { | ||
+ self = advanceFunction(time) | ||
+ } | ||
+} |
141
Advance/Animation/Basic/BasicAnimation.swift
@@ -0,0 +1,141 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+/// Interpolates between values over a specified duration. | ||
+/// | ||
+/// - parameter Value: The type of value to be animated. | ||
+public struct BasicAnimation<Value: VectorConvertible>: ValueAnimationType { | ||
+ | ||
+ /// The initial value at time 0. | ||
+ private (set) public var from: Value | ||
+ | ||
+ /// The final value when the animation is finished. | ||
+ private (set) public var to: Value | ||
+ | ||
+ /// The duration of the animation in seconds. | ||
+ private (set) public var duration: Double | ||
+ | ||
+ /// The timing function that is used to map elapsed time to an | ||
+ /// interpolated value. | ||
+ private (set) public var timingFunction: TimingFunctionType | ||
+ | ||
+ /// Creates a new `BasicAnimation` instance. | ||
+ /// | ||
+ /// - parameter from: The value at time `0`. | ||
+ /// - parameter to: The value at the end of the animation. | ||
+ /// - parameter duration: How long (in seconds) the animation should last. | ||
+ /// - parameter timingFunction: The timing function to use. | ||
+ public init(from: Value, to: Value, duration: Double, timingFunction: TimingFunctionType = UnitBezier(preset: .SwiftOut)) { | ||
+ self.from = from | ||
+ self.to = to | ||
+ self.duration = duration | ||
+ self.timingFunction = timingFunction | ||
+ self.value = from | ||
+ self.velocity = Value.zero | ||
+ } | ||
+ | ||
+ /// The current value. | ||
+ private(set) public var value: Value | ||
+ | ||
+ /// The current velocity. | ||
+ private(set) public var velocity: Value | ||
+ | ||
+ private var elapsed: Double = 0.0 | ||
+ | ||
+ /// Returns `true` if the advanced time is `>=` duration. | ||
+ public var finished: Bool { | ||
+ return elapsed >= duration | ||
+ } | ||
+ | ||
+ /// Advances the animation. | ||
+ /// | ||
+ /// - parameter elapsed: The time (in seconds) to advance the animation. | ||
+ public mutating func advance(time: Double) { | ||
+ let starting = value | ||
+ | ||
+ elapsed += time | ||
+ var progress = elapsed / duration | ||
+ progress = max(progress, 0.0) | ||
+ progress = min(progress, 1.0) | ||
+ let adjustedProgress = timingFunction.solveForTime(Scalar(progress), epsilon: 1.0 / Scalar(duration * 1000.0)) | ||
+ | ||
+ let val = from.vector.interpolatedTo(to.vector, alpha: Scalar(adjustedProgress)) | ||
+ value = Value(vector: val) | ||
+ | ||
+ let vel = Scalar(1.0/time) * (value.vector - starting.vector) | ||
+ velocity = Value(vector: vel) | ||
+ } | ||
+ | ||
+} | ||
+ | ||
+ | ||
+public extension Animatable { | ||
+ | ||
+ /// Animates to the specified value, using a default duration and timing | ||
+ /// function. | ||
+ /// | ||
+ /// - parameter to: The value to animate to. | ||
+ /// - parameter completion: An optional closure that will be called when | ||
+ /// the animation completes. | ||
+ public func animateTo(to: Value, completion: Completion? = nil) { | ||
+ animateTo(to, duration: 0.25, timingFunction: UnitBezier(preset: .SwiftOut), completion: completion) | ||
+ } | ||
+ | ||
+ /// Animates to the specified value. | ||
+ /// | ||
+ /// - parameter to: The value to animate to. | ||
+ /// - parameter duration: The duration of the animation. | ||
+ /// - parameter timingFunction: The timing (easing) function to use. | ||
+ /// - parameter completion: An optional closure that will be called when | ||
+ /// the animation completes. | ||
+ public func animateTo(to: Value, duration: Double, timingFunction: TimingFunctionType, completion: Completion? = nil) { | ||
+ let a = BasicAnimation(from: value, to: to, duration: duration, timingFunction: timingFunction) | ||
+ animate(a, completion: completion) | ||
+ } | ||
+ | ||
+} | ||
+ | ||
+public extension VectorConvertible { | ||
+ | ||
+ /// Animates to the specified value. | ||
+ /// | ||
+ /// - parameter to: The value to animate to. | ||
+ /// - parameter duration: The duration of the animation. | ||
+ /// - parameter timingFunction: The timing (easing) function to use. | ||
+ /// - parameter callback: A closure that will be called with the new value | ||
+ /// for each frame of the animation until it is finished. | ||
+ /// - returns: The underlying animator. | ||
+ public func animateTo(to: Self, duration: Double, timingFunction: TimingFunctionType, callback: (Self)->Void) -> Animator<BasicAnimation<Self>> { | ||
+ let a = BasicAnimation(from: self, to: to, duration: duration, timingFunction: timingFunction) | ||
+ let animator = AnimatorContext.shared.animate(a) | ||
+ animator.changed.observe { (a) in | ||
+ callback(a.value) | ||
+ } | ||
+ return animator | ||
+ } | ||
+} |
115
Advance/Animation/Basic/TimingFunctionType.swift
@@ -0,0 +1,115 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import Foundation | ||
+import CoreGraphics | ||
+ | ||
+/// Conforming types can be used to convert linear input time (`0.0 -> 1.0`) to transformed output time (also `0.0 -> 1.0`). | ||
+public protocol TimingFunctionType { | ||
+ | ||
+ /// Transforms the given time. | ||
+ /// | ||
+ /// - parameter x: The input time (ranges between 0.0 and 1.0). | ||
+ /// - parameter epsilon: The required precision of the result (where `x * epsilon` is the maximum time segment to be evaluated). | ||
+ /// - returns: The resulting output time. | ||
+ func solveForTime(x: Scalar, epsilon: Scalar) -> Scalar | ||
+} | ||
+ | ||
+/// Returns the input time, unmodified. | ||
+public struct LinearTimingFunction: TimingFunctionType { | ||
+ | ||
+ /// Creates a new instance of `LinearTimingFunction`. | ||
+ public init(){} | ||
+ | ||
+ /// Solves for time `x`. | ||
+ public func solveForTime(x: Scalar, epsilon: Scalar) -> Scalar { | ||
+ return x | ||
+ } | ||
+} | ||
+ | ||
+/// Output time is calculated as `(1.0-x)`. | ||
+public struct ReversedTimingFunction: TimingFunctionType { | ||
+ /// Creates a new instance of `ReversedTimingFunction`. | ||
+ public init(){} | ||
+ | ||
+ /// Solves for time `x`. | ||
+ public func solveForTime(x: Scalar, epsilon: Scalar) -> Scalar { | ||
+ return 1.0 - x | ||
+ } | ||
+} | ||
+ | ||
+ | ||
+extension UnitBezier: TimingFunctionType { | ||
+ | ||
+ /// Solves for time `x`. | ||
+ public func solveForTime(x: Scalar, epsilon: Scalar) -> Scalar { | ||
+ return solve(x, epsilon: epsilon) | ||
+ } | ||
+} | ||
+ | ||
+public extension UnitBezier { | ||
+ | ||
+ /// A set of preset bezier curves. | ||
+ public enum Preset { | ||
+ /// Equivalent to `kCAMediaTimingFunctionDefault`. | ||
+ case Default | ||
+ | ||
+ /// Equivalent to `kCAMediaTimingFunctionEaseIn`. | ||
+ case EaseIn | ||
+ | ||
+ /// Equivalent to `kCAMediaTimingFunctionEaseOut`. | ||
+ case EaseOut | ||
+ | ||
+ /// Equivalent to `kCAMediaTimingFunctionEaseInEaseOut`. | ||
+ case EaseInEaseOut | ||
+ | ||
+ /// No easing. | ||
+ case Linear | ||
+ | ||
+ /// Inspired by the default curve in Google Material Design. | ||
+ case SwiftOut | ||
+ } | ||
+ | ||
+ /// Initializes a UnitBezier with a preset. | ||
+ public init(preset: Preset) { | ||
+ switch preset { | ||
+ case .Default: | ||
+ self = UnitBezier(p1x: 0.25, p1y: 0.1, p2x: 0.25, p2y: 1.0) | ||
+ case .EaseIn: | ||
+ self = UnitBezier(p1x: 0.42, p1y: 0.0, p2x: 1.0, p2y: 1.0) | ||
+ case .EaseOut: | ||
+ self = UnitBezier(p1x: 0.0, p1y: 0.0, p2x: 0.58, p2y: 1.0) | ||
+ case .EaseInEaseOut: | ||
+ self = UnitBezier(p1x: 0.42, p1y: 0.0, p2x: 0.58, p2y: 1.0) | ||
+ case .Linear: | ||
+ self = UnitBezier(p1x: 0.0, p1y: 0.0, p2x: 1.0, p2y: 1.0) | ||
+ case .SwiftOut: | ||
+ self = UnitBezier(p1x: 0.4, p1y: 0.0, p2x: 0.2, p2y: 1.0) | ||
+ } | ||
+ } | ||
+} |
196
Advance/Animation/Basic/UnitBezier.swift
@@ -0,0 +1,196 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import Foundation | ||
+import CoreGraphics | ||
+ | ||
+ | ||
+/// A bezier curve, often used to calculate timing functions. | ||
+public struct UnitBezier { | ||
+ | ||
+ /// The horizontal component of the first control point. | ||
+ public var p1x: Scalar | ||
+ | ||
+ /// The vertical component of the first control point. | ||
+ public var p1y: Scalar | ||
+ | ||
+ /// The horizontal component of the second control point. | ||
+ public var p2x: Scalar | ||
+ | ||
+ /// The vertical component of the second control point. | ||
+ public var p2y: Scalar | ||
+ | ||
+ /// Creates a new `UnitBezier` instance. | ||
+ public init(p1x: Scalar, p1y: Scalar, p2x: Scalar, p2y: Scalar) { | ||
+ self.p1x = p1x | ||
+ self.p1y = p1y | ||
+ self.p2x = p2x | ||
+ self.p2y = p2y | ||
+ } | ||
+ | ||
+ /// Calculates the resulting `y` for given `x`. | ||
+ /// | ||
+ /// - parameter x: The value to solve for. | ||
+ /// - parameter epsilon: The required precision of the result (where `x * epsilon` is the maximum time segment to be evaluated). | ||
+ /// - returns: The solved `y` value. | ||
+ public func solve(x: Scalar, epsilon: Scalar) -> Scalar { | ||
+ return UnitBezierSolver(bezier: self).solve(x, eps: epsilon) | ||
+ } | ||
+} | ||
+ | ||
+extension UnitBezier: Equatable { } | ||
+ | ||
+/// Equatable. | ||
+public func ==(lhs: UnitBezier, rhs: UnitBezier) -> Bool { | ||
+ return lhs.p1x == rhs.p1x | ||
+ && lhs.p1y == rhs.p1y | ||
+ && lhs.p2x == rhs.p2x | ||
+ && lhs.p2y == rhs.p2y | ||
+} | ||
+ | ||
+ | ||
+// Ported to Swift from WebCore: | ||
+// http://opensource.apple.com/source/WebCore/WebCore-955.66/platform/graphics/UnitBezier.h | ||
+ | ||
+/* | ||
+* Copyright (C) 2008 Apple Inc. All Rights Reserved. | ||
+* | ||
+* Redistribution and use in source and binary forms, with or without | ||
+* modification, are permitted provided that the following conditions | ||
+* are met: | ||
+* 1. Redistributions of source code must retain the above copyright | ||
+* notice, this list of conditions and the following disclaimer. | ||
+* 2. Redistributions in binary form must reproduce the above copyright | ||
+* notice, this list of conditions and the following disclaimer in the | ||
+* documentation and/or other materials provided with the distribution. | ||
+* | ||
+* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY | ||
+* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | ||
+* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR | ||
+* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | ||
+* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | ||
+* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | ||
+* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | ||
+* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+*/ | ||
+ | ||
+ | ||
+private struct UnitBezierSolver { | ||
+ | ||
+ private let ax: Scalar | ||
+ private let bx: Scalar | ||
+ private let cx: Scalar | ||
+ | ||
+ private let ay: Scalar | ||
+ private let by: Scalar | ||
+ private let cy: Scalar | ||
+ | ||
+ init(bezier: UnitBezier) { | ||
+ self.init(p1x: bezier.p1x, p1y: bezier.p1y, p2x: bezier.p2x, p2y: bezier.p2y) | ||
+ } | ||
+ | ||
+ init(p1x: Scalar, p1y: Scalar, p2x: Scalar, p2y: Scalar) { | ||
+ | ||
+ // Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1). | ||
+ cx = 3.0 * p1x | ||
+ bx = 3.0 * (p2x - p1x) - cx | ||
+ ax = 1.0 - cx - bx | ||
+ | ||
+ cy = 3.0 * p1y | ||
+ by = 3.0 * (p2y - p1y) - cy | ||
+ ay = 1.0 - cy - by | ||
+ } | ||
+ | ||
+ func sampleCurveX(t: Scalar) -> Scalar { | ||
+ return ((ax * t + bx) * t + cx) * t | ||
+ } | ||
+ | ||
+ func sampleCurveY(t: Scalar) -> Scalar { | ||
+ return ((ay * t + by) * t + cy) * t | ||
+ } | ||
+ | ||
+ func sampleCurveDerivativeX(t: Scalar) -> Scalar { | ||
+ return (3.0 * ax * t + 2.0 * bx) * t + cx | ||
+ } | ||
+ | ||
+ func solveCurveX(x: Scalar, eps: Scalar) -> Scalar { | ||
+ var t0: Scalar = 0.0 | ||
+ var t1: Scalar = 0.0 | ||
+ var t2: Scalar = 0.0 | ||
+ var x2: Scalar = 0.0 | ||
+ var d2: Scalar = 0.0 | ||
+ | ||
+ // First try a few iterations of Newton's method -- normally very fast. | ||
+ t2 = x | ||
+ for _ in 0..<8 { | ||
+ x2 = sampleCurveX(t2) - x | ||
+ if abs(x2) < eps { | ||
+ return t2 | ||
+ } | ||
+ d2 = sampleCurveDerivativeX(t2) | ||
+ if abs(d2) < 1e-6 { | ||
+ break | ||
+ } | ||
+ t2 = t2 - x2 / d2 | ||
+ } | ||
+ | ||
+ // Fall back to the bisection method for reliability. | ||
+ t0 = 0.0 | ||
+ t1 = 1.0 | ||
+ t2 = x | ||
+ | ||
+ if t2 < t0 { | ||
+ return t0 | ||
+ } | ||
+ if t2 > t1 { | ||
+ return t1 | ||
+ } | ||
+ | ||
+ while t0 < t1 { | ||
+ x2 = sampleCurveX(t2) | ||
+ if abs(x2-x) < eps { | ||
+ return t2 | ||
+ } | ||
+ if x > x2 { | ||
+ t0 = t2 | ||
+ } else { | ||
+ t1 = t2 | ||
+ } | ||
+ t2 = (t1-t0) * 0.5 + t0 | ||
+ } | ||
+ | ||
+ return t2 | ||
+ } | ||
+ | ||
+ func solve(x: Scalar, eps: Scalar) -> Scalar { | ||
+ return sampleCurveY(solveCurveX(x, eps: eps)) | ||
+ } | ||
+} |
120
Advance/Animation/Decay/DecayAnimation.swift
@@ -0,0 +1,120 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+/// Given a starting velocity, `DecayAnimation` will slowly bring the value | ||
+/// to a stop (where `velocity` == `Value.zero`). | ||
+/// | ||
+/// `DecayAnimation` uses a `DynamicSimulation` containing a `DecayFunction` | ||
+/// internally. | ||
+public struct DecayAnimation<Value: VectorConvertible>: ValueAnimationType { | ||
+ | ||
+ private var solver: DynamicSolver<DecayFunction<Value.Vector>> | ||
+ | ||
+ /// Creates a new `DecayAnimation` instance. | ||
+ /// | ||
+ /// - parameter threshold: The minimum velocity, below which the animation | ||
+ /// will finish. | ||
+ /// - parameter from: The initial value of the animation. | ||
+ /// - parameter velocity: The velocity at time `0`. | ||
+ public init(threshold: Scalar = 0.1, from: Value = Value.zero, velocity: Value = Value.zero) { | ||
+ var f = DecayFunction<Value.Vector>() | ||
+ f.threshold = threshold | ||
+ f.drag = 3.0 | ||
+ solver = DynamicSolver(function: f, value: from.vector, velocity: velocity.vector) | ||
+ } | ||
+ | ||
+ /// Advances the animation. | ||
+ /// | ||
+ /// - parameter elapsed: The time (in seconds) to advance the animation. | ||
+ public mutating func advance(elapsed: Double) { | ||
+ solver.advance(elapsed) | ||
+ } | ||
+ | ||
+ /// Returns `true` if the velocity has settled at 0. | ||
+ public var finished: Bool { | ||
+ return solver.settled | ||
+ } | ||
+ | ||
+ /// The current value. | ||
+ public var value: Value { | ||
+ get { return Value(vector: solver.value) } | ||
+ set { solver.value = newValue.vector } | ||
+ } | ||
+ | ||
+ /// The current velocity. | ||
+ public var velocity: Value { | ||
+ get { return Value(vector: solver.velocity) } | ||
+ set { solver.velocity = newValue.vector } | ||
+ } | ||
+ | ||
+ /// Each component of the simulation's velocity must be within this distance | ||
+ /// of 0.0 for the animation to complete. | ||
+ public var threshold: Scalar { | ||
+ get { return solver.function.threshold } | ||
+ set { solver.function.threshold = newValue } | ||
+ } | ||
+ | ||
+ /// The strength with which the velocity will be reduced. The acceleration | ||
+ /// for the simulation is calculated as `-drag * velocity`. Default: `3.0`. | ||
+ public var drag: Scalar { | ||
+ get { return solver.function.drag } | ||
+ set { solver.function.drag = newValue } | ||
+ } | ||
+} | ||
+ | ||
+ | ||
+public extension Animatable { | ||
+ | ||
+ /// Adds a decay animation, starting from the current value and velocity. | ||
+ /// | ||
+ /// - parameter drag: The amount of drag that will slow down the velocity. | ||
+ /// - parameter threshold: The settling threshold that determines how | ||
+ /// close the velocity must be to `0` before the simulation is allowed | ||
+ /// to settle. | ||
+ /// - parameter completion: An optional closure that will be called at | ||
+ /// the end of the animation. | ||
+ public func decay(drag: Scalar, threshold: Scalar, completion: Completion? = nil) { | ||
+ decay(velocity, drag: drag, threshold: threshold, completion: completion) | ||
+ } | ||
+ | ||
+ /// Adds a decay animation, starting from the current value. | ||
+ /// | ||
+ /// - parameter velocity: The initial velocity at time `0`. | ||
+ /// - parameter drag: The amount of drag that will slow down the velocity. | ||
+ /// - parameter threshold: The settling threshold that determines how | ||
+ /// close the velocity must be to `0` before the simulation is allowed | ||
+ /// to settle. | ||
+ /// - parameter completion: An optional closure that will be called at | ||
+ /// the end of the animation. | ||
+ public func decay(velocity: Value, drag: Scalar, threshold: Scalar, completion: Completion? = nil) { | ||
+ var d = DecayAnimation(threshold: threshold, from: value, velocity: velocity) | ||
+ d.drag = drag | ||
+ animate(d, completion: completion) | ||
+ } | ||
+ | ||
+} |
131
Advance/Animation/Spring/SpringAnimation.swift
@@ -0,0 +1,131 @@ | ||
+/* | ||
+ | ||
+ Copyright (c) 2016, Storehouse Media Inc. | ||
+ All rights reserved. | ||
+ | ||
+ Redistribution and use in source and binary forms, with or without | ||
+ modification, are permitted provided that the following conditions are met: | ||
+ | ||
+ * Redistributions of source code must retain the above copyright notice, this | ||
+ list of conditions and the following disclaimer. | ||
+ | ||
+ * Redistributions in binary form must reproduce the above copyright notice, | ||
+ this list of conditions and the following disclaimer in the documentation | ||
+ and/or other materials provided with the distribution. | ||
+ | ||
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+ */ | ||
+ | ||
+ | ||
+/// The `SpringAnimation` struct is an implementation of | ||
+/// `ValueAnimationType` that uses a configurable spring function to animate | ||
+/// the value. | ||
+/// | ||
+/// Spring animations do not have a duration. Instead, you should configure | ||
+/// the properties in 'configuration' to customize the way the spring will | ||
+/// change the value as the simulation advances. The animation is finished | ||
+/// when the spring has come to rest at its target value. | ||
+/// | ||
+/// SpringAnimation instances use a `DynamicSimulation` containing a | ||
+/// `SpringFunction` internally to perform the spring calculations. | ||
+public struct SpringAnimation<Value: VectorConvertible>: ValueAnimationType { | ||
+ | ||
+ // The underlying spring simulation. | ||
+ private var solver: DynamicSolver<SpringFunction<Value.Vector>> | ||
+ | ||
+ /// Creates a new `SpringAnimation` instance. | ||
+ /// | ||
+ /// - parameter from: The value of the animation at time `0`. | ||
+ /// - parameter target: The final value that the spring will settle on at | ||
+ /// the end of the animation. | ||
+ /// - parameter velocity: The initial velocity at the start of the animation. | ||
+ public init(from: Value, target: Value, velocity: Value = Value.zero) { | ||
+ let f = SpringFunction(target: target.vector) | ||
+ solver = DynamicSolver(function: f, value: from.vector, velocity: velocity.vector) | ||
+ } | ||
+ | ||
+ /// Advances the animation. | ||
+ /// | ||
+ /// - parameter elapsed: The time (in seconds) to advance the animation. | ||
+ public mutating func advance(elapsed: Double) { | ||
+ solver.advance(elapsed) | ||
+ } | ||
+ | ||
+ /// Returns `true` if the spring has reached a settled state. | ||
+ public var finished: Bool { | ||
+ return solver.settled | ||
+ } | ||
+ | ||
+ /// The current value. | ||
+ public var value: Value { | ||
+ get { return Value(vector: solver.value) } | ||
+ set { solver.value = newValue.vector } | ||
+ } | ||
+ | ||
+ /// The current velocity. | ||
+ public var velocity: Value { | ||
+ get { return Value(vector: solver.velocity) } | ||
+ set { solver.velocity = newValue.vector } | ||
+ } | ||
+ | ||
+ | ||
+ /// The value that the spring will move toward. | ||
+ public var target: Value { | ||
+ get { return Value(vector: solver.function.target) } | ||
+ set { solver.function.target = newValue.vector } | ||
+ } | ||
+ | ||
+ /// The configuration of the underlying spring simulation. | ||
+ public var configuration: SpringConfiguration { | ||
+ get { return solver.function.configuration } | ||
+ set { solver.function.configuration = newValue } | ||
+ } | ||
+} | ||
+ | ||
+ | ||
+public extension Animatable { | ||
+ | ||
+ /// Animates to the given value using a spring function. | ||
+ /// | ||
+ /// - parameter to: The value to animate to. | ||
+ /// - parameter initialVelocity: An optional velocity to use at time `0`. | ||
+ /// If no velocity is given, the current velocity of the `Animatable` instance | ||
+ /// will be used (if another animation is in progress). | ||
+ /// - parameter configuration: A spring configuration instance to use. | ||
+ /// - parameter completion: A closure that will be called at the end of the | ||
+ /// animation. | ||
+ public func springTo(to: Value, initialVelocity: Value? = nil, configuration: SpringConfiguration = SpringConfiguration(), completion: Completion? = nil) { | ||
+ var a = SpringAnimation(from: value, target: to, velocity: initialVelocity ?? velocity) | ||
+ a.configuration = configuration | ||
+ animate(a, completion: completion) | ||
+ } | ||
+} | ||
+ | ||
+public extension VectorConvertible { | ||
+ | ||
+ /// Animates to the given value using a spring function. | ||
+ /// | ||
+ /// - parameter to: The value to animate to. | ||
+ /// - parameter callback: A closure that will be called at each step of the animation. | ||
+ /// - returns: The animator instance that is powering the animation. | ||
+ public func springTo(to: Self, configuration: SpringConfiguration, callback: (Self) -> Void) -> Animator<SpringAnimation<Self>> { | ||
+ var a = SpringAnimation(from: self, target: to, velocity: Self.zero) | ||
+ a.configuration = configuration | ||
+ let animator = AnimatorContext.shared.animate(a) | ||
+ animator.changed.observe({ (a) -> Void in | ||
+ callback(a.value) | ||
+ }) | ||
+ return animator | ||
+ } | ||
+ | ||
+} |
43
Advance/Animation/ValueAnimationType.swift
@@ -0,0 +1,43 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import Foundation | ||
+ | ||
+/// Conforming types can be used to animate values conforming to `VectorConvertible`. | ||
+public protocol ValueAnimationType: AnimationType { | ||
+ | ||
+ /// The type of value to be animated. | ||
+ typealias Value: VectorConvertible | ||
+ | ||
+ /// The current value of the animation. | ||
+ var value: Value { get } | ||
+ | ||
+ /// The current velocity of the animation. | ||
+ var velocity: Value { get } | ||
+ | ||
+} |
197
Advance/Animator/Animator.swift
@@ -0,0 +1,197 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+/// The state of an `Animator` instance. | ||
+public enum AnimatorState: Equatable { | ||
+ /// The animator has not yet started. | ||
+ case Pending | ||
+ | ||
+ /// The animator is currently running. | ||
+ case Running | ||
+ | ||
+ /// The animator has stopped running. | ||
+ case Completed(AnimatorResult) | ||
+} | ||
+ | ||
+/// Equatable. | ||
+public func ==(lhs: AnimatorState, rhs: AnimatorState) -> Bool { | ||
+ switch (lhs, rhs) { | ||
+ case (.Pending, .Pending): | ||
+ return true | ||
+ case (.Running, .Running): | ||
+ return true | ||
+ case (.Completed(let l), .Completed(let r)): | ||
+ return l == r | ||
+ default: | ||
+ return false | ||
+ } | ||
+} | ||
+ | ||
+/// The possible result cases of an animator. | ||
+public enum AnimatorResult { | ||
+ /// The animator was cancelled before the animation completed. | ||
+ case Cancelled | ||
+ | ||
+ /// The animator successfully ran the animation until it was finished. | ||
+ case Finished | ||
+} | ||
+ | ||
+/// Runs an animation until the animations finishes, or until `cancel()` | ||
+/// is called. | ||
+/// | ||
+/// The `Animator` class is one-shot: It runs the animation precisely one time. | ||
+/// | ||
+/// It starts in the `Pending` state. From here either: | ||
+/// - It enters the running state. This occurs if start() is called. | ||
+/// - It is cancelled. This occurs if cancel() is called, and causes the animator | ||
+/// to enter the `Completed` state, with a result of `Cancelled`. | ||
+/// | ||
+/// After entering the `Running` state, the `started` event is fired. The | ||
+/// animation then updates on every frame, triggering the `changed` event each | ||
+/// time, until either: | ||
+/// - The animation finishes on its own, after which the animator enters the | ||
+/// `Completed` state, with a result of `Finished`. | ||
+/// - `cancel()` is called, after which the animator enters the `Completed` | ||
+/// state, with a result of `Cancelled`. | ||
+/// | ||
+/// When the animator enters the `Completed` state, it triggers either the | ||
+/// `cancelled` or `finished` event, depending on the result. After entering | ||
+/// the `Completed` state, the animator is finished and no further state changes | ||
+/// can occur. | ||
+public final class Animator<A: AnimationType> { | ||
+ | ||
+ private lazy var subscription: LoopSubscription? = { | ||
+ | ||
+ let s = Loop.shared.subscribe() | ||
+ | ||
+ s.advanced.observe({ [unowned self] (elapsed) -> Void in | ||
+ guard self.state == .Running else { return } | ||
+ self.animation.advance(elapsed) | ||
+ self.changed.fire(self.animation) | ||
+ if self.animation.finished == true { | ||
+ self.finish() | ||
+ } | ||
+ }) | ||
+ | ||
+ return s | ||
+ }() | ||
+ | ||
+ /// The current state of the animator. Animators begin in a running state, | ||
+ /// and they are guarenteed to transition into either the cancelled or | ||
+ /// finished state exactly one time – no further state changes are allowed. | ||
+ private (set) public var state: AnimatorState = .Pending { | ||
+ willSet { | ||
+ guard newValue != state else { return } | ||
+ switch newValue { | ||
+ case .Pending: | ||
+ assert(false, "Invalid state transition") | ||
+ case .Running: | ||
+ assert(state == .Pending, "Invalid state transition") | ||
+ case .Completed(_): | ||
+ assert(state == .Pending || state == .Running, "Invalid state transition") | ||
+ } | ||
+ } | ||
+ didSet { | ||
+ guard oldValue != state else { return } | ||
+ switch state { | ||
+ case .Pending: | ||
+ break | ||
+ case .Running: | ||
+ started.close(animation) | ||
+ case .Completed(let result): | ||
+ switch result { | ||
+ case .Cancelled: | ||
+ cancelled.close(animation) | ||
+ case .Finished: | ||
+ finished.close(animation) | ||
+ } | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ /// The animation that is being run. | ||
+ private (set) public var animation: A | ||
+ | ||
+ /// Fired when the animator starts running | ||
+ public let started = Event<A>() | ||
+ | ||
+ /// Fired after every animation update. | ||
+ public let changed = Event<A>() | ||
+ | ||
+ /// Fired if the animator is cancelled. | ||
+ public let cancelled = Event<A>() | ||
+ | ||
+ /// Fired when the animation finishes. | ||
+ public let finished = Event<A>() | ||
+ | ||
+ /// Creates a new animator. | ||
+ /// | ||
+ /// - parameter animation: The animation to be run. | ||
+ public init(animation: A, loop: Loop = Loop.shared) { | ||
+ self.animation = animation | ||
+ } | ||
+ | ||
+ deinit { | ||
+ if state == .Running || state == .Pending { | ||
+ cancel() | ||
+ } | ||
+ } | ||
+ | ||
+ | ||
+ /// Starts a pending animation | ||
+ /// | ||
+ /// If the animator is not in a `pending` state, calling start() will have | ||
+ /// no effect. | ||
+ public func start() { | ||
+ guard state == .Pending else { return } | ||
+ state = .Running | ||
+ if animation.finished == true { | ||
+ finish() | ||
+ } else { | ||
+ subscription?.paused = false | ||
+ } | ||
+ } | ||
+ | ||
+ /// Cancels the animation. | ||
+ /// | ||
+ /// If the animator is in a `running` or `pending` state, this will immediately | ||
+ /// transition to the `cancelled` state (and call any `onCancel` observers). | ||
+ /// If the animator is already cancelled or finished, calling `cancel()` will | ||
+ /// have no effect. | ||
+ public func cancel() { | ||
+ guard state == .Running || state == .Pending else { return } | ||
+ state = .Completed(.Cancelled) | ||
+ subscription = nil | ||
+ } | ||
+ | ||
+ private func finish() { | ||
+ assert(state == .Running || state == .Pending) | ||
+ state = .Completed(.Finished) | ||
+ subscription = nil | ||
+ } | ||
+} |
85
Advance/Animator/AnimatorContext.swift
@@ -0,0 +1,85 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+/// Creates and manages `Animator` instances, retaining them until completion. | ||
+public final class AnimatorContext { | ||
+ | ||
+ /// The default context. | ||
+ public static let shared = AnimatorContext() | ||
+ | ||
+ private var animators: Set<AnimatorWrapper> = [] | ||
+ | ||
+ /// Creates a new animator context. | ||
+ public init() {} | ||
+ | ||
+ deinit { | ||
+ for a in animators { | ||
+ a.animator.cancel() | ||
+ } | ||
+ } | ||
+ | ||
+ /// Generates a new animator instance to run the given animation. | ||
+ /// | ||
+ /// - parameter animation: The animation to run. | ||
+ /// - returns: The newly generated `Animator` instance. | ||
+ public func animate<A: AnimationType>(animation: A) -> Animator<A> { | ||
+ let a = Animator(animation: animation) | ||
+ a.start() | ||
+ if a.state == .Running { | ||
+ let wrapper = AnimatorWrapper(animator: a) | ||
+ animators.insert(wrapper) | ||
+ let obs: (A)->Void = { [weak self] (a) -> Void in | ||
+ self?.animators.remove(wrapper) | ||
+ } | ||
+ a.cancelled.observe(obs) | ||
+ a.finished.observe(obs) | ||
+ } | ||
+ return a | ||
+ } | ||
+} | ||
+ | ||
+private protocol AnimatorType: class { | ||
+ func cancel() | ||
+} | ||
+ | ||
+extension Animator: AnimatorType {} | ||
+ | ||
+private struct AnimatorWrapper: Hashable { | ||
+ let animator: AnimatorType | ||
+ init<A: AnimationType>(animator: Animator<A>) { | ||
+ self.animator = animator | ||
+ } | ||
+ | ||
+ var hashValue: Int { | ||
+ return unsafeAddressOf(animator).hashValue | ||
+ } | ||
+} | ||
+ | ||
+private func ==(lhs: AnimatorWrapper, rhs: AnimatorWrapper) -> Bool { | ||
+ return lhs.animator === rhs.animator | ||
+} |
145
Advance/Events/Event.swift
@@ -0,0 +1,145 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+/// The state of the event. | ||
+public enum EventState<T> { | ||
+ | ||
+ /// The event is active and accepting new payloads. | ||
+ case Active | ||
+ | ||
+ /// The event is closed. Subsequent calls to `fire(payload:)` will be ignored. | ||
+ case Closed(T) | ||
+} | ||
+ | ||
+/// A simple EventType implementation. | ||
+public final class Event<T> { | ||
+ | ||
+ /// The current state of the event. | ||
+ private (set) public var state = EventState<T>.Active | ||
+ | ||
+ public typealias PayloadType = T | ||
+ public typealias Observer = (PayloadType) -> Void | ||
+ | ||
+ private var observers: [Observer] = [] | ||
+ private var keyedObservers: [String:Observer] = [:] | ||
+ | ||
+ /// Returns `true` if the event state is `Closed`. | ||
+ public var closed: Bool { | ||
+ if case .Active = state { | ||
+ return false | ||
+ } else { | ||
+ return true | ||
+ } | ||
+ } | ||
+ | ||
+ private var closedValue: T? { | ||
+ if case let .Closed(v) = state { | ||
+ return v | ||
+ } | ||
+ return nil | ||
+ } | ||
+ | ||
+ /// Notifies observers. | ||
+ /// | ||
+ /// If the event has been closed, this has no effect. | ||
+ /// | ||
+ /// - parameter payload: A value to be passed to each observer. | ||
+ public func fire(payload: T) { | ||
+ guard closed == false else { return } | ||
+ deliver(payload) | ||
+ } | ||
+ | ||
+ /// Closes the event. | ||
+ /// | ||
+ /// If the event has already been closed, this has no effect. | ||
+ /// | ||
+ /// Calls all observers with the given payload. Once an event is closed, | ||
+ /// calling `fire(payload:)` or `close(payload:)` will have no effect. | ||
+ /// Adding an observer after an event is closed will simply call the | ||
+ /// observer synchronously with the payload that the event was closed | ||
+ /// with. | ||
+ public func close(payload: T) { | ||
+ guard closed == false else { return } | ||
+ state = .Closed(payload) | ||
+ deliver(payload) | ||
+ } | ||
+ | ||
+ private func deliver(payload: T) { | ||
+ for o in observers { | ||
+ o(payload) | ||
+ } | ||
+ for o in keyedObservers.values { | ||
+ o(payload) | ||
+ } | ||
+ } | ||
+ | ||
+ /// Adds an observer. | ||
+ /// | ||
+ /// Adding an observer after an event is closed will simply call the | ||
+ /// observer synchronously with the payload that the event was closed | ||
+ /// with. | ||
+ /// | ||
+ /// - parameter observer: A closure that will be executed when this event | ||
+ /// is fired. | ||
+ public func observe(observer: Observer) { | ||
+ guard closed == false else { | ||
+ observer(closedValue!) | ||
+ return | ||
+ } | ||
+ observers.append(observer) | ||
+ } | ||
+ | ||
+ /// Adds an observer for a key. | ||
+ /// | ||
+ /// Adding an observer after an event is closed will simply call the | ||
+ /// observer synchronously with the payload that the event was closed | ||
+ /// with. | ||
+ /// | ||
+ /// - seeAlso: func unobserve(key:) | ||
+ /// - parameter observer: A closure that will be executed when this event | ||
+ /// is fired. | ||
+ /// - parameter key: A string that identifies this observer, which can | ||
+ /// be used to remove the observer. | ||
+ public func observe(observer: Observer, key: String) { | ||
+ guard closed == false else { | ||
+ observer(closedValue!) | ||
+ return | ||
+ } | ||
+ keyedObservers[key] = observer | ||
+ } | ||
+ | ||
+ /// Removed an observer with a given key. | ||
+ /// | ||
+ /// - seeAlso: func observe(observer:key:) | ||
+ /// - parameter key: A string that identifies the observer to be removed. | ||
+ /// If an observer does not exist for the given key, the method returns | ||
+ /// without impact. | ||
+ public func unobserve(key: String) { | ||
+ keyedObservers.removeValueForKey(key) | ||
+ } | ||
+} |
51
Advance/Extensions/CAMediaTimingFunction.swift
@@ -0,0 +1,51 @@ | ||
+/* | ||
+ | ||
+ Copyright (c) 2016, Storehouse Media Inc. | ||
+ All rights reserved. | ||
+ | ||
+ Redistribution and use in source and binary forms, with or without | ||
+ modification, are permitted provided that the following conditions are met: | ||
+ | ||
+ * Redistributions of source code must retain the above copyright notice, this | ||
+ list of conditions and the following disclaimer. | ||
+ | ||
+ * Redistributions in binary form must reproduce the above copyright notice, | ||
+ this list of conditions and the following disclaimer in the documentation | ||
+ and/or other materials provided with the distribution. | ||
+ | ||
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+ */ | ||
+ | ||
+import QuartzCore | ||
+ | ||
+extension CAMediaTimingFunction: TimingFunctionType { | ||
+ | ||
+ /// Solves for the given time with the specified precision. | ||
+ public func solveForTime(x: Scalar, epsilon: Scalar) -> Scalar { | ||
+ return unitBezier.solve(x, epsilon: epsilon) | ||
+ } | ||
+ | ||
+ /// Returns a `UnitBezier` instance created from this timing function's | ||
+ /// control points. | ||
+ public var unitBezier: UnitBezier { | ||
+ let pointsPointer1 = UnsafeMutablePointer<Float>.alloc(2) | ||
+ let pointsPointer2 = UnsafeMutablePointer<Float>.alloc(2) | ||
+ getControlPointAtIndex(1, values: pointsPointer1) | ||
+ getControlPointAtIndex(2, values: pointsPointer2) | ||
+ let b = UnitBezier(p1x: Scalar(pointsPointer1[0]), p1y: Scalar(pointsPointer1[1]), p2x: Scalar(pointsPointer2[0]), p2y: Scalar(pointsPointer2[1])) | ||
+ pointsPointer1.dealloc(2) | ||
+ pointsPointer2.dealloc(2) | ||
+ return b | ||
+ } | ||
+ | ||
+} |
26
Advance/Info.plist
@@ -0,0 +1,26 @@ | ||
+<?xml version="1.0" encoding="UTF-8"?> | ||
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
+<plist version="1.0"> | ||
+<dict> | ||
+ <key>CFBundleDevelopmentRegion</key> | ||
+ <string>en</string> | ||
+ <key>CFBundleExecutable</key> | ||
+ <string>$(EXECUTABLE_NAME)</string> | ||
+ <key>CFBundleIdentifier</key> | ||
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | ||
+ <key>CFBundleInfoDictionaryVersion</key> | ||
+ <string>6.0</string> | ||
+ <key>CFBundleName</key> | ||
+ <string>$(PRODUCT_NAME)</string> | ||
+ <key>CFBundlePackageType</key> | ||
+ <string>FMWK</string> | ||
+ <key>CFBundleShortVersionString</key> | ||
+ <string>0.9</string> | ||
+ <key>CFBundleSignature</key> | ||
+ <string>????</string> | ||
+ <key>CFBundleVersion</key> | ||
+ <string>$(CURRENT_PROJECT_VERSION)</string> | ||
+ <key>NSPrincipalClass</key> | ||
+ <string></string> | ||
+</dict> | ||
+</plist> |
169
Advance/Loop/Loop.swift
@@ -0,0 +1,169 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import Foundation | ||
+import QuartzCore | ||
+ | ||
+ | ||
+/// The animation loop that powers all of the Animation framework. | ||
+public final class Loop { | ||
+ | ||
+ /// The default loop. | ||
+ public static let shared = Loop() | ||
+ | ||
+ private var currentAnimationTime: Double = 0.0 | ||
+ | ||
+ private lazy var displayLink: CADisplayLink = { | ||
+ let link = CADisplayLink(target: self, selector: "displayLinkDidFire:") | ||
+ link.paused = true | ||
+ link.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes) | ||
+ return link | ||
+ }() | ||
+ | ||
+ private var tokens: Set<LoopSubscription.Token> = [] | ||
+ | ||
+ /// All currently active subscriptions. | ||
+ public var subscriptions: [LoopSubscription] { | ||
+ return tokens.filter({$0.subscription != nil}).map({ $0.subscription! }) | ||
+ } | ||
+ | ||
+ private init() { | ||
+ | ||
+ } | ||
+ | ||
+ /// Generates and returns a subscription for this loop. | ||
+ /// | ||
+ /// **Note that loops are retained by subscriptions.** | ||
+ public func subscribe() -> LoopSubscription { | ||
+ return LoopSubscription(loop: self) | ||
+ } | ||
+ | ||
+ private func add(subscription: LoopSubscription) { | ||
+ assert(subscription.loop === self) | ||
+ tokens.insert(subscription.token) | ||
+ startIfNeeded() | ||
+ } | ||
+ | ||
+ private func remove(subscription: LoopSubscription) { | ||
+ assert(subscription.loop === self) | ||
+ tokens.remove(subscription.token) | ||
+ stopIfPossible() | ||
+ } | ||
+ | ||
+ private func startIfNeeded() { | ||
+ guard tokens.count > 0 else { return } | ||
+ guard displayLink.paused == true else { return } | ||
+ displayLink.paused = false | ||
+ currentAnimationTime = 0 | ||
+ } | ||
+ | ||
+ private func stopIfPossible() { | ||
+ guard tokens.count == 0 else { return } | ||
+ guard displayLink.paused == false else { return } | ||
+ displayLink.paused = true | ||
+ } | ||
+ | ||
+ dynamic private func displayLinkDidFire(displayLink: CADisplayLink) { | ||
+ | ||
+ let timestamp = max(displayLink.timestamp, currentAnimationTime) | ||
+ | ||
+ if currentAnimationTime == 0.0 { | ||
+ currentAnimationTime = timestamp - displayLink.duration | ||
+ } | ||
+ | ||
+ let elapsed = timestamp - currentAnimationTime | ||
+ currentAnimationTime = timestamp | ||
+ | ||
+ let t = tokens | ||
+ | ||
+ // Loop through once to let everything update the animation state... | ||
+ for token in t { | ||
+ guard token.subscription != nil else { | ||
+ tokens.remove(token) | ||
+ continue | ||
+ } | ||
+ token.subscription?.advance(elapsed) | ||
+ } | ||
+ | ||
+ stopIfPossible() | ||
+ } | ||
+} | ||
+ | ||
+/// The interface through which consumers can respond to animation loop updates. | ||
+public final class LoopSubscription { | ||
+ | ||
+ private final class Token: Hashable { | ||
+ weak var subscription: LoopSubscription? | ||
+ init(subscription: LoopSubscription) { | ||
+ self.subscription = subscription | ||
+ } | ||
+ var hashValue: Int { | ||
+ return unsafeAddressOf(self).hashValue | ||
+ } | ||
+ } | ||
+ | ||
+ private lazy var token: Token = { | ||
+ return Token(subscription: self) | ||
+ }() | ||
+ | ||
+ /// Fired during the update phase of each turn of the loop. Contains | ||
+ /// the elapsed time for the current animation frame. | ||
+ public let advanced = Event<Double>() | ||
+ | ||
+ /// The associated loop instance. | ||
+ public let loop: Loop | ||
+ | ||
+ /// Any time animation updates are not required, the subscription should | ||
+ /// be paused for efficiency. | ||
+ public var paused: Bool = true { | ||
+ didSet { | ||
+ guard paused != oldValue else { return } | ||
+ if paused { | ||
+ loop.remove(self) | ||
+ } else { | ||
+ loop.add(self) | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ private init(loop: Loop) { | ||
+ self.loop = loop | ||
+ } | ||
+ | ||
+ deinit { | ||
+ paused = true | ||
+ } | ||
+ | ||
+ private func advance(elapsed: Double) { | ||
+ advanced.fire(elapsed) | ||
+ } | ||
+} | ||
+ | ||
+private func ==(lhs: LoopSubscription.Token, rhs: LoopSubscription.Token) -> Bool { | ||
+ return lhs === rhs | ||
+} |
58
Advance/Simulation/DecayFunction.swift
@@ -0,0 +1,58 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+/// Gradually reduces velocity until it equals `Vector.zero`. | ||
+public struct DecayFunction<Vector: VectorType>: DynamicFunctionType { | ||
+ | ||
+ /// How close to 0 each component of the velocity must be before the | ||
+ /// simulation is allowed to settle. | ||
+ public var threshold: Scalar = 0.1 | ||
+ | ||
+ /// How much to erode the velocity. | ||
+ public var drag: Scalar = 3.0 | ||
+ | ||
+ /// Creates a new `DecayFunction` instance. | ||
+ public init() {} | ||
+ | ||
+ /// Calculates acceleration for a given state of the simulation. | ||
+ public func acceleration(value: Vector, velocity: Vector) -> Vector { | ||
+ return -drag * velocity | ||
+ } | ||
+ | ||
+ /// Returns `true` if the simulation can become settled. | ||
+ public func canSettle(value: Vector, velocity: Vector) -> Bool { | ||
+ let min = Vector(scalar: -threshold) | ||
+ let max = Vector(scalar: threshold) | ||
+ return velocity.clamped(min: min, max: max) == velocity | ||
+ } | ||
+ | ||
+ /// Returns the value to settle on. | ||
+ public func settledValue(value: Vector, velocity: Vector) -> Vector { | ||
+ return value | ||
+ } | ||
+} |
53
Advance/Simulation/DynamicFunctionType.swift
@@ -0,0 +1,53 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+/// Conforming types implement a dynamic function that models changes to | ||
+/// a vector over time. | ||
+public protocol DynamicFunctionType { | ||
+ typealias Vector: VectorType | ||
+ | ||
+ /// The computed acceleration for a given simulation state. | ||
+ /// | ||
+ /// - parameter value: The current value of the simulation. | ||
+ /// - parameter velocity: The current velocity of the simulation. | ||
+ /// - returns: A vector containing the acceleration (in units per second) | ||
+ /// based on `value` and `velocity`. | ||
+ func acceleration(value: Vector, velocity: Vector) -> Vector | ||
+ | ||
+ /// Returns `true` if the simulation should be allowed to enter its settled | ||
+ /// state. For example, a decay function may check that `velocity` is below | ||
+ /// a minimum threshold. | ||
+ func canSettle(value: Vector, velocity: Vector) -> Bool | ||
+ | ||
+ /// Returns the value for the simulation as it enters the settled state. | ||
+ /// | ||
+ /// - parameter value: The current value of the simulation. | ||
+ /// - parameter velocity: The current velocity of the simulation. | ||
+ /// - returns: The value that the simulation will settle on. | ||
+ func settledValue(value: Vector, velocity: Vector) -> Vector | ||
+} |
216
Advance/Simulation/DynamicSimulation.swift
@@ -0,0 +1,216 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import Foundation | ||
+ | ||
+ | ||
+/// `DynamicSolver` simulates changes to a value over time, based on | ||
+/// a function that calculates acceleration after each time step. | ||
+/// | ||
+/// [The RK4 method](https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods) | ||
+/// is used to integrate the acceleration function. | ||
+/// | ||
+/// Constant time steps are not guarenteed elsewhere in the framework. Due to | ||
+/// the nature of dynamic functions, however, it is desirable to maintain | ||
+/// a constant update interval for a dynamic simulation. `DynamicSolver` | ||
+/// instances maintain their own internal time state. When `advance(elapsed:) | ||
+/// is called on an instance, it may run an arbitrary number of time steps | ||
+/// internally (and call the underlying function as needed) in order to "catch | ||
+/// up" to the outside time. It then uses linear interpolation to match the | ||
+/// internal state to the required external time in order to return the most | ||
+/// precise calculations. | ||
+public struct DynamicSolver<F: DynamicFunctionType> : Advanceable { | ||
+ | ||
+ // The internal time step. 0.008 == 120fps (double the typical screen refresh | ||
+ // rate). The math required to solve most functions is easy for modern | ||
+ // CPUs, but it's worth experimenting with this value if solver calculations | ||
+ // ever become a performance bottleneck. | ||
+ private let tickTime: Double = 0.008 | ||
+ | ||
+ /// The function driving the simulation. | ||
+ public var function: F { | ||
+ didSet { | ||
+ // If the function changes, we need to make sure that its new state | ||
+ // will allow the solver to settle. | ||
+ settled = false | ||
+ settleIfPossible() | ||
+ } | ||
+ } | ||
+ | ||
+ // Tracks the delta between external and internal time. | ||
+ private var timeAccumulator: Double = 0.0 | ||
+ | ||
+ /// Returns `true` if the solver has settled and does not currently | ||
+ /// need to be advanced on each frame. | ||
+ private (set) public var settled: Bool = false | ||
+ | ||
+ // The current state of the solver. | ||
+ private var simulationState: DynamicSolverState<F.Vector> | ||
+ | ||
+ // The latest interpolated state that we use to return values to the outside | ||
+ // world. | ||
+ private var interpolatedState: DynamicSolverState<F.Vector> | ||
+ | ||
+ /// Creates a new `DynamicSolver` instance. | ||
+ /// | ||
+ /// - parameter function: The function that will drive the simulation. | ||
+ /// - parameter value: The initial value of the simulation. | ||
+ /// - parameter velocity: The initial velocity of the simulation. | ||
+ public init(function: F, value: F.Vector, velocity: F.Vector = F.Vector.zero) { | ||
+ self.function = function | ||
+ simulationState = DynamicSolverState(value: value, velocity: velocity) | ||
+ interpolatedState = simulationState | ||
+ settleIfPossible() | ||
+ } | ||
+ | ||
+ private mutating func settleIfPossible() { | ||
+ guard settled == false else { return } | ||
+ if function.canSettle(simulationState.value, velocity: simulationState.velocity) { | ||
+ simulationState.value = function.settledValue(simulationState.value, velocity: simulationState.velocity) | ||
+ simulationState.velocity = F.Vector.zero | ||
+ interpolatedState = simulationState | ||
+ settled = true | ||
+ } | ||
+ } | ||
+ | ||
+ /// Advances the simulation. | ||
+ /// | ||
+ /// - parameter elapsed: The duration by which to advance the simulation | ||
+ /// in seconds. | ||
+ public mutating func advance(elapsed: Double) { | ||
+ guard settled == false else { return } | ||
+ | ||
+ // Limit to 10 physics ticks per update, should never come close. | ||
+ let t = min(elapsed, tickTime * 10.0) | ||
+ | ||
+ // Add the new time to the accumulator. This can be thought of as the | ||
+ // delta between the time of the current physics state, and the time | ||
+ // that we need to solve for. When it is positive, we need to advance | ||
+ // the simulation to catch up. | ||
+ timeAccumulator += t | ||
+ | ||
+ var previousState = simulationState | ||
+ | ||
+ // Advance the simulation until the time accumulator is negative – | ||
+ // this means that the current state is ahead of the needed time. | ||
+ while timeAccumulator > 0.0 { | ||
+ if settled { | ||
+ break | ||
+ } | ||
+ previousState = simulationState | ||
+ simulationState = simulationState.integrate(function, time: tickTime) | ||
+ timeAccumulator -= tickTime | ||
+ } | ||
+ | ||
+ assert(timeAccumulator <= 0.0) | ||
+ assert(timeAccumulator > -tickTime) | ||
+ | ||
+ // If snapping is possible, we can just do that and avoid interpolation. | ||
+ settleIfPossible() | ||
+ | ||
+ if settled == false { | ||
+ // The simulation did not settle. At this point, the latest state | ||
+ // was calculated for some time in the future of what we need | ||
+ // to satisfy `elapsed`. We can figure out the alpha in between | ||
+ // `previousState` and `simulationState`, and interpolate. This | ||
+ // will let us provide a more accurate value to the outside world, | ||
+ // while maintaining a consistent time step internally. | ||
+ let alpha = Scalar((tickTime + timeAccumulator) / tickTime) | ||
+ interpolatedState = previousState | ||
+ interpolatedState.value = interpolatedState.value.interpolatedTo(simulationState.value, alpha: alpha) | ||
+ interpolatedState.velocity = interpolatedState.velocity.interpolatedTo(simulationState.velocity, alpha: alpha) | ||
+ } | ||
+ } | ||
+ | ||
+ /// The current value. | ||
+ public var value: F.Vector { | ||
+ get { return interpolatedState.value } | ||
+ set { | ||
+ interpolatedState.value = newValue | ||
+ simulationState.value = newValue | ||
+ settled = false | ||
+ settleIfPossible() | ||
+ } | ||
+ } | ||
+ | ||
+ /// The current velocity. | ||
+ public var velocity: F.Vector { | ||
+ get { return interpolatedState.velocity } | ||
+ set { | ||
+ interpolatedState.velocity = newValue | ||
+ simulationState.velocity = newValue | ||
+ settled = false | ||
+ settleIfPossible() | ||
+ } | ||
+ } | ||
+} | ||
+ | ||
+private struct DynamicSolverState<Vector: VectorType> { | ||
+ var value: Vector | ||
+ var velocity: Vector | ||
+ init(value: Vector, velocity: Vector) { | ||
+ self.value = value | ||
+ self.velocity = velocity | ||
+ } | ||
+} | ||
+ | ||
+private extension DynamicSolverState { | ||
+ typealias Derivative = DynamicSolverState<Vector> | ||
+ | ||
+ /// RK4 Integration. | ||
+ func integrate<F: DynamicFunctionType where F.Vector == Vector>(function: F, time: Double) -> DynamicSolverState<Vector> { | ||
+ let initial = Derivative(value:Vector.zero, velocity: Vector.zero) | ||
+ | ||
+ let a = evaluate(function, time: 0.0, derivative: initial) | ||
+ let b = evaluate(function, time: time * 0.5, derivative: a) | ||
+ let c = evaluate(function, time: time * 0.5, derivative: b) | ||
+ let d = evaluate(function, time: time, derivative: c) | ||
+ | ||
+ var dxdt = a.value | ||
+ dxdt += (2.0 * (b.value + c.value)) + d.value | ||
+ dxdt = Scalar(1.0/6.0) * dxdt | ||
+ | ||
+ var dvdt = a.velocity | ||
+ dvdt += (2.0 * (b.velocity + c.velocity)) + d.velocity | ||
+ dvdt = Scalar(1.0/6.0) * dvdt | ||
+ | ||
+ | ||
+ let val = value + Scalar(time) * dxdt | ||
+ let vel = velocity + Scalar(time) * dvdt | ||
+ | ||
+ return DynamicSolverState(value: val, velocity: vel) | ||
+ } | ||
+ | ||
+ private func evaluate<F: DynamicFunctionType where F.Vector == Vector>(function: F, time: Double, derivative: Derivative) -> Derivative { | ||
+ let val = value + Scalar(time) * derivative.value | ||
+ let vel = velocity + Scalar(time) * derivative.velocity | ||
+ let accel = function.acceleration(val, velocity: vel) | ||
+ let d = Derivative(value: vel, velocity: accel) | ||
+ return d | ||
+ } | ||
+} |
126
Advance/Simulation/Spring.swift
@@ -0,0 +1,126 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+/// Animates changes to a value using spring physics. | ||
+/// | ||
+/// Instances of `Spring` should be used in situations where spring physics | ||
+/// are the only animation type required, or when convenient access to the | ||
+/// properties of a running spring simulation is needed. | ||
+/// | ||
+/// The focused API of this class makes it more convenient in such cases | ||
+/// than using an `Animatable` instance, where a new spring animation would | ||
+/// have to be added each time the spring needed to be modified. | ||
+/// | ||
+/// ``` | ||
+/// let s = Spring(value: CGPoint.zero) | ||
+/// | ||
+/// s.changed.observe { (value) in | ||
+/// // do something with the value when it changes | ||
+/// } | ||
+/// | ||
+/// s.target = CGPoint(x: 100.0, y: 200.0) | ||
+/// // Off it goes! | ||
+/// ``` | ||
+public final class Spring<T: VectorConvertible> { | ||
+ | ||
+ private var solver: DynamicSolver<SpringFunction<T.Vector>> { | ||
+ didSet { | ||
+ lastNotifiedValue = T(vector: solver.value) | ||
+ if solver.settled == false && subscription.paused == true { | ||
+ subscription.paused = false | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ private lazy var subscription: LoopSubscription = { | ||
+ let s = Loop.shared.subscribe() | ||
+ | ||
+ s.advanced.observe({ [unowned self] (elapsed) -> Void in | ||
+ self.solver.advance(elapsed) | ||
+ if self.solver.settled { | ||
+ self.subscription.paused = true | ||
+ } | ||
+ }) | ||
+ | ||
+ return s | ||
+ }() | ||
+ | ||
+ /// Fires when `value` has changed. | ||
+ public let changed = Event<T>() | ||
+ | ||
+ private var lastNotifiedValue: T { | ||
+ didSet { | ||
+ guard lastNotifiedValue != oldValue else { return } | ||
+ changed.fire(lastNotifiedValue) | ||
+ } | ||
+ } | ||
+ | ||
+ /// Creates a new `Spring` instance | ||
+ /// | ||
+ /// - parameter value: The initial value of the spring. The spring will be | ||
+ /// initialized with `target` and `value` equal to the given value, and | ||
+ /// a velocity of `0`. | ||
+ public init(value: T) { | ||
+ let f = SpringFunction(target: value.vector) | ||
+ solver = DynamicSolver(function: f, value: value.vector) | ||
+ lastNotifiedValue = value | ||
+ } | ||
+ | ||
+ /// Removes any current velocity and snaps the spring directly to the given value. | ||
+ public func reset(value: T) { | ||
+ var f = solver.function | ||
+ f.target = value.vector | ||
+ solver = DynamicSolver(function: f, value: value.vector) | ||
+ lastNotifiedValue = value | ||
+ } | ||
+ | ||
+ /// The current value of the spring. | ||
+ public var value: T { | ||
+ get { return T(vector: solver.value) } | ||
+ set { solver.value = newValue.vector } | ||
+ } | ||
+ | ||
+ /// The current velocity of the simulation. | ||
+ public var velocity: T { | ||
+ get { return T(vector: solver.velocity) } | ||
+ set { solver.velocity = newValue.vector } | ||
+ } | ||
+ | ||
+ /// The target value of the spring. As the simulation runs, `value` will be | ||
+ /// pulled toward this value. | ||
+ public var target: T { | ||
+ get { return T(vector: solver.function.target) } | ||
+ set { solver.function.target = newValue.vector } | ||
+ } | ||
+ | ||
+ /// Configuration options for the spring. | ||
+ public var configuration: SpringConfiguration { | ||
+ get { return solver.function.configuration } | ||
+ set { solver.function.configuration = newValue } | ||
+ } | ||
+} |
93
Advance/Simulation/SpringFunction.swift
@@ -0,0 +1,93 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import Foundation | ||
+import QuartzCore | ||
+ | ||
+/// The configuration options for a spring function. | ||
+public struct SpringConfiguration { | ||
+ | ||
+ /// Strength of the spring. | ||
+ public var tension: Scalar = 120.0 | ||
+ | ||
+ /// How damped the spring is. | ||
+ public var damping: Scalar = 12.0 | ||
+ | ||
+ /// The minimum scalar distance used for settling the spring simulation. | ||
+ public var threshold: Scalar = 0.1 | ||
+ | ||
+ /// Creates a new `SpringConfiguration` instance with default values. | ||
+ public init() {} | ||
+} | ||
+ | ||
+/// Implements a simple spring acceleration function. | ||
+public struct SpringFunction<T: VectorType>: DynamicFunctionType { | ||
+ | ||
+ /// The target of the spring. | ||
+ public var target: T | ||
+ | ||
+ /// Configuration options. | ||
+ public var configuration: SpringConfiguration | ||
+ | ||
+ /// Creates a new `SpringFunction` instance. | ||
+ /// | ||
+ /// - parameter target: The target of the new instance. | ||
+ public init(target: T) { | ||
+ self.target = target | ||
+ self.configuration = SpringConfiguration() | ||
+ } | ||
+ | ||
+ /// Calculates acceleration for a given state of the simulation. | ||
+ public func acceleration(value: T, velocity: T) -> T { | ||
+ let delta = value - target | ||
+ let accel = (-configuration.tension * delta) - (configuration.damping * velocity) | ||
+ return accel | ||
+ } | ||
+ | ||
+ /// Returns `true` if the simulation can become settled. | ||
+ public func canSettle(value: T, velocity: T) -> Bool { | ||
+ let min = Vector(scalar: -configuration.threshold) | ||
+ let max = Vector(scalar: configuration.threshold) | ||
+ | ||
+ if velocity.clamped(min: min, max: max) != velocity { | ||
+ return false | ||
+ } | ||
+ | ||
+ let valueDelta = value - target | ||
+ if valueDelta.clamped(min: min, max: max) != valueDelta { | ||
+ return false | ||
+ } | ||
+ | ||
+ return true | ||
+ } | ||
+ | ||
+ /// Returns the value to settle on. | ||
+ public func settledValue(value: T, velocity: T) -> T { | ||
+ return target | ||
+ } | ||
+} |
46
Advance/VectorConvertible/CGFloat+VectorConvertible.swift
@@ -0,0 +1,46 @@ | ||
+/* | ||
+ | ||
+ Copyright (c) 2016, Storehouse Media Inc. | ||
+ All rights reserved. | ||
+ | ||
+ Redistribution and use in source and binary forms, with or without | ||
+ modification, are permitted provided that the following conditions are met: | ||
+ | ||
+ * Redistributions of source code must retain the above copyright notice, this | ||
+ list of conditions and the following disclaimer. | ||
+ | ||
+ * Redistributions in binary form must reproduce the above copyright notice, | ||
+ this list of conditions and the following disclaimer in the documentation | ||
+ and/or other materials provided with the distribution. | ||
+ | ||
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+ */ | ||
+ | ||
+import CoreGraphics | ||
+ | ||
+/// Adds `VectorConvertible` conformance. | ||
+extension CGFloat: VectorConvertible { | ||
+ | ||
+ /// The underlying vector type. | ||
+ public typealias Vector = Vector1 | ||
+ | ||
+ /// Creates a new instance from a vector. | ||
+ public init(vector: Vector) { | ||
+ self.init(vector) | ||
+ } | ||
+ | ||
+ /// Returns the vector representation. | ||
+ public var vector: Vector { | ||
+ return Vector(self) | ||
+ } | ||
+} |
46
Advance/VectorConvertible/CGPoint+VectorConvertible.swift
@@ -0,0 +1,46 @@ | ||
+/* | ||
+ | ||
+ Copyright (c) 2016, Storehouse Media Inc. | ||
+ All rights reserved. | ||
+ | ||
+ Redistribution and use in source and binary forms, with or without | ||
+ modification, are permitted provided that the following conditions are met: | ||
+ | ||
+ * Redistributions of source code must retain the above copyright notice, this | ||
+ list of conditions and the following disclaimer. | ||
+ | ||
+ * Redistributions in binary form must reproduce the above copyright notice, | ||
+ this list of conditions and the following disclaimer in the documentation | ||
+ and/or other materials provided with the distribution. | ||
+ | ||
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+ */ | ||
+ | ||
+import CoreGraphics | ||
+ | ||
+/// Adds `VectorConvertible` conformance. | ||
+extension CGPoint: VectorConvertible { | ||
+ | ||
+ /// The underlying vector type. | ||
+ public typealias Vector = Vector2 | ||
+ | ||
+ /// Creates a new instance from a vector. | ||
+ public init(vector: Vector) { | ||
+ self.init(x: CGFloat(vector.x), y: CGFloat(vector.y)) | ||
+ } | ||
+ | ||
+ /// Returns the vector representation. | ||
+ public var vector: Vector { | ||
+ return Vector(Scalar(x), Scalar(y)) | ||
+ } | ||
+} |
47
Advance/VectorConvertible/CGRect+VectorConvertible.swift
@@ -0,0 +1,47 @@ | ||
+/* | ||
+ | ||
+ Copyright (c) 2016, Storehouse Media Inc. | ||
+ All rights reserved. | ||
+ | ||
+ Redistribution and use in source and binary forms, with or without | ||
+ modification, are permitted provided that the following conditions are met: | ||
+ | ||
+ * Redistributions of source code must retain the above copyright notice, this | ||
+ list of conditions and the following disclaimer. | ||
+ | ||
+ * Redistributions in binary form must reproduce the above copyright notice, | ||
+ this list of conditions and the following disclaimer in the documentation | ||
+ and/or other materials provided with the distribution. | ||
+ | ||
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+ */ | ||
+ | ||
+import CoreGraphics | ||
+ | ||
+/// Adds `VectorConvertible` conformance. | ||
+extension CGRect: VectorConvertible { | ||
+ | ||
+ /// The underlying vector type. | ||
+ public typealias Vector = Vector4 | ||
+ | ||
+ /// Creates a new instance from a vector. | ||
+ public init(vector: Vector) { | ||
+ origin = CGPoint(x: CGFloat(vector.x), y: CGFloat(vector.y)) | ||
+ size = CGSize(width: CGFloat(vector.z), height: CGFloat(vector.w)) | ||
+ } | ||
+ | ||
+ /// Returns the vector representation. | ||
+ public var vector: Vector { | ||
+ return Vector(Scalar(origin.x), Scalar(origin.y), Scalar(size.width), Scalar(size.height)) | ||
+ } | ||
+} |
46
Advance/VectorConvertible/CGSize+VectorConvertible.swift
@@ -0,0 +1,46 @@ | ||
+/* | ||
+ | ||
+ Copyright (c) 2016, Storehouse Media Inc. | ||
+ All rights reserved. | ||
+ | ||
+ Redistribution and use in source and binary forms, with or without | ||
+ modification, are permitted provided that the following conditions are met: | ||
+ | ||
+ * Redistributions of source code must retain the above copyright notice, this | ||
+ list of conditions and the following disclaimer. | ||
+ | ||
+ * Redistributions in binary form must reproduce the above copyright notice, | ||
+ this list of conditions and the following disclaimer in the documentation | ||
+ and/or other materials provided with the distribution. | ||
+ | ||
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+ */ | ||
+ | ||
+import CoreGraphics | ||
+ | ||
+/// Adds `VectorConvertible` conformance. | ||
+extension CGSize: VectorConvertible { | ||
+ | ||
+ /// The underlying vector type. | ||
+ public typealias Vector = Vector2 | ||
+ | ||
+ /// Creates a new instance from a vector. | ||
+ public init(vector: Vector) { | ||
+ self.init(width: CGFloat(vector.x), height: CGFloat(vector.y)) | ||
+ } | ||
+ | ||
+ /// Returns the vector representation. | ||
+ public var vector: Vector { | ||
+ return Vector(Scalar(width), Scalar(height)) | ||
+ } | ||
+} |
46
Advance/VectorConvertible/CGVector+VectorConvertible.swift
@@ -0,0 +1,46 @@ | ||
+/* | ||
+ | ||
+ Copyright (c) 2016, Storehouse Media Inc. | ||
+ All rights reserved. | ||
+ | ||
+ Redistribution and use in source and binary forms, with or without | ||
+ modification, are permitted provided that the following conditions are met: | ||
+ | ||
+ * Redistributions of source code must retain the above copyright notice, this | ||
+ list of conditions and the following disclaimer. | ||
+ | ||
+ * Redistributions in binary form must reproduce the above copyright notice, | ||
+ this list of conditions and the following disclaimer in the documentation | ||
+ and/or other materials provided with the distribution. | ||
+ | ||
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+ */ | ||
+ | ||
+import CoreGraphics | ||
+ | ||
+/// Adds `VectorConvertible` conformance. | ||
+extension CGVector: VectorConvertible { | ||
+ | ||
+ /// The underlying vector type. | ||
+ public typealias Vector = Vector2 | ||
+ | ||
+ /// Creates a new instance from a vector. | ||
+ public init(vector: Vector) { | ||
+ self.init(dx: CGFloat(vector.x), dy: CGFloat(vector.y)) | ||
+ } | ||
+ | ||
+ /// Returns the vector representation. | ||
+ public var vector: Vector { | ||
+ return Vector(Scalar(dx), Scalar(dy)) | ||
+ } | ||
+} |
44
Advance/VectorConvertible/Double+VectorConvertible.swift
@@ -0,0 +1,44 @@ | ||
+/* | ||
+ | ||
+ Copyright (c) 2016, Storehouse Media Inc. | ||
+ All rights reserved. | ||
+ | ||
+ Redistribution and use in source and binary forms, with or without | ||
+ modification, are permitted provided that the following conditions are met: | ||
+ | ||
+ * Redistributions of source code must retain the above copyright notice, this | ||
+ list of conditions and the following disclaimer. | ||
+ | ||
+ * Redistributions in binary form must reproduce the above copyright notice, | ||
+ this list of conditions and the following disclaimer in the documentation | ||
+ and/or other materials provided with the distribution. | ||
+ | ||
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+ */ | ||
+ | ||
+/// Adds `VectorConvertible` conformance. | ||
+extension Double: VectorConvertible { | ||
+ | ||
+ /// The underlying vector type. | ||
+ public typealias Vector = Vector1 | ||
+ | ||
+ /// Creates a new instance from a vector. | ||
+ public init(vector: Vector) { | ||
+ self.init(vector) | ||
+ } | ||
+ | ||
+ /// Returns the vector representation. | ||
+ public var vector: Vector { | ||
+ return Vector(self) | ||
+ } | ||
+} |
73
Advance/VectorConvertible/VectorConvertible.swift
@@ -0,0 +1,73 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+/// Conforming types can be converted to and from vector types. | ||
+public protocol VectorConvertible: Equatable, Interpolatable { | ||
+ | ||
+ /// The concrete VectorType implementation that can represent the | ||
+ /// conforming type. | ||
+ typealias Vector: VectorType | ||
+ | ||
+ /// Creates a new instance from a vector. | ||
+ init(vector: Vector) | ||
+ | ||
+ /// The vector representation of this instance. | ||
+ var vector: Vector { get } | ||
+} | ||
+ | ||
+public extension VectorConvertible { | ||
+ | ||
+ /// Returns an instance initialized using the zero vector. | ||
+ public static var zero: Self { | ||
+ return Self(vector: Vector.zero) | ||
+ } | ||
+} | ||
+ | ||
+public extension VectorConvertible { | ||
+ | ||
+ /// Interpolates between values. | ||
+ /// | ||
+ /// - parameter to: The value to interpolate to. | ||
+ /// - parameter alpha: The amount (between 0.0 and 1.0) to interpolate, | ||
+ /// where `0` returns the receiver, and `1` returns the `to` value. | ||
+ /// - Returns: The interpolated result. | ||
+ public func interpolatedTo(to: Self, alpha: Scalar) -> Self { | ||
+ return Self(vector: vector.interpolatedTo(to.vector, alpha: alpha)) | ||
+ } | ||
+ | ||
+ /// Interpolates in place. | ||
+ /// | ||
+ /// - parameter to: The value to interpolate to. | ||
+ /// - parameter alpha: The amount (between 0.0 and 1.0) to interpolate, | ||
+ /// where `0` leaves the receiver unchanged, and `1` assumes the value | ||
+ /// of `to`. | ||
+ public mutating func interpolateTo(to: Self, alpha: Scalar) { | ||
+ self = interpolatedTo(to, alpha: alpha) | ||
+ } | ||
+ | ||
+} |
43
Advance/Vectors/Interpolatable.swift
@@ -0,0 +1,43 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+/// Conforming types can be linearly interpolated. | ||
+public protocol Interpolatable { | ||
+ /// Interpolate between the given values. | ||
+ /// | ||
+ /// - parameter from: The value to interpolate from. | ||
+ /// - parameter to: The value to interpolate to. | ||
+ /// - parameter alpha: The amount to interpolate between `from` and `to`, | ||
+ /// where 0.0 is fully weighted toward `from`, and 1.0 is fully weighted | ||
+ /// toward `to`. | ||
+ /// - Returns: The interpolated result. | ||
+ func interpolatedTo(to: Self, alpha: Scalar) -> Self | ||
+ | ||
+ /// Interpolates between `self` and `to` | ||
+ mutating func interpolateTo(to: Self, alpha: Scalar) | ||
+} |
71
Advance/Vectors/Vector1.swift
@@ -0,0 +1,71 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+/// A vector with 1 component. | ||
+public typealias Vector1 = Scalar | ||
+ | ||
+extension Vector1: VectorType { | ||
+ | ||
+ /// Creates a vector for which all components are equal to the given scalar. | ||
+ public init(scalar: Scalar) { | ||
+ self = scalar | ||
+ } | ||
+ | ||
+ /// The empty vector (all scalar components are equal to `0.0`). | ||
+ public static var zero: Vector1 { | ||
+ return Vector1(0.0) | ||
+ } | ||
+ | ||
+ /// The number of scalar components in this vector type. | ||
+ public static var length: Int { | ||
+ return 1 | ||
+ } | ||
+ | ||
+ public subscript(index: Int) -> Scalar { | ||
+ get { | ||
+ precondition(index == 0) | ||
+ return self | ||
+ } | ||
+ set { | ||
+ precondition(index == 0) | ||
+ self = newValue | ||
+ } | ||
+ } | ||
+ | ||
+ /// Interpolate between the given values. | ||
+ public func interpolatedTo(to: Vector1, alpha: Scalar) -> Vector1 { | ||
+ var result = self | ||
+ result.interpolateTo(to, alpha: alpha) | ||
+ return result | ||
+ } | ||
+ | ||
+ /// Interpolate between the given values. | ||
+ public mutating func interpolateTo(to: Vector1, alpha: Scalar) { | ||
+ self += alpha * (to - self) | ||
+ } | ||
+} |
153
Advance/Vectors/Vector2.swift
@@ -0,0 +1,153 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+/// A vector with 2 components. | ||
+public struct Vector2 { | ||
+ | ||
+ /// Component at index `0` | ||
+ public var x: Scalar | ||
+ | ||
+ /// Component at index `1` | ||
+ public var y: Scalar | ||
+ | ||
+ /// Creates a new `Vector2` instance. | ||
+ public init(_ x: Scalar, _ y: Scalar) { | ||
+ self.x = x | ||
+ self.y = y | ||
+ } | ||
+} | ||
+ | ||
+extension Vector2: VectorType { | ||
+ | ||
+ /// Creates a vector for which all components are equal to the given scalar. | ||
+ public init(scalar: Scalar) { | ||
+ x = scalar | ||
+ y = scalar | ||
+ } | ||
+ | ||
+ /// The number of scalar components in this vector type. | ||
+ public static var length: Int { | ||
+ return 2 | ||
+ } | ||
+ | ||
+ /// The empty vector (all scalar components are equal to `0.0`). | ||
+ public static var zero: Vector2 { | ||
+ return Vector2(0.0, 0.0) | ||
+ } | ||
+ | ||
+ public subscript(index: Int) -> Scalar { | ||
+ get { | ||
+ precondition(index >= 0) | ||
+ precondition(index < 2) | ||
+ switch index { | ||
+ case 0: | ||
+ return x | ||
+ case 1: | ||
+ return y | ||
+ default: | ||
+ fatalError() | ||
+ } | ||
+ } | ||
+ set { | ||
+ precondition(index >= 0) | ||
+ precondition(index < 2) | ||
+ switch index { | ||
+ case 0: | ||
+ x = newValue | ||
+ case 1: | ||
+ y = newValue | ||
+ default: | ||
+ break | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ /// Interpolate between the given values. | ||
+ public func interpolatedTo(to: Vector2, alpha: Scalar) -> Vector2 { | ||
+ var result = self | ||
+ result.interpolateTo(to, alpha: alpha) | ||
+ return result | ||
+ } | ||
+ | ||
+ /// Interpolate between the given values. | ||
+ public mutating func interpolateTo(to: Vector2, alpha: Scalar) { | ||
+ x += alpha * (to.x - x) | ||
+ y += alpha * (to.y - y) | ||
+ } | ||
+} | ||
+ | ||
+/// Equatable. | ||
+public func ==(lhs: Vector2, rhs: Vector2) -> Bool { | ||
+ return lhs.x == rhs.x | ||
+ && lhs.y == rhs.y | ||
+} | ||
+ | ||
+/// Product. | ||
+public func *(lhs: Vector2, rhs: Vector2) -> Vector2 { | ||
+ return Vector2(lhs.x*rhs.x, lhs.y*rhs.y) | ||
+} | ||
+ | ||
+/// Product (in place). | ||
+public func *=(inout lhs: Vector2, rhs: Vector2) { | ||
+ lhs = lhs * rhs | ||
+} | ||
+ | ||
+/// Quotient. | ||
+public func /(lhs: Vector2, rhs: Vector2) -> Vector2 { | ||
+ return Vector2(lhs.x/rhs.x, lhs.y/rhs.y) | ||
+} | ||
+ | ||
+/// Quotient (in place). | ||
+public func /=(inout lhs: Vector2, rhs: Vector2) { | ||
+ lhs = lhs / rhs | ||
+} | ||
+ | ||
+/// Sum. | ||
+public func +(lhs: Vector2, rhs: Vector2) -> Vector2 { | ||
+ return Vector2(lhs.x+rhs.x, lhs.y+rhs.y) | ||
+} | ||
+ | ||
+/// Sum (in place). | ||
+public func +=(inout lhs: Vector2, rhs: Vector2) { | ||
+ lhs = lhs + rhs | ||
+} | ||
+ | ||
+/// Difference. | ||
+public func -(lhs: Vector2, rhs: Vector2) -> Vector2 { | ||
+ return Vector2(lhs.x-rhs.x, lhs.y-rhs.y) | ||
+} | ||
+ | ||
+/// Difference (in place). | ||
+public func -=(inout lhs: Vector2, rhs: Vector2) { | ||
+ lhs = lhs - rhs | ||
+} | ||
+ | ||
+/// Scalar-Vector product. | ||
+public func *(lhs: Scalar, rhs: Vector2) -> Vector2 { | ||
+ return Vector2(lhs*rhs.x, lhs*rhs.y) | ||
+} |
164
Advance/Vectors/Vector3.swift
@@ -0,0 +1,164 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+/// A vector with 3 components. | ||
+public struct Vector3 { | ||
+ | ||
+ /// Component at index `0` | ||
+ public var x: Scalar | ||
+ | ||
+ /// Component at index `1` | ||
+ public var y: Scalar | ||
+ | ||
+ /// Component at index `2` | ||
+ public var z: Scalar | ||
+ | ||
+ /// Creates a new `Vector3` instance. | ||
+ public init(_ x: Scalar, _ y: Scalar, _ z: Scalar) { | ||
+ self.x = x | ||
+ self.y = y | ||
+ self.z = z | ||
+ } | ||
+} | ||
+ | ||
+extension Vector3: VectorType { | ||
+ | ||
+ /// Creates a vector for which all components are equal to the given scalar. | ||
+ public init(scalar: Scalar) { | ||
+ x = scalar | ||
+ y = scalar | ||
+ z = scalar | ||
+ } | ||
+ | ||
+ /// The number of scalar components in this vector type. | ||
+ public static var length: Int { | ||
+ return 3 | ||
+ } | ||
+ | ||
+ /// The empty vector (all scalar components are equal to `0.0`). | ||
+ public static var zero: Vector3 { | ||
+ return Vector3(0.0, 0.0, 0.0) | ||
+ } | ||
+ | ||
+ public subscript(index: Int) -> Scalar { | ||
+ get { | ||
+ precondition(index >= 0) | ||
+ precondition(index < 3) | ||
+ switch index { | ||
+ case 0: | ||
+ return x | ||
+ case 1: | ||
+ return y | ||
+ case 2: | ||
+ return z | ||
+ default: | ||
+ fatalError() | ||
+ } | ||
+ } | ||
+ set { | ||
+ precondition(index >= 0) | ||
+ precondition(index < 3) | ||
+ switch index { | ||
+ case 0: | ||
+ x = newValue | ||
+ case 1: | ||
+ y = newValue | ||
+ case 2: | ||
+ z = newValue | ||
+ default: | ||
+ break | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ /// Interpolate between the given values. | ||
+ public func interpolatedTo(to: Vector3, alpha: Scalar) -> Vector3 { | ||
+ var result = self | ||
+ result.interpolateTo(to, alpha: alpha) | ||
+ return result | ||
+ } | ||
+ | ||
+ /// Interpolate between the given values. | ||
+ public mutating func interpolateTo(to: Vector3, alpha: Scalar) { | ||
+ x += alpha * (to.x - x) | ||
+ y += alpha * (to.y - y) | ||
+ z += alpha * (to.z - z) | ||
+ } | ||
+} | ||
+ | ||
+/// Equatable. | ||
+public func ==(lhs: Vector3, rhs: Vector3) -> Bool { | ||
+ return lhs.x == rhs.x | ||
+ && lhs.y == rhs.y | ||
+ && lhs.z == rhs.z | ||
+} | ||
+ | ||
+/// Product. | ||
+public func *(lhs: Vector3, rhs: Vector3) -> Vector3 { | ||
+ return Vector3(lhs.x*rhs.x, lhs.y*rhs.y, lhs.z*rhs.z) | ||
+} | ||
+ | ||
+/// Product (in place). | ||
+public func *=(inout lhs: Vector3, rhs: Vector3) { | ||
+ lhs = lhs * rhs | ||
+} | ||
+ | ||
+/// Quotient | ||
+public func /(lhs: Vector3, rhs: Vector3) -> Vector3 { | ||
+ return Vector3(lhs.x/rhs.x, lhs.y/rhs.y, lhs.z/rhs.z) | ||
+} | ||
+ | ||
+/// Quotient (in place). | ||
+public func /=(inout lhs: Vector3, rhs: Vector3) { | ||
+ lhs = lhs / rhs | ||
+} | ||
+ | ||
+/// Sum. | ||
+public func +(lhs: Vector3, rhs: Vector3) -> Vector3 { | ||
+ return Vector3(lhs.x+rhs.x, lhs.y+rhs.y, lhs.z+rhs.z) | ||
+} | ||
+ | ||
+/// Sum (in place). | ||
+public func +=(inout lhs: Vector3, rhs: Vector3) { | ||
+ lhs = lhs + rhs | ||
+} | ||
+ | ||
+/// Difference. | ||
+public func -(lhs: Vector3, rhs: Vector3) -> Vector3 { | ||
+ return Vector3(lhs.x-rhs.x, lhs.y-rhs.y, lhs.z-rhs.z) | ||
+} | ||
+ | ||
+/// Difference (in place). | ||
+public func -=(inout lhs: Vector3, rhs: Vector3) { | ||
+ lhs = lhs - rhs | ||
+} | ||
+ | ||
+/// Scalar-Vector product. | ||
+public func *(lhs: Scalar, rhs: Vector3) -> Vector3 { | ||
+ return Vector3(lhs*rhs.x, lhs*rhs.y, lhs*rhs.z) | ||
+} |
175
Advance/Vectors/Vector4.swift
@@ -0,0 +1,175 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+/// A vector with 4 component. | ||
+public struct Vector4 { | ||
+ | ||
+ /// Component at index `0` | ||
+ public var x: Scalar | ||
+ | ||
+ /// Component at index `1` | ||
+ public var y: Scalar | ||
+ | ||
+ /// Component at index `2` | ||
+ public var z: Scalar | ||
+ | ||
+ /// Component at index `3` | ||
+ public var w: Scalar | ||
+ | ||
+ /// Creates a new `Vector4` instance. | ||
+ public init(_ x: Scalar, _ y: Scalar, _ z: Scalar, _ w: Scalar) { | ||
+ self.x = x | ||
+ self.y = y | ||
+ self.z = z | ||
+ self.w = w | ||
+ } | ||
+} | ||
+ | ||
+extension Vector4: VectorType { | ||
+ | ||
+ /// Creates a vector for which all components are equal to the given scalar. | ||
+ public init(scalar: Scalar) { | ||
+ x = scalar | ||
+ y = scalar | ||
+ z = scalar | ||
+ w = scalar | ||
+ } | ||
+ | ||
+ /// The number of scalar components in this vector type. | ||
+ public static var length: Int { | ||
+ return 4 | ||
+ } | ||
+ | ||
+ /// The empty vector (all scalar components are equal to `0.0`). | ||
+ public static var zero: Vector4 { | ||
+ return Vector4(0.0, 0.0, 0.0, 0.0) | ||
+ } | ||
+ | ||
+ public subscript(index: Int) -> Scalar { | ||
+ get { | ||
+ precondition(index >= 0) | ||
+ precondition(index < 4) | ||
+ switch index { | ||
+ case 0: | ||
+ return x | ||
+ case 1: | ||
+ return y | ||
+ case 2: | ||
+ return z | ||
+ case 3: | ||
+ return w | ||
+ default: | ||
+ fatalError() | ||
+ } | ||
+ } | ||
+ set { | ||
+ precondition(index >= 0) | ||
+ precondition(index < 4) | ||
+ switch index { | ||
+ case 0: | ||
+ x = newValue | ||
+ case 1: | ||
+ y = newValue | ||
+ case 2: | ||
+ z = newValue | ||
+ case 3: | ||
+ w = newValue | ||
+ default: | ||
+ break | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ /// Interpolate between the given values. | ||
+ public func interpolatedTo(to: Vector4, alpha: Scalar) -> Vector4 { | ||
+ var result = self | ||
+ result.interpolateTo(to, alpha: alpha) | ||
+ return result | ||
+ } | ||
+ | ||
+ /// Interpolate between the given values. | ||
+ public mutating func interpolateTo(to: Vector4, alpha: Scalar) { | ||
+ x += alpha * (to.x - x) | ||
+ y += alpha * (to.y - y) | ||
+ z += alpha * (to.z - z) | ||
+ w += alpha * (to.w - w) | ||
+ } | ||
+} | ||
+ | ||
+/// Equatable. | ||
+public func ==(lhs: Vector4, rhs: Vector4) -> Bool { | ||
+ return lhs.x == rhs.x | ||
+ && lhs.y == rhs.y | ||
+ && lhs.z == rhs.z | ||
+ && lhs.w == rhs.w | ||
+} | ||
+ | ||
+/// Product. | ||
+public func *(lhs: Vector4, rhs: Vector4) -> Vector4 { | ||
+ return Vector4(lhs.x*rhs.x, lhs.y*rhs.y, lhs.z*rhs.z, lhs.w*rhs.w) | ||
+} | ||
+ | ||
+/// Product (in place). | ||
+public func *=(inout lhs: Vector4, rhs: Vector4) { | ||
+ lhs = lhs * rhs | ||
+} | ||
+ | ||
+/// Quotient | ||
+public func /(lhs: Vector4, rhs: Vector4) -> Vector4 { | ||
+ return Vector4(lhs.x/rhs.x, lhs.y/rhs.y, lhs.z/rhs.z, lhs.w/rhs.w) | ||
+} | ||
+ | ||
+/// Quotient (in place). | ||
+public func /=(inout lhs: Vector4, rhs: Vector4) { | ||
+ lhs = lhs / rhs | ||
+} | ||
+ | ||
+/// Sum. | ||
+public func +(lhs: Vector4, rhs: Vector4) -> Vector4 { | ||
+ return Vector4(lhs.x+rhs.x, lhs.y+rhs.y, lhs.z+rhs.z, lhs.w+rhs.w) | ||
+} | ||
+ | ||
+/// Sum (in place). | ||
+public func +=(inout lhs: Vector4, rhs: Vector4) { | ||
+ lhs = lhs + rhs | ||
+} | ||
+ | ||
+/// Difference. | ||
+public func -(lhs: Vector4, rhs: Vector4) -> Vector4 { | ||
+ return Vector4(lhs.x-rhs.x, lhs.y-rhs.y, lhs.z-rhs.z, lhs.w-rhs.w) | ||
+} | ||
+ | ||
+/// Difference (in place). | ||
+public func -=(inout lhs: Vector4, rhs: Vector4) { | ||
+ lhs = lhs - rhs | ||
+} | ||
+ | ||
+/// Scalar-Vector product. | ||
+public func *(lhs: Scalar, rhs: Vector4) -> Vector4 { | ||
+ return Vector4(lhs*rhs.x, lhs*rhs.y, lhs*rhs.z, lhs*rhs.w) | ||
+} |
60
Advance/Vectors/VectorMathCapable.swift
@@ -0,0 +1,60 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import Foundation | ||
+import simd | ||
+ | ||
+/// Conforming types implement functions for basic vector arithmetic. | ||
+public protocol VectorMathCapable { | ||
+ /// Product. | ||
+ func *(lhs: Self, rhs: Self) -> Self | ||
+ | ||
+ /// Product (in place). | ||
+ func *=(inout lhs: Self, rhs: Self) | ||
+ | ||
+ /// Quotient. | ||
+ func /(lhs: Self, rhs: Self) -> Self | ||
+ | ||
+ /// Quotient (in place). | ||
+ func /=(inout lhs: Self, rhs: Self) | ||
+ | ||
+ /// Sum. | ||
+ func +(lhs: Self, rhs: Self) -> Self | ||
+ | ||
+ /// Sum (in place). | ||
+ func +=(inout lhs: Self, rhs: Self) | ||
+ | ||
+ /// Difference. | ||
+ func -(lhs: Self, rhs: Self) -> Self | ||
+ | ||
+ /// Difference (in place). | ||
+ func -=(inout lhs: Self, rhs: Self) | ||
+ | ||
+ /// Scalar-Vector product. | ||
+ func *(lhs: Scalar, rhs: Self) -> Self | ||
+} |
79
Advance/Vectors/VectorType.swift
@@ -0,0 +1,79 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+/// The underlying type of scalar quantities. | ||
+public typealias Scalar = Double | ||
+ | ||
+/// Conforming types can be operated on as vectors composed of `Scalar` components. | ||
+public protocol VectorType: Equatable, Interpolatable, VectorMathCapable { | ||
+ | ||
+ /// Creates a vector for which all components are equal to the given scalar. | ||
+ init(scalar: Scalar) | ||
+ | ||
+ /// The number of scalar components in this vector type. | ||
+ static var length: Int { get } | ||
+ | ||
+ /// The empty vector (all scalar components are equal to `0.0`). | ||
+ static var zero: Self { get } | ||
+ | ||
+ /// Subscripting for vector components. | ||
+ subscript(index: Int) -> Scalar { get set } | ||
+ | ||
+ /// Returns a vector where each component is clamped by the corresponding | ||
+ /// components in `min` and `max`. | ||
+ /// | ||
+ /// - parameter x: The vector to be clamped. | ||
+ /// - parameter min: Each component in the output vector will `>=` the | ||
+ /// corresponding component in this vector. | ||
+ /// - parameter max: Each component in the output vector will be `<=` the | ||
+ /// corresponding component in this vector. | ||
+ func clamped(min min: Self, max: Self) -> Self | ||
+ | ||
+ /// Clamps in place. | ||
+ mutating func clamp(min: Self, max: Self) | ||
+} | ||
+ | ||
+ | ||
+public extension VectorType { | ||
+ | ||
+ /// Returns a vector where each component is clamped by the corresponding | ||
+ /// components in `min` and `max`. | ||
+ public func clamped(min min: Self, max: Self) -> Self { | ||
+ var result = self | ||
+ for i in 0..<Self.length { | ||
+ if result[i] < min[i] { result[i] = min[i] } | ||
+ if result[i] > max[i] { result[i] = max[i] } | ||
+ } | ||
+ return result | ||
+ } | ||
+ | ||
+ /// Clamps in place. | ||
+ public mutating func clamp(min: Self, max: Self) { | ||
+ self = clamped(min: min, max: max) | ||
+ } | ||
+} |
300
AdvanceSample/ActivityView.swift
@@ -0,0 +1,300 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import UIKit | ||
+import Advance | ||
+ | ||
+ | ||
+ | ||
+public final class ActivityView: UIView { | ||
+ | ||
+ private static let points: [CGPoint] = { | ||
+ var points: [CGPoint] = Array(count: 10, repeatedValue: CGPoint.zero) | ||
+ points[0] = CGPoint(x: 0.5, y: 0.0) | ||
+ points[1] = CGPoint(x: 0.20928571428571, y: 0.16642857142857) | ||
+ points[2] = CGPoint(x: 0.79071428571429, y: 0.16642857142857) | ||
+ points[3] = CGPoint(x: 0.5, y: 0.3325) | ||
+ points[4] = CGPoint(x: 0.20928571428571, y: 0.49964285714286) | ||
+ points[5] = CGPoint(x: 0.79071428571429, y: 0.49964285714286) | ||
+ points[6] = CGPoint(x: 0.5, y: 0.66607142857143) | ||
+ points[7] = CGPoint(x: 0.20928571428571, y: 0.83357142857143) | ||
+ points[8] = CGPoint(x: 0.79071428571429, y: 0.83357142857143) | ||
+ points[9] = CGPoint(x: 0.5, y: 1.0) | ||
+ return points | ||
+ }() | ||
+ | ||
+ private let segments: [ActivitySegment] = { | ||
+ var segments: [ActivitySegment] = [] | ||
+ segments.append(ActivitySegment(firstPoint: points[1], secondPoint: points[0])) | ||
+ segments.append(ActivitySegment(firstPoint: points[0], secondPoint: points[2])) | ||
+ segments.append(ActivitySegment(firstPoint: points[1], secondPoint: points[3])) | ||
+ segments.append(ActivitySegment(firstPoint: points[2], secondPoint: points[3])) | ||
+ segments.append(ActivitySegment(firstPoint: points[1], secondPoint: points[4])) | ||
+ segments.append(ActivitySegment(firstPoint: points[4], secondPoint: points[3])) | ||
+ segments.append(ActivitySegment(firstPoint: points[3], secondPoint: points[5])) | ||
+ segments.append(ActivitySegment(firstPoint: points[4], secondPoint: points[6])) | ||
+ segments.append(ActivitySegment(firstPoint: points[6], secondPoint: points[5])) | ||
+ segments.append(ActivitySegment(firstPoint: points[5], secondPoint: points[8])) | ||
+ segments.append(ActivitySegment(firstPoint: points[6], secondPoint: points[7])) | ||
+ segments.append(ActivitySegment(firstPoint: points[6], secondPoint: points[8])) | ||
+ segments.append(ActivitySegment(firstPoint: points[7], secondPoint: points[9])) | ||
+ segments.append(ActivitySegment(firstPoint: points[8], secondPoint: points[9])) | ||
+ return segments | ||
+ }() | ||
+ | ||
+ private let visibilitySprings: [Spring<CGFloat>] = { | ||
+ return (0...13).map {_ in | ||
+ let s = Spring(value: CGFloat(1.0)) | ||
+ s.configuration.threshold = 0.001 | ||
+ s.configuration.tension = 220.0 + Double(arc4random() % 200); | ||
+ s.configuration.damping = 30.0 + Double(arc4random() % 10); | ||
+ return s | ||
+ } | ||
+ }() | ||
+ | ||
+ private var segmentLayers: [CAShapeLayer] = { | ||
+ return (0...13).map {_ in | ||
+ let sl = CAShapeLayer() | ||
+ var actions: [String: AnyObject] = [:] | ||
+ actions["position"] = NSNull() | ||
+ actions["bounds"] = NSNull() | ||
+ actions["lineWidth"] = NSNull() | ||
+ actions["strokeColor"] = NSNull() | ||
+ actions["path"] = NSNull() | ||
+ return sl | ||
+ } | ||
+ }() | ||
+ | ||
+ private var strokeColor: UIColor { | ||
+ return color.colorWithAlphaComponent(0.6) | ||
+ } | ||
+ | ||
+ private var flashStrokeColor: UIColor { | ||
+ return color | ||
+ } | ||
+ | ||
+ private let lineWidth = CGFloat(1.0) | ||
+ private let flashLineWidth = CGFloat(2.0) | ||
+ | ||
+ private var flashTimer: NSTimer? = nil | ||
+ | ||
+ public var color = UIColor(red: 0.0, green: 196.0/255.0, blue: 1.0, alpha: 1.0) { | ||
+ didSet { | ||
+ for sl in segmentLayers { | ||
+ sl.strokeColor = strokeColor.CGColor | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ public var assembledAmount: CGFloat = 1.0 { | ||
+ didSet { | ||
+ updateSegmentVisibility(true) | ||
+ } | ||
+ } | ||
+ | ||
+ public var flashing: Bool = false { | ||
+ didSet { | ||
+ guard flashing != oldValue else { return } | ||
+ if flashing { | ||
+ let t = NSTimer(timeInterval: 1.0, target: self, selector: "flash", userInfo: nil, repeats: true) | ||
+ NSRunLoop.mainRunLoop().addTimer(t, forMode: NSRunLoopCommonModes) | ||
+ flashTimer = t | ||
+ flash() | ||
+ } else { | ||
+ flashTimer?.invalidate() | ||
+ flashTimer = nil | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ required override public init(frame: CGRect) { | ||
+ super.init(frame: frame) | ||
+ | ||
+ backgroundColor = UIColor.clearColor() | ||
+ layer.allowsGroupOpacity = false | ||
+ | ||
+ for vs in visibilitySprings { | ||
+ vs.changed.observe({ [weak self] (vis) in | ||
+ self?.setNeedsLayout() | ||
+ }) | ||
+ } | ||
+ | ||
+ for sl in segmentLayers { | ||
+ sl.strokeColor = strokeColor.CGColor | ||
+ sl.lineWidth = lineWidth | ||
+ layer.addSublayer(sl) | ||
+ } | ||
+ } | ||
+ | ||
+ required public init?(coder aDecoder: NSCoder) { | ||
+ fatalError("init(coder:) has not been implemented") | ||
+ } | ||
+ | ||
+ override public func layoutSubviews() { | ||
+ super.layoutSubviews() | ||
+ | ||
+ for i in 0..<segments.count { | ||
+ let s = segments[i] | ||
+ let sl = segmentLayers[i] | ||
+ let visibility = visibilitySprings[i].value | ||
+ sl.frame = bounds | ||
+ sl.path = s.path(bounds.size, visibility: visibility).CGPath | ||
+ sl.opacity = Float(visibility) | ||
+ } | ||
+ } | ||
+ | ||
+ override public func sizeThatFits(size: CGSize) -> CGSize { | ||
+ return CGSize(width: 40.0, height: 40.0) | ||
+ } | ||
+ | ||
+ public func resetAssembledAmount(assembledAmount: CGFloat) { | ||
+ self.assembledAmount = assembledAmount | ||
+ updateSegmentVisibility(false) | ||
+ } | ||
+ | ||
+ func updateSegmentVisibility(animated: Bool) { | ||
+ for i in 0..<segments.count { | ||
+ let positionInArray = CGFloat(i) / CGFloat(segments.count-1) | ||
+ | ||
+ let minVis = (1.0-positionInArray) * 0.4 | ||
+ let maxVis = minVis + 0.6 | ||
+ | ||
+ var mappedVis = (assembledAmount - minVis) / (maxVis - minVis) | ||
+ mappedVis = min(mappedVis, 1.0) | ||
+ mappedVis = max(mappedVis, 0.0) | ||
+ mappedVis = quadEaseInOut(mappedVis) | ||
+ | ||
+ if animated { | ||
+ visibilitySprings[i].target = mappedVis | ||
+ } else { | ||
+ visibilitySprings[i].reset(mappedVis) | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ private dynamic func flash() { | ||
+ for i in 0..<segmentLayers.count { | ||
+ let t = dispatch_time(DISPATCH_TIME_NOW, Int64(Double(NSEC_PER_SEC) * 0.04 * Double(i))) | ||
+ dispatch_after(t, dispatch_get_main_queue(), { | ||
+ self.flashSegment(i) | ||
+ }) | ||
+ } | ||
+ } | ||
+ | ||
+ private func flashSegment(index: Int) { | ||
+ let sl = segmentLayers[index] | ||
+ | ||
+ CATransaction.begin() | ||
+ | ||
+ let c = CAKeyframeAnimation(keyPath: "strokeColor") | ||
+ c.values = [strokeColor.CGColor, flashStrokeColor.CGColor, strokeColor.CGColor] | ||
+ c.keyTimes = [0.0, 0.3, 1.0] | ||
+ c.calculationMode = kCAAnimationCubic | ||
+ c.duration = 0.5 | ||
+ sl.addAnimation(c, forKey: "flashStrokeColor") | ||
+ | ||
+ let s = CAKeyframeAnimation(keyPath: "lineWidth") | ||
+ s.values = [lineWidth, flashLineWidth, lineWidth] | ||
+ s.keyTimes = [0.0, 0.3, 1.0] | ||
+ s.calculationMode = kCAAnimationCubic | ||
+ s.duration = 0.5 | ||
+ sl.addAnimation(s, forKey: "flashLineWidth") | ||
+ | ||
+ CATransaction.commit() | ||
+ } | ||
+} | ||
+ | ||
+private func quadEaseInOut(t: CGFloat) -> CGFloat { | ||
+ var result = t / 0.5 | ||
+ if (result < 1.0) { | ||
+ return 0.5*result*result | ||
+ } else { | ||
+ result -= 1.0 | ||
+ return -0.5 * (result * (result - 2.0) - 1.0) | ||
+ } | ||
+} | ||
+ | ||
+ | ||
+private struct ActivitySegment { | ||
+ | ||
+ let firstPoint: CGPoint | ||
+ let secondPoint: CGPoint | ||
+ | ||
+ let initialPosition: CGPoint = { | ||
+ var p = CGPoint.zero | ||
+ p.x = (CGFloat(arc4random() % 100) / 100.0) | ||
+ p.y = (CGFloat(arc4random() % 100) / 100.0) | ||
+ p.x = ((p.x - 0.5) * 2.0) + 0.5 | ||
+ p.y -= 0.6; | ||
+ return p | ||
+ }() | ||
+ | ||
+ let initialRotation = ((CGFloat(arc4random() % 100) / 100.0) * CGFloat(M_PI)) | ||
+ | ||
+ func path(size: CGSize, visibility: CGFloat) -> UIBezierPath { | ||
+ var p1 = initialPosition | ||
+ var p2 = initialPosition | ||
+ | ||
+ p1.interpolateTo(firstPoint, alpha: Scalar(visibility)) | ||
+ p2.interpolateTo(secondPoint, alpha: Scalar(visibility)) | ||
+ | ||
+ let rotation = initialRotation.interpolatedTo(0.0, alpha: Scalar(visibility)) | ||
+ let midX = p1.x + (p2.x - p1.x) * 0.5 | ||
+ let midY = p1.y + (p2.y - p1.y) * 0.5 | ||
+ | ||
+ p1.x -= midX | ||
+ p2.x -= midX | ||
+ p1.y -= midY | ||
+ p2.y -= midY | ||
+ | ||
+ p1 = CGPointApplyAffineTransform(p1, CGAffineTransformMakeRotation(rotation)) | ||
+ p2 = CGPointApplyAffineTransform(p2, CGAffineTransformMakeRotation(rotation)) | ||
+ | ||
+ p1.x += midX | ||
+ p2.x += midX | ||
+ p1.y += midY | ||
+ p2.y += midY | ||
+ | ||
+ // we store layout info in relative coords | ||
+ p1.x *= size.width | ||
+ p1.y *= size.height | ||
+ p2.x *= size.width | ||
+ p2.y *= size.height | ||
+ | ||
+ let p = UIBezierPath() | ||
+ p.moveToPoint(p1) | ||
+ p.addLineToPoint(p2) | ||
+ | ||
+ return p | ||
+ } | ||
+ | ||
+ init(firstPoint: CGPoint, secondPoint: CGPoint) { | ||
+ self.firstPoint = firstPoint | ||
+ self.secondPoint = secondPoint | ||
+ } | ||
+} |
86
AdvanceSample/ActivityViewController.swift
@@ -0,0 +1,86 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import UIKit | ||
+ | ||
+class ActivityViewController: DemoViewController { | ||
+ | ||
+ let activityView = ActivityView() | ||
+ | ||
+ let slider = UISlider() | ||
+ | ||
+ override func viewDidLoad() { | ||
+ super.viewDidLoad() | ||
+ | ||
+ title = "Logo" | ||
+ note = "Drag the slider to disassemble." | ||
+ | ||
+ activityView.flashing = true | ||
+ contentView.addSubview(activityView) | ||
+ | ||
+ contentView.addSubview(slider) | ||
+ slider.alpha = 0.0 | ||
+ slider.minimumValue = 0.0 | ||
+ slider.maximumValue = 1.0 | ||
+ slider.value = 1.0 | ||
+ slider.addTarget(self, action: "sliderChanged", forControlEvents: .ValueChanged) | ||
+ slider.minimumTrackTintColor = UIColor(red: 0.0, green: 196.0/255.0, blue: 1.0, alpha: 1.0) | ||
+ } | ||
+ | ||
+ override func viewDidLayoutSubviews() { | ||
+ super.viewDidLayoutSubviews() | ||
+ let size = min(view.bounds.width, view.bounds.height) * 0.6 | ||
+ activityView.bounds.size = CGSize(width: size, height: size) | ||
+ activityView.center = CGPoint(x: view.bounds.midX, y: view.bounds.midY) | ||
+ | ||
+ let sliderInset = CGFloat(32.0) | ||
+ | ||
+ var sliderFrame = CGRect.zero | ||
+ sliderFrame.size.width = view.bounds.width - sliderInset*2.0 | ||
+ sliderFrame.size.height = 44.0 | ||
+ sliderFrame.origin.x = sliderInset | ||
+ sliderFrame.origin.y = view.bounds.maxY - sliderInset - 44.0 | ||
+ slider.frame = sliderFrame | ||
+ } | ||
+ | ||
+ dynamic func sliderChanged() { | ||
+ activityView.assembledAmount = CGFloat(slider.value) | ||
+ activityView.flashing = activityView.assembledAmount == 1.0 | ||
+ } | ||
+ | ||
+ override func didEnterFullScreen() { | ||
+ super.didEnterFullScreen() | ||
+ slider.alpha = 1.0 | ||
+ } | ||
+ | ||
+ override func didLeaveFullScreen() { | ||
+ super.didLeaveFullScreen() | ||
+ slider.alpha = 0.0 | ||
+ } | ||
+ | ||
+} |
77
AdvanceSample/AppDelegate.swift
@@ -0,0 +1,77 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import UIKit | ||
+import Advance | ||
+ | ||
+@UIApplicationMain | ||
+class AppDelegate: UIResponder, UIApplicationDelegate { | ||
+ | ||
+ var window: UIWindow? | ||
+ | ||
+ func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { | ||
+ // Override point for customization after application launch. | ||
+ | ||
+ window = UIWindow(frame: UIScreen.mainScreen().bounds) | ||
+ | ||
+ let a = ActivityViewController() | ||
+ let g = GesturesViewController() | ||
+ let s = SpringsViewController() | ||
+ let gravity = GravityViewController() | ||
+ | ||
+ window?.rootViewController = BrowserViewController(viewControllers: [a, gravity, s, g]) | ||
+ window?.makeKeyAndVisible() | ||
+ | ||
+ return true | ||
+ } | ||
+ | ||
+ func applicationWillResignActive(application: UIApplication) { | ||
+ // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. | ||
+ // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. | ||
+ } | ||
+ | ||
+ func applicationDidEnterBackground(application: UIApplication) { | ||
+ // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. | ||
+ // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. | ||
+ } | ||
+ | ||
+ func applicationWillEnterForeground(application: UIApplication) { | ||
+ // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. | ||
+ } | ||
+ | ||
+ func applicationDidBecomeActive(application: UIApplication) { | ||
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. | ||
+ } | ||
+ | ||
+ func applicationWillTerminate(application: UIApplication) { | ||
+ // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. | ||
+ } | ||
+ | ||
+ | ||
+} | ||
+ |
38
AdvanceSample/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,38 @@ | ||
+{ | ||
+ "images" : [ | ||
+ { | ||
+ "idiom" : "iphone", | ||
+ "size" : "29x29", | ||
+ "scale" : "2x" | ||
+ }, | ||
+ { | ||
+ "idiom" : "iphone", | ||
+ "size" : "29x29", | ||
+ "scale" : "3x" | ||
+ }, | ||
+ { | ||
+ "idiom" : "iphone", | ||
+ "size" : "40x40", | ||
+ "scale" : "2x" | ||
+ }, | ||
+ { | ||
+ "idiom" : "iphone", | ||
+ "size" : "40x40", | ||
+ "scale" : "3x" | ||
+ }, | ||
+ { | ||
+ "idiom" : "iphone", | ||
+ "size" : "60x60", | ||
+ "scale" : "2x" | ||
+ }, | ||
+ { | ||
+ "idiom" : "iphone", | ||
+ "size" : "60x60", | ||
+ "scale" : "3x" | ||
+ } | ||
+ ], | ||
+ "info" : { | ||
+ "version" : 1, | ||
+ "author" : "xcode" | ||
+ } | ||
+} |
6
AdvanceSample/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@ | ||
+{ | ||
+ "info" : { | ||
+ "version" : 1, | ||
+ "author" : "xcode" | ||
+ } | ||
+} |
21
AdvanceSample/Assets.xcassets/background-blurred.imageset/Contents.json
@@ -0,0 +1,21 @@ | ||
+{ | ||
+ "images" : [ | ||
+ { | ||
+ "idiom" : "universal", | ||
+ "filename" : "background-blurred.jpg", | ||
+ "scale" : "1x" | ||
+ }, | ||
+ { | ||
+ "idiom" : "universal", | ||
+ "scale" : "2x" | ||
+ }, | ||
+ { | ||
+ "idiom" : "universal", | ||
+ "scale" : "3x" | ||
+ } | ||
+ ], | ||
+ "info" : { | ||
+ "version" : 1, | ||
+ "author" : "xcode" | ||
+ } | ||
+} |
BIN
AdvanceSample/Assets.xcassets/background-blurred.imageset/background-blurred.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21
AdvanceSample/Assets.xcassets/background.imageset/Contents.json
@@ -0,0 +1,21 @@ | ||
+{ | ||
+ "images" : [ | ||
+ { | ||
+ "idiom" : "universal", | ||
+ "filename" : "background.jpg", | ||
+ "scale" : "1x" | ||
+ }, | ||
+ { | ||
+ "idiom" : "universal", | ||
+ "scale" : "2x" | ||
+ }, | ||
+ { | ||
+ "idiom" : "universal", | ||
+ "scale" : "3x" | ||
+ } | ||
+ ], | ||
+ "info" : { | ||
+ "version" : 1, | ||
+ "author" : "xcode" | ||
+ } | ||
+} |
BIN
AdvanceSample/Assets.xcassets/background.imageset/background.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15
AdvanceSample/Assets.xcassets/logo.imageset/Contents.json
@@ -0,0 +1,15 @@ | ||
+{ | ||
+ "images" : [ | ||
+ { | ||
+ "idiom" : "universal", | ||
+ "filename" : "logo.pdf" | ||
+ } | ||
+ ], | ||
+ "info" : { | ||
+ "version" : 1, | ||
+ "author" : "xcode" | ||
+ }, | ||
+ "properties" : { | ||
+ "template-rendering-intent" : "template" | ||
+ } | ||
+} |
BIN
AdvanceSample/Assets.xcassets/logo.imageset/logo.pdf
Binary file not shown.
27
AdvanceSample/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,27 @@ | ||
+<?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM"> | ||
+ <dependencies> | ||
+ <deployment identifier="iOS"/> | ||
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9530"/> | ||
+ </dependencies> | ||
+ <scenes> | ||
+ <!--View Controller--> | ||
+ <scene sceneID="EHf-IW-A2E"> | ||
+ <objects> | ||
+ <viewController id="01J-lp-oVM" sceneMemberID="viewController"> | ||
+ <layoutGuides> | ||
+ <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/> | ||
+ <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/> | ||
+ </layoutGuides> | ||
+ <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3"> | ||
+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/> | ||
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> | ||
+ </view> | ||
+ </viewController> | ||
+ <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/> | ||
+ </objects> | ||
+ <point key="canvasLocation" x="53" y="375"/> | ||
+ </scene> | ||
+ </scenes> | ||
+</document> |
509
AdvanceSample/BrowserView.swift
@@ -0,0 +1,509 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import UIKit | ||
+import Advance | ||
+ | ||
+class BrowserItem: NSObject { | ||
+ | ||
+ let center = Spring(value: CGPoint.zero) | ||
+ let transform = Spring(value: SimpleTransform()) | ||
+ let size = Spring(value: CGSize.zero) | ||
+ | ||
+ let tapRecognizer = UITapGestureRecognizer() | ||
+ | ||
+ let recognizer = DirectManipulationGestureRecognizer() | ||
+ let panRecognizer = UIPanGestureRecognizer() | ||
+ | ||
+ var anchorPoint = CGPoint(x: 0.5, y: 0.5) { | ||
+ didSet { browserView?.setNeedsLayout() } | ||
+ } | ||
+ | ||
+ let view: UIView = { | ||
+ let v = UIView(frame: CGRect.zero) | ||
+ v.backgroundColor = UIColor.blueColor() | ||
+ v.layer.cornerRadius = 6.0 | ||
+ v.clipsToBounds = true | ||
+ return v | ||
+ }() | ||
+ | ||
+ private var transformWhenGestureBegan = SimpleTransform() | ||
+ private var centerWhenGestureBegan = CGPoint.zero | ||
+ private (set) var gestureInProgress = false | ||
+ | ||
+ var frame: CGRect { | ||
+ var f = CGRect.zero | ||
+ f.size = size.value | ||
+ f.origin.x -= anchorPoint.x * f.size.width | ||
+ f.origin.y -= anchorPoint.y * f.size.height | ||
+ f = CGRectApplyAffineTransform(f, transform.value.affineTransform) | ||
+ f.origin.x += center.value.x | ||
+ f.origin.y += center.value.y | ||
+ return f | ||
+ } | ||
+ | ||
+ private (set) weak var browserView: BrowserView? = nil | ||
+ | ||
+ override init() { | ||
+ super.init() | ||
+ | ||
+ center.configuration.threshold = 0.1 | ||
+ center.configuration.tension = 120.0 | ||
+ center.configuration.damping = 27.0 | ||
+ center.changed.observe { [unowned self] (p) -> Void in | ||
+ self.browserView?.setNeedsLayout() | ||
+ } | ||
+ | ||
+ transform.configuration.threshold = 0.001 | ||
+ transform.changed.observe { [unowned self] (p) -> Void in | ||
+ self.browserView?.setNeedsLayout() | ||
+ } | ||
+ | ||
+ size.configuration.threshold = 0.1 | ||
+ size.changed.observe { [unowned self] (p) -> Void in | ||
+ self.browserView?.setNeedsLayout() | ||
+ } | ||
+ | ||
+ tapRecognizer.addTarget(self, action: "tap") | ||
+ view.addGestureRecognizer(tapRecognizer) | ||
+ | ||
+ recognizer.addTarget(self, action: "gesture:") | ||
+ view.addGestureRecognizer(recognizer) | ||
+ | ||
+ panRecognizer.addTarget(self, action: "pan:") | ||
+ panRecognizer.delegate = self | ||
+ view.addGestureRecognizer(panRecognizer) | ||
+ } | ||
+ | ||
+ private dynamic func tap() { | ||
+ if browserView?.fullScreenItem != self { | ||
+ browserView?.enterFullScreen(self) | ||
+ } | ||
+ } | ||
+ | ||
+ private dynamic func gesture(recognizer: DirectManipulationGestureRecognizer) { | ||
+ switch recognizer.state { | ||
+ case .Began: | ||
+ | ||
+ let gestureLocation = recognizer.locationInView(view) | ||
+ let newCenter = view.superview!.convertPoint(gestureLocation, fromView: view) | ||
+ center.reset(newCenter) | ||
+ | ||
+ var anchorPoint = gestureLocation | ||
+ anchorPoint.x /= view.bounds.width | ||
+ anchorPoint.y /= view.bounds.height | ||
+ self.anchorPoint = anchorPoint | ||
+ | ||
+ gestureInProgress = true | ||
+ centerWhenGestureBegan = center.value | ||
+ transformWhenGestureBegan = transform.value | ||
+ center.reset(center.value) | ||
+ transform.reset(transform.value) | ||
+ case .Changed: | ||
+ var t = transformWhenGestureBegan | ||
+ t.rotation += recognizer.rotation | ||
+ t.scale *= recognizer.scale | ||
+ transform.reset(t) | ||
+ | ||
+ var c = centerWhenGestureBegan | ||
+ c.x += recognizer.translationInView(view.superview).x | ||
+ c.y += recognizer.translationInView(view.superview).y | ||
+ center.reset(c) | ||
+ break | ||
+ case .Ended, .Cancelled: | ||
+ | ||
+ // Reset the anchor point | ||
+ let mid = CGPoint(x: view.bounds.midX, y: view.bounds.midY) | ||
+ let newCenter = view.superview!.convertPoint(mid, fromView: view) | ||
+ center.reset(newCenter) | ||
+ anchorPoint = CGPoint(x: 0.5, y: 0.5) | ||
+ | ||
+ var velocity = SimpleTransform.zero | ||
+ velocity.scale = recognizer.scaleVelocity | ||
+ velocity.rotation = recognizer.rotationVelocity | ||
+ var config = SpringConfiguration() | ||
+ config.threshold = 0.001 | ||
+ transform.velocity = velocity | ||
+ | ||
+ let centerVel = recognizer.translationVelocityInView(view.superview) | ||
+ var centerConfig = SpringConfiguration() | ||
+ centerConfig.tension = 40.0 | ||
+ centerConfig.damping = 5.0 | ||
+ center.velocity = centerVel | ||
+ | ||
+ gestureInProgress = false | ||
+ | ||
+ if recognizer.scaleVelocity <= 0.0 && transform.value.scale < 1.0 && browserView?.fullScreenItem == self { | ||
+ browserView?.leaveFullScreen() | ||
+ } else if recognizer.scaleVelocity >= 0.0 && transform.value.scale > 0.75 && browserView?.fullScreenItem != self { | ||
+ browserView?.enterFullScreen(self) | ||
+ } else { | ||
+ browserView?.updateAllItems(true) | ||
+ } | ||
+ break | ||
+ default: | ||
+ break | ||
+ } | ||
+ } | ||
+ | ||
+ dynamic func pan(recognizer: UIPanGestureRecognizer) { | ||
+ switch recognizer.state { | ||
+ case .Began: | ||
+ gestureInProgress = true | ||
+ centerWhenGestureBegan = center.value | ||
+ center.reset(center.value) | ||
+ break | ||
+ case .Changed: | ||
+ var c = centerWhenGestureBegan | ||
+ c.y += recognizer.translationInView(view.superview).y | ||
+ center.reset(c) | ||
+ break | ||
+ case .Ended: | ||
+ gestureInProgress = false | ||
+ center.velocity.y = recognizer.velocityInView(view.superview).y | ||
+ if abs(recognizer.translationInView(view.superview).y) > 10.0 { | ||
+ browserView?.leaveFullScreen() | ||
+ } else { | ||
+ browserView?.updateAllItems(true) | ||
+ } | ||
+ default: | ||
+ break | ||
+ } | ||
+ } | ||
+} | ||
+ | ||
+extension BrowserItem: UIGestureRecognizerDelegate { | ||
+ func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool { | ||
+ if gestureRecognizer == panRecognizer { | ||
+ if browserView?.fullScreenItem != self { | ||
+ return false | ||
+ } | ||
+ | ||
+ let translation = panRecognizer.translationInView(view) | ||
+ if abs(translation.x) > abs(translation.y) { | ||
+ return false | ||
+ } | ||
+ } | ||
+ return true | ||
+ } | ||
+} | ||
+ | ||
+func ==(lhs: BrowserItem, rhs: BrowserItem) -> Bool { | ||
+ return lhs === rhs | ||
+} | ||
+ | ||
+protocol BrowserViewDelegate: class { | ||
+ func browserView(browserView: BrowserView, didShowItem item: BrowserItem) | ||
+ func browserView(browserView: BrowserView, didHideItem item: BrowserItem) | ||
+ func browserView(browserView: BrowserView, didEnterFullScreenForItem item: BrowserItem) | ||
+ func browserView(browserView: BrowserView, didLeaveFullScreenForItem item: BrowserItem) | ||
+ func browserViewDidScroll(browserView: BrowserView) | ||
+} | ||
+ | ||
+class BrowserView: UIView { | ||
+ | ||
+ weak var delegate: BrowserViewDelegate? = nil | ||
+ | ||
+ private let paginationRatio: CGFloat = 0.68 | ||
+ | ||
+ private let index = Animatable(value: CGFloat.zero) | ||
+ | ||
+ private var panInProgress = false | ||
+ private var indexWhenPanBegan: CGFloat = 0.0 | ||
+ | ||
+ private var visibleItems: Set<BrowserItem> = [] | ||
+ | ||
+ private let panRecognizer = UIPanGestureRecognizer() | ||
+ | ||
+ private var lastLayoutSize = CGSize.zero | ||
+ | ||
+ private var fullScreenItem: BrowserItem? = nil | ||
+ | ||
+ private let coverVisibilty: Spring<CGFloat> = { | ||
+ let s = Spring(value: CGFloat(1.0)) | ||
+ s.configuration.threshold = 0.001 | ||
+ s.configuration.tension = 220.0 | ||
+ s.configuration.damping = 28.0 | ||
+ return s | ||
+ }() | ||
+ | ||
+ var coverView: CoverView? = nil { | ||
+ didSet { | ||
+ guard coverView !== oldValue else { return } | ||
+ oldValue?.removeFromSuperview() | ||
+ if let v = coverView { | ||
+ addSubview(v) | ||
+ } | ||
+ setNeedsLayout() | ||
+ } | ||
+ } | ||
+ | ||
+ | ||
+ var currentIndex: CGFloat { | ||
+ return index.value | ||
+ } | ||
+ | ||
+ var items: [BrowserItem] = [] { | ||
+ willSet { | ||
+ leaveFullScreen() | ||
+ for item in visibleItems { | ||
+ hideItem(item) | ||
+ } | ||
+ | ||
+ for item in items { | ||
+ item.browserView = nil | ||
+ } | ||
+ } | ||
+ didSet { | ||
+ for item in items { | ||
+ item.browserView = self | ||
+ } | ||
+ updateAllItems(false) | ||
+ setNeedsLayout() | ||
+ } | ||
+ } | ||
+ | ||
+ override init(frame: CGRect) { | ||
+ super.init(frame: frame) | ||
+ | ||
+ index.changed.observe { [unowned self] (idx) -> Void in | ||
+ self.setNeedsLayout() | ||
+ self.delegate?.browserViewDidScroll(self) | ||
+ } | ||
+ | ||
+ coverVisibilty.changed.observe { [unowned self] (v) in | ||
+ self.setNeedsLayout() | ||
+ } | ||
+ | ||
+ panRecognizer.addTarget(self, action: "pan:") | ||
+ panRecognizer.delegate = self | ||
+ addGestureRecognizer(panRecognizer) | ||
+ } | ||
+ | ||
+ required init?(coder aDecoder: NSCoder) { | ||
+ fatalError("init(coder:) has not been implemented") | ||
+ } | ||
+ | ||
+ override func layoutSubviews() { | ||
+ super.layoutSubviews() | ||
+ guard bounds.size != CGSize.zero else { return } | ||
+ | ||
+ if lastLayoutSize != bounds.size { | ||
+ lastLayoutSize = bounds.size | ||
+ updateAllItems(false) | ||
+ } | ||
+ | ||
+ var b = bounds | ||
+ b.origin.x = 0.0 + (index.value * b.width * paginationRatio) | ||
+ if bounds != b { | ||
+ bounds = b | ||
+ } | ||
+ | ||
+ // bring centermost item to front | ||
+ var closestDistance = CGFloat.max | ||
+ var closestItem: BrowserItem? = nil | ||
+ for item in visibleItems { | ||
+ let distance = abs(item.center.value.x - bounds.midX) | ||
+ if distance < closestDistance { | ||
+ closestDistance = distance | ||
+ closestItem = item | ||
+ } | ||
+ } | ||
+ if let item = closestItem { | ||
+ bringSubviewToFront(item.view) | ||
+ } | ||
+ | ||
+ updateAllItems(true) | ||
+ updateVisibleItems() | ||
+ | ||
+ | ||
+ // If present, update the cover view. | ||
+ if let cv = coverView { | ||
+ let cvVis = coverVisibilty.value | ||
+ | ||
+ let initialCenter = CGPoint(x: bounds.midX, y: bounds.midY) | ||
+ let finalCenter = CGPoint(x: bounds.midX, y: bounds.height * 0.3 * 0.25) | ||
+ cv.center = initialCenter.interpolatedTo(finalCenter, alpha: 1.0-Scalar(cvVis)) | ||
+ | ||
+ let t = CGAffineTransformMakeScale(0.7 + cvVis*0.3, 0.7 + cvVis*0.3) | ||
+ cv.transform = t | ||
+ | ||
+ cv.URLVisibility = CGFloat(cvVis) | ||
+ cv.alpha = 0.5 + CGFloat(cvVis*0.5) | ||
+ } | ||
+ | ||
+ var coverVisibility = 1.0 - currentIndex | ||
+ coverVisibility = min(coverVisibility, 1.0) | ||
+ coverVisibility = max(coverVisibility, 0.0) | ||
+ coverVisibilty.target = coverVisibility | ||
+ } | ||
+ | ||
+ private func updateAllItems(animated: Bool) { | ||
+ for i in 0..<items.count { | ||
+ updateItemAtIndex(i, animated: animated) | ||
+ } | ||
+ } | ||
+ | ||
+ private func updateItemAtIndex(index: Int, animated: Bool) { | ||
+ let item = items[index] | ||
+ guard item.gestureInProgress == false else { return } | ||
+ | ||
+ var center = CGPoint.zero | ||
+ center.y = bounds.midY | ||
+ center.x = bounds.width/2.0 + (CGFloat(index+1) * bounds.width * paginationRatio) | ||
+ let size = bounds.size | ||
+ | ||
+ var transform = SimpleTransform() | ||
+ | ||
+ var distance = abs(center.x - bounds.midX) | ||
+ distance = min(distance, bounds.width*paginationRatio) / bounds.width*paginationRatio | ||
+ transform.scale = 0.7 - (0.3 * distance) | ||
+ | ||
+ let tension = 60.0 + Scalar(distance) * 40.0 | ||
+ | ||
+ item.transform.configuration.tension = tension | ||
+ item.transform.configuration.damping = 18.0 | ||
+ | ||
+ if item == fullScreenItem { | ||
+ transform.scale = 1.0 | ||
+ item.transform.configuration.tension = 160.0 | ||
+ item.transform.configuration.damping = 28.0 | ||
+ } | ||
+ | ||
+ if animated { | ||
+ item.center.target = center | ||
+ item.size.target = size | ||
+ item.transform.target = transform | ||
+ } else { | ||
+ item.center.reset(center) | ||
+ item.size.reset(size) | ||
+ item.transform.reset(transform) | ||
+ } | ||
+ } | ||
+ | ||
+ private func updateVisibleItems() { | ||
+ | ||
+ for item in items { | ||
+ let isVisible = visibleItems.contains(item) | ||
+ let shouldBeVisible = item.frame.intersects(bounds) | ||
+ if isVisible && !shouldBeVisible { | ||
+ hideItem(item) | ||
+ } else if !isVisible && shouldBeVisible { | ||
+ showItem(item) | ||
+ } | ||
+ | ||
+ if shouldBeVisible { | ||
+ updateViewForItemAtIndex(items.indexOf(item)!) | ||
+ } | ||
+ } | ||
+ | ||
+ } | ||
+ | ||
+ private func showItem(item: BrowserItem) { | ||
+ assert(item.browserView == self) | ||
+ assert(visibleItems.contains(item) == false) | ||
+ visibleItems.insert(item) | ||
+ updateViewForItemAtIndex(items.indexOf(item)!) | ||
+ addSubview(item.view) | ||
+ delegate?.browserView(self, didShowItem: item) | ||
+ } | ||
+ | ||
+ private func updateViewForItemAtIndex(index: Int) { | ||
+ let item = items[index] | ||
+ item.view.bounds = CGRect(origin: CGPoint.zero, size: item.size.value) | ||
+ item.view.center = item.center.value | ||
+ item.view.layer.anchorPoint = item.anchorPoint | ||
+ item.view.transform = item.transform.value.affineTransform | ||
+ } | ||
+ | ||
+ private func hideItem(item: BrowserItem) { | ||
+ assert(item.browserView == self) | ||
+ assert(visibleItems.contains(item)) | ||
+ visibleItems.remove(item) | ||
+ item.view.removeFromSuperview() | ||
+ delegate?.browserView(self, didHideItem: item) | ||
+ } | ||
+ | ||
+ func enterFullScreen(item: BrowserItem) { | ||
+ assert(item.browserView == self) | ||
+ leaveFullScreen() | ||
+ fullScreenItem = item | ||
+ updateAllItems(true) | ||
+ index.animateTo(CGFloat(items.indexOf(item)! + 1)) | ||
+ delegate?.browserView(self, didEnterFullScreenForItem: item) | ||
+ } | ||
+ | ||
+ func leaveFullScreen() { | ||
+ guard let item = fullScreenItem else { return } | ||
+ fullScreenItem = nil | ||
+ updateAllItems(true) | ||
+ delegate?.browserView(self, didLeaveFullScreenForItem: item) | ||
+ } | ||
+ | ||
+ private dynamic func pan(recognizer: UIPanGestureRecognizer) { | ||
+ switch recognizer.state { | ||
+ case .Began: | ||
+ panInProgress = true | ||
+ indexWhenPanBegan = index.value | ||
+ index.cancelAnimation() | ||
+ case .Changed: | ||
+ index.value = indexWhenPanBegan - (recognizer.translationInView(self).x / bounds.width * paginationRatio) | ||
+ break | ||
+ case .Ended, .Cancelled: | ||
+ panInProgress = false | ||
+ let vel = -recognizer.velocityInView(self).x / bounds.width * paginationRatio | ||
+ var destIndex = round(index.value + (vel/5.0)) | ||
+ if destIndex == round(index.value) { | ||
+ if vel < 0.0 { | ||
+ destIndex -= 1.0 | ||
+ } else if vel > 0.0 { | ||
+ destIndex += 1.0 | ||
+ } | ||
+ } | ||
+ destIndex = min(destIndex, CGFloat(items.count)) | ||
+ destIndex = max(destIndex, CGFloat(0.0)) | ||
+ var config = SpringConfiguration() | ||
+ config.tension = 120.0 | ||
+ config.damping = 20.0 | ||
+ config.threshold = 0.001 | ||
+ index.springTo(destIndex, initialVelocity: vel, configuration: config, completion: nil) | ||
+ default: | ||
+ break | ||
+ } | ||
+ } | ||
+ | ||
+} | ||
+ | ||
+extension BrowserView: UIGestureRecognizerDelegate { | ||
+ override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool { | ||
+ if gestureRecognizer == panRecognizer && fullScreenItem != nil { | ||
+ return false | ||
+ } | ||
+ return true | ||
+ } | ||
+} |
150
AdvanceSample/BrowserViewController.swift
@@ -0,0 +1,150 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import UIKit | ||
+import Advance | ||
+ | ||
+private final class DemoItem: BrowserItem { | ||
+ let viewController: DemoViewController | ||
+ init(viewController: DemoViewController) { | ||
+ self.viewController = viewController | ||
+ super.init() | ||
+ } | ||
+} | ||
+ | ||
+final class BrowserViewController: UIViewController { | ||
+ | ||
+ let viewControllers: [DemoViewController] | ||
+ | ||
+ let backgroundImageView = UIImageView(frame: CGRect.zero) | ||
+ let blurredBackgroundImageView = UIImageView(frame: CGRect.zero) | ||
+ let backgroundDimmingView = UIView(frame: CGRect.zero) | ||
+ | ||
+ let blurSpring = Spring(value: CGFloat.zero) | ||
+ | ||
+ let browserView = BrowserView(frame: CGRect.zero) | ||
+ | ||
+ required init(viewControllers: [DemoViewController]) { | ||
+ self.viewControllers = viewControllers | ||
+ super.init(nibName: nil, bundle: nil) | ||
+ | ||
+ var cfg = SpringConfiguration() | ||
+ cfg.threshold = 0.001 | ||
+ cfg.tension = 60.0 | ||
+ cfg.damping = 26.0 | ||
+ blurSpring.configuration = cfg | ||
+ } | ||
+ | ||
+ required init?(coder aDecoder: NSCoder) { | ||
+ fatalError("init(coder:) has not been implemented") | ||
+ } | ||
+ | ||
+ override func viewDidLoad() { | ||
+ super.viewDidLoad() | ||
+ | ||
+ | ||
+ | ||
+ backgroundImageView.contentMode = .ScaleAspectFill | ||
+ backgroundImageView.image = UIImage(named: "background") | ||
+ view.addSubview(backgroundImageView) | ||
+ | ||
+ blurredBackgroundImageView.contentMode = .ScaleAspectFill | ||
+ blurredBackgroundImageView.image = UIImage(named: "background-blurred") | ||
+ blurredBackgroundImageView.alpha = 0.0 | ||
+ view.addSubview(blurredBackgroundImageView) | ||
+ | ||
+ backgroundDimmingView.backgroundColor = UIColor.blackColor() | ||
+ backgroundDimmingView.alpha = 0.3 | ||
+ view.addSubview(backgroundDimmingView) | ||
+ | ||
+ let cv = CoverView(frame: CGRect(x: 0.0, y: 0.0, width: 300.0, height: 300.0)) | ||
+ browserView.coverView = cv | ||
+ | ||
+ | ||
+ | ||
+ view.addSubview(browserView) | ||
+ browserView.delegate = self | ||
+ | ||
+ browserView.items = viewControllers.map({ (vc) -> DemoItem in | ||
+ return DemoItem(viewController: vc) | ||
+ }) | ||
+ | ||
+ blurSpring.changed.observe { [unowned self] (b) in | ||
+ self.blurredBackgroundImageView.alpha = b | ||
+ } | ||
+ } | ||
+ | ||
+ override func viewDidLayoutSubviews() { | ||
+ super.viewDidLayoutSubviews() | ||
+ backgroundImageView.frame = view.bounds | ||
+ blurredBackgroundImageView.frame = view.bounds | ||
+ backgroundDimmingView.frame = view.bounds | ||
+ browserView.frame = view.bounds | ||
+ } | ||
+ | ||
+ override func prefersStatusBarHidden() -> Bool { | ||
+ return true | ||
+ } | ||
+} | ||
+ | ||
+extension BrowserViewController: BrowserViewDelegate { | ||
+ func browserView(browserView: BrowserView, didShowItem item: BrowserItem) { | ||
+ guard let item = item as? DemoItem else { fatalError() } | ||
+ assert(item.viewController.parentViewController != self) | ||
+ addChildViewController(item.viewController) | ||
+ item.viewController.view.frame = item.view.bounds | ||
+ item.view.addSubview(item.viewController.view) | ||
+ item.viewController.didMoveToParentViewController(self) | ||
+ } | ||
+ | ||
+ func browserView(browserView: BrowserView, didHideItem item: BrowserItem) { | ||
+ guard let item = item as? DemoItem else { fatalError() } | ||
+ assert(item.viewController.parentViewController == self) | ||
+ item.viewController.willMoveToParentViewController(nil) | ||
+ item.viewController.view.removeFromSuperview() | ||
+ item.viewController.removeFromParentViewController() | ||
+ } | ||
+ | ||
+ func browserView(browserView: BrowserView, didEnterFullScreenForItem item: BrowserItem) { | ||
+ guard let item = item as? DemoItem else { fatalError() } | ||
+ item.viewController.fullScreen = true | ||
+ | ||
+ } | ||
+ | ||
+ func browserView(browserView: BrowserView, didLeaveFullScreenForItem item: BrowserItem) { | ||
+ guard let item = item as? DemoItem else { fatalError() } | ||
+ item.viewController.fullScreen = false | ||
+ } | ||
+ | ||
+ func browserViewDidScroll(browserView: BrowserView) { | ||
+ var blurAmount = browserView.currentIndex | ||
+ blurAmount = min(blurAmount, 1.0) | ||
+ blurAmount = max(blurAmount, 0.0) | ||
+ blurSpring.target = blurAmount | ||
+ } | ||
+} |
76
AdvanceSample/CoverView.swift
@@ -0,0 +1,76 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import UIKit | ||
+ | ||
+final class CoverView: UIView { | ||
+ | ||
+ let logoView: UIImageView | ||
+ | ||
+ let urlLabel: UILabel | ||
+ | ||
+ var URLVisibility: CGFloat = 1.0 { | ||
+ didSet { | ||
+ urlLabel.alpha = URLVisibility | ||
+ } | ||
+ } | ||
+ | ||
+ override init(frame: CGRect) { | ||
+ logoView = UIImageView(image: UIImage(named: "logo")) | ||
+ logoView.tintColor = UIColor.whiteColor() | ||
+ logoView.sizeToFit() | ||
+ | ||
+ urlLabel = UILabel(frame: CGRect.zero) | ||
+ | ||
+ var attribs: [String: AnyObject] = [:] | ||
+ attribs[NSFontAttributeName] = UIFont.systemFontOfSize(12.0, weight: UIFontWeightRegular) | ||
+ attribs[NSForegroundColorAttributeName] = UIColor.whiteColor() | ||
+ | ||
+ // attribs[NSUnderlineStyleAttributeName] = NSUnderlineStyle.StyleSingle.rawValue | ||
+ | ||
+ urlLabel.attributedText = NSAttributedString(string: "github.com/storehouse/Advance", attributes: attribs) | ||
+ urlLabel.sizeToFit() | ||
+ | ||
+ super.init(frame: frame) | ||
+ | ||
+ addSubview(logoView) | ||
+ addSubview(urlLabel) | ||
+ | ||
+ } | ||
+ | ||
+ required init?(coder aDecoder: NSCoder) { | ||
+ fatalError("init(coder:) has not been implemented") | ||
+ } | ||
+ | ||
+ override func layoutSubviews() { | ||
+ super.layoutSubviews() | ||
+ logoView.center = CGPoint(x: bounds.midX, y: bounds.midY) | ||
+ urlLabel.center = CGPoint(x: bounds.midX, y: logoView.frame.maxY + 4.0 + urlLabel.bounds.height/2.0) | ||
+ } | ||
+ | ||
+} |
128
AdvanceSample/DemoViewController.swift
@@ -0,0 +1,128 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import UIKit | ||
+ | ||
+class DemoViewController: UIViewController { | ||
+ | ||
+ var note: String { | ||
+ get { return noteLabel.text ?? "" } | ||
+ set { | ||
+ noteLabel.text = newValue | ||
+ view.setNeedsLayout() | ||
+ } | ||
+ } | ||
+ | ||
+ override var title: String? { | ||
+ didSet { | ||
+ titleLabel.text = title | ||
+ view.setNeedsLayout() | ||
+ } | ||
+ } | ||
+ | ||
+ let contentView = UIView() | ||
+ | ||
+ private let titleLabel = UILabel() | ||
+ | ||
+ private let noteLabel = UILabel() | ||
+ | ||
+ override func viewDidLoad() { | ||
+ super.viewDidLoad() | ||
+ view.backgroundColor = UIColor(white: 1.0, alpha: 1.0) | ||
+ | ||
+ contentView.alpha = 0.4 | ||
+ contentView.layer.allowsGroupOpacity = false | ||
+ contentView.userInteractionEnabled = false | ||
+ contentView.frame = view.bounds | ||
+ view.addSubview(contentView) | ||
+ | ||
+ titleLabel.font = UIFont.systemFontOfSize(32.0, weight: UIFontWeightMedium) | ||
+ titleLabel.textColor = UIColor.darkGrayColor() | ||
+ titleLabel.textAlignment = .Center | ||
+ titleLabel.numberOfLines = 0 | ||
+ view.addSubview(titleLabel) | ||
+ | ||
+ noteLabel.font = UIFont.systemFontOfSize(16.0, weight: UIFontWeightThin) | ||
+ noteLabel.textColor = UIColor.darkGrayColor() | ||
+ noteLabel.textAlignment = .Center | ||
+ noteLabel.numberOfLines = 0 | ||
+ noteLabel.alpha = 0.0 | ||
+ view.addSubview(noteLabel) | ||
+ } | ||
+ | ||
+ final var fullScreen = false { | ||
+ didSet { | ||
+ guard fullScreen != oldValue else { return } | ||
+ | ||
+ UIView.animateWithDuration(0.4) { | ||
+ if self.fullScreen { | ||
+ self.didEnterFullScreen() | ||
+ } else { | ||
+ self.didLeaveFullScreen() | ||
+ } | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ func didEnterFullScreen() { | ||
+ noteLabel.alpha = 1.0 | ||
+ contentView.alpha = 1.0 | ||
+ contentView.userInteractionEnabled = true | ||
+ titleLabel.alpha = 0.0 | ||
+ } | ||
+ | ||
+ func didLeaveFullScreen() { | ||
+ noteLabel.alpha = 0.0 | ||
+ contentView.alpha = 0.5 | ||
+ contentView.userInteractionEnabled = false | ||
+ titleLabel.alpha = 1.0 | ||
+ } | ||
+ | ||
+ override func viewDidLayoutSubviews() { | ||
+ super.viewDidLayoutSubviews() | ||
+ | ||
+ contentView.frame = view.bounds | ||
+ | ||
+ let labelHeight = noteLabel.sizeThatFits(CGSize(width: view.bounds.width-64.0, height: CGFloat.max)).height | ||
+ var labelFrame = CGRect.zero | ||
+ labelFrame.origin.x = 32.0 | ||
+ labelFrame.origin.y = 32.0 | ||
+ labelFrame.size.width = view.bounds.width - 64.0 | ||
+ labelFrame.size.height = labelHeight | ||
+ noteLabel.frame = labelFrame | ||
+ | ||
+ let titleHeight = titleLabel.sizeThatFits(CGSize(width: view.bounds.width-64.0, height: CGFloat.max)).height | ||
+ var titleFrame = CGRect.zero | ||
+ titleFrame.origin.x = 32.0 | ||
+ titleFrame.origin.y = 32.0 | ||
+ titleFrame.size.width = view.bounds.width - 64.0 | ||
+ titleFrame.size.height = titleHeight | ||
+ titleLabel.frame = titleFrame | ||
+ } | ||
+ | ||
+} |
360
AdvanceSample/DirectManipulationGestureRecognizer.swift
@@ -0,0 +1,360 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import Foundation | ||
+import UIKit.UIGestureRecognizerSubclass | ||
+ | ||
+private let π = CGFloat(M_PI) | ||
+ | ||
+ | ||
+public final class DirectManipulationGestureRecognizer: UIGestureRecognizer { | ||
+ | ||
+ public enum PinchDirection { | ||
+ case In | ||
+ case Out | ||
+ case Any | ||
+ } | ||
+ | ||
+ public var requiredPinchDirection: PinchDirection = .Any | ||
+ | ||
+ // The state of the touches when we first saw them | ||
+ private var preliminaryState: GestureState = GestureState() | ||
+ | ||
+ // The state of the touches when the recognizer successfully began | ||
+ private var initialState: GestureState = GestureState() | ||
+ | ||
+ // The current and previous states, for reference. | ||
+ private var previousState: GestureState = GestureState() | ||
+ private var currentState: GestureState = GestureState() | ||
+ | ||
+ // As per the UIKit documentation, we don't retain the touches. But | ||
+ // we do remember their address so that we can compare against them as needed. | ||
+ private var firstTouchPointer: UnsafePointer<Void>? = nil | ||
+ private var secondTouchPointer: UnsafePointer<Void>? = nil | ||
+ | ||
+ private var previousCumulativeAngle: CGFloat = 0.0 | ||
+ private var cumulativeAngle: CGFloat = 0.0 | ||
+ | ||
+ private var lastUpdateTime: NSTimeInterval = 0.0 | ||
+ private var lastUpdateDuration: NSTimeInterval = 0.0 | ||
+ | ||
+ public override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent) { | ||
+ super.touchesBegan(touches, withEvent: event) | ||
+ guard state == .Possible else { return } | ||
+ guard touches.count > 0 else { return } | ||
+ | ||
+ var touches = touches | ||
+ | ||
+ while touches.count > 0 { | ||
+ let t = touches.removeFirst() | ||
+ if firstTouchPointer == nil { | ||
+ firstTouchPointer = unsafeAddressOf(t) | ||
+ let state = TouchState(t: t) | ||
+ preliminaryState.firstTouchState = state | ||
+ initialState.firstTouchState = state | ||
+ previousState.firstTouchState = state | ||
+ currentState.firstTouchState = state | ||
+ } else if secondTouchPointer == nil { | ||
+ secondTouchPointer = unsafeAddressOf(t) | ||
+ let state = TouchState(t: t) | ||
+ preliminaryState.secondTouchState = state | ||
+ initialState.secondTouchState = state | ||
+ previousState.secondTouchState = state | ||
+ currentState.secondTouchState = state | ||
+ } else { | ||
+ continue | ||
+ } | ||
+ } | ||
+ | ||
+ if requiredPinchDirection == .Any && firstTouchPointer != nil && secondTouchPointer != nil { | ||
+ begin() | ||
+ } | ||
+ } | ||
+ | ||
+ public override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent) { | ||
+ super.touchesMoved(touches, withEvent: event) | ||
+ guard state == .Possible || state == .Began || state == .Changed else { return } | ||
+ | ||
+ previousState = currentState | ||
+ | ||
+ for t in touches { | ||
+ if unsafeAddressOf(t) == firstTouchPointer { | ||
+ currentState.firstTouchState = TouchState(t: t) | ||
+ } | ||
+ | ||
+ if unsafeAddressOf(t) == secondTouchPointer { | ||
+ currentState.secondTouchState = TouchState(t: t) | ||
+ } | ||
+ } | ||
+ | ||
+ // If either of the touches have just begun or ended, there's a good chance | ||
+ // that we just began or ended the gesture. It turns out that setting the state | ||
+ // to .Changed stomps on the notification of the gesture beginning – so | ||
+ // from the target's perspective, the recognizer just start claiming to | ||
+ // have changed without going through a .Began phase. We can just check | ||
+ // to make sure that neither touch has just started or ended to avoid this. | ||
+ if currentState.firstTouchState.phase == .Began || currentState.secondTouchState.phase == .Began { | ||
+ return | ||
+ } | ||
+ | ||
+ if currentState.firstTouchState.phase == .Ended || currentState.secondTouchState.phase == .Ended { | ||
+ return | ||
+ } | ||
+ | ||
+ if currentState.firstTouchState.phase == .Cancelled || currentState.secondTouchState.phase == .Cancelled { | ||
+ return | ||
+ } | ||
+ | ||
+ if state == .Possible { | ||
+ guard firstTouchPointer != nil && secondTouchPointer != nil else { return } | ||
+ | ||
+ // How far the user has pinched since we first saw the touches. | ||
+ let pinchDistance = currentState.distanceBetween - preliminaryState.distanceBetween | ||
+ | ||
+ switch requiredPinchDirection { | ||
+ case .Any: | ||
+ begin() | ||
+ case .In: | ||
+ if pinchDistance > 0.0 { | ||
+ begin() | ||
+ } else if pinchDistance < 0.0 { | ||
+ state = .Failed | ||
+ } | ||
+ case .Out: | ||
+ if pinchDistance < 0.0 { | ||
+ begin() | ||
+ } else if pinchDistance > 0.0 { | ||
+ state = .Failed | ||
+ } | ||
+ } | ||
+ | ||
+ if state == .Possible && currentState.distanceTo(preliminaryState) > 4.0 { | ||
+ state = .Failed | ||
+ } | ||
+ | ||
+ return | ||
+ } | ||
+ | ||
+ var angleDelta = currentState.angle - previousState.angle | ||
+ if (angleDelta > π) { | ||
+ angleDelta -= π*2.0; | ||
+ } else if (angleDelta < -π) { | ||
+ angleDelta += π*2.0; | ||
+ } | ||
+ | ||
+ previousCumulativeAngle = cumulativeAngle | ||
+ cumulativeAngle += angleDelta | ||
+ | ||
+ let currentTime = CACurrentMediaTime() | ||
+ lastUpdateDuration = currentTime - lastUpdateTime | ||
+ lastUpdateTime = currentTime | ||
+ | ||
+ assert(state == .Began || state == .Changed) | ||
+ state = .Changed | ||
+ } | ||
+ | ||
+ public override func touchesCancelled(touches: Set<UITouch>, withEvent event: UIEvent) { | ||
+ super.touchesCancelled(touches, withEvent: event) | ||
+ | ||
+ | ||
+ for t in touches { | ||
+ if unsafeAddressOf(t) == firstTouchPointer || unsafeAddressOf(t) == secondTouchPointer { | ||
+ if state == .Possible { | ||
+ state = .Failed | ||
+ } else if state == .Began || state == .Changed { | ||
+ state = .Cancelled | ||
+ } | ||
+ return | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ public override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent) { | ||
+ super.touchesEnded(touches, withEvent: event) | ||
+ | ||
+ for t in touches { | ||
+ if unsafeAddressOf(t) == firstTouchPointer || unsafeAddressOf(t) == secondTouchPointer { | ||
+ if state == .Possible { | ||
+ state = .Failed | ||
+ } else if state == .Began || state == .Changed { | ||
+ state = .Ended | ||
+ } | ||
+ return | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ private func begin() { | ||
+ guard state == .Possible else { fatalError() } | ||
+ initialState = currentState | ||
+ previousState = currentState | ||
+ cumulativeAngle = 0.0 | ||
+ previousCumulativeAngle = 0.0 | ||
+ lastUpdateTime = CACurrentMediaTime() | ||
+ lastUpdateDuration = 0.0 | ||
+ state = .Began | ||
+ } | ||
+ | ||
+ override public func reset() { | ||
+ super.reset() | ||
+ | ||
+ firstTouchPointer = nil | ||
+ secondTouchPointer = nil | ||
+ | ||
+ preliminaryState = GestureState() | ||
+ initialState = GestureState() | ||
+ previousState = GestureState() | ||
+ currentState = GestureState() | ||
+ } | ||
+ | ||
+ | ||
+ public func translationInView(view: UIView?) -> CGPoint { | ||
+ let current = currentState.convertedToView(view) | ||
+ let initial = initialState.convertedToView(view) | ||
+ return current.center - initial.center | ||
+ } | ||
+ | ||
+ public func translationVelocityInView(view: UIView?) -> CGPoint { | ||
+ guard active else { return CGPoint.zero } | ||
+ let current = currentState.convertedToView(view) | ||
+ let previous = previousState.convertedToView(view) | ||
+ var vel = current.center - previous.center | ||
+ vel.x /= CGFloat(lastUpdateDuration) | ||
+ vel.y /= CGFloat(lastUpdateDuration) | ||
+ return vel | ||
+ } | ||
+ | ||
+ public var rotation: CGFloat { | ||
+ guard active else { return 0.0 } | ||
+ return cumulativeAngle | ||
+ } | ||
+ | ||
+ public var rotationVelocity: CGFloat { | ||
+ guard active else { return 0.0 } | ||
+ let delta = cumulativeAngle - previousCumulativeAngle | ||
+ return delta / CGFloat(lastUpdateDuration) | ||
+ } | ||
+ | ||
+ public var scale: CGFloat { | ||
+ guard active else { return 1.0 } | ||
+ return currentState.distanceBetween / initialState.distanceBetween | ||
+ } | ||
+ | ||
+ public var scaleVelocity: CGFloat { | ||
+ guard active else { return 1.0 } | ||
+ let current = currentState.distanceBetween / initialState.distanceBetween | ||
+ let previous = previousState.distanceBetween / initialState.distanceBetween | ||
+ return ((current - previous) / CGFloat(lastUpdateDuration)) | ||
+ } | ||
+ | ||
+ private var active: Bool { | ||
+ return state == .Began || state == .Changed || state == .Ended | ||
+ } | ||
+} | ||
+ | ||
+ | ||
+private extension DirectManipulationGestureRecognizer { | ||
+ struct GestureState { | ||
+ var firstTouchState: TouchState = TouchState() | ||
+ var secondTouchState: TouchState = TouchState() | ||
+ | ||
+ | ||
+ func convertedToView(view: UIView?) -> GestureState { | ||
+ var s = self | ||
+ s.firstTouchState = s.firstTouchState.convertedToView(view) | ||
+ s.secondTouchState = s.secondTouchState.convertedToView(view) | ||
+ return s | ||
+ } | ||
+ | ||
+ func distanceTo(state: GestureState) -> CGFloat { | ||
+ return (state.center - center).distance | ||
+ } | ||
+ | ||
+ var distanceBetween: CGFloat { | ||
+ return delta.distance | ||
+ } | ||
+ | ||
+ var delta: CGPoint { | ||
+ return secondTouchState.location - firstTouchState.location | ||
+ } | ||
+ | ||
+ var angle: CGFloat { | ||
+ return delta.angle | ||
+ } | ||
+ | ||
+ var center: CGPoint { | ||
+ var p = CGPoint.zero | ||
+ p.x = firstTouchState.location.x + ((secondTouchState.location.x - firstTouchState.location.x) * 0.5) | ||
+ p.y = firstTouchState.location.y + ((secondTouchState.location.y - firstTouchState.location.y) * 0.5) | ||
+ return p | ||
+ } | ||
+ } | ||
+ | ||
+ struct TouchState { | ||
+ var location: CGPoint = CGPoint.zero | ||
+ var phase: UITouchPhase = UITouchPhase.Began | ||
+ | ||
+ init() {} | ||
+ | ||
+ init(t: UITouch) { | ||
+ location = t.locationInView(nil) | ||
+ phase = t.phase | ||
+ } | ||
+ | ||
+ func convertedToView(view: UIView?) -> TouchState { | ||
+ var s = self | ||
+ if let view = view { | ||
+ s.location = view.convertPoint(s.location, fromView: nil) | ||
+ } | ||
+ return s | ||
+ } | ||
+ | ||
+ func distanceTo(state: TouchState) -> CGFloat { | ||
+ return (state.location - location).distance | ||
+ } | ||
+ } | ||
+} | ||
+ | ||
+private extension CGPoint { | ||
+ | ||
+ var angle: CGFloat { | ||
+ return atan2(y, x); | ||
+ } | ||
+ | ||
+ var distance: CGFloat { | ||
+ return sqrt((x*x) + (y*y)) | ||
+ } | ||
+ | ||
+} | ||
+ | ||
+private func -(lhs: CGPoint, rhs: CGPoint) -> CGPoint { | ||
+ var result = CGPoint.zero | ||
+ result.x = lhs.x - rhs.x | ||
+ result.y = lhs.y - rhs.y | ||
+ return result | ||
+} |
126
AdvanceSample/GestureView.swift
@@ -0,0 +1,126 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import UIKit | ||
+import Advance | ||
+ | ||
+final class GestureView: UIView { | ||
+ | ||
+ let animatableCenter = Animatable(value: CGPoint.zero) | ||
+ ;let animatableTransform = Animatable(value: SimpleTransform()) | ||
+ | ||
+ private var centerWhenGestureBegan = CGPoint.zero | ||
+ private var transformWhenGestureBegan = SimpleTransform.zero | ||
+ | ||
+ private let recognizer = DirectManipulationGestureRecognizer() | ||
+ | ||
+ override init(frame: CGRect) { | ||
+ super.init(frame: frame) | ||
+ | ||
+ backgroundColor = UIColor(red: 0.0, green: 196.0/255.0, blue: 1.0, alpha: 1.0) | ||
+ | ||
+ recognizer.addTarget(self, action: "manipulate:") | ||
+ addGestureRecognizer(recognizer) | ||
+ | ||
+ animatableCenter.changed.observe { [weak self] (c) -> Void in | ||
+ self?.center = c | ||
+ } | ||
+ | ||
+ animatableTransform.changed.observe { [weak self] (t) -> Void in | ||
+ self?.transform = t.affineTransform | ||
+ } | ||
+ } | ||
+ | ||
+ required init?(coder aDecoder: NSCoder) { | ||
+ fatalError("init(coder:) has not been implemented") | ||
+ } | ||
+ | ||
+ override var frame: CGRect { | ||
+ didSet { | ||
+ animatableCenter.value = center | ||
+ } | ||
+ } | ||
+ | ||
+ private dynamic func manipulate(recognizer: DirectManipulationGestureRecognizer) { | ||
+ switch recognizer.state { | ||
+ case .Began: | ||
+ | ||
+ // Take the anchor point into consideration | ||
+ let gestureLocation = recognizer.locationInView(self) | ||
+ let newCenter = superview!.convertPoint(gestureLocation, fromView: self) | ||
+ animatableCenter.value = newCenter | ||
+ | ||
+ var anchorPoint = gestureLocation | ||
+ anchorPoint.x /= bounds.width | ||
+ anchorPoint.y /= bounds.height | ||
+ layer.anchorPoint = anchorPoint | ||
+ | ||
+ animatableTransform.cancelAnimation() | ||
+ animatableCenter.cancelAnimation() | ||
+ centerWhenGestureBegan = animatableCenter.value | ||
+ transformWhenGestureBegan = animatableTransform.value | ||
+ break | ||
+ case .Changed: | ||
+ var t = transformWhenGestureBegan | ||
+ t.rotation += recognizer.rotation | ||
+ t.scale *= recognizer.scale | ||
+ animatableTransform.value = t | ||
+ | ||
+ var center = centerWhenGestureBegan | ||
+ center.x += recognizer.translationInView(superview).x | ||
+ center.y += recognizer.translationInView(superview).y | ||
+ animatableCenter.value = center | ||
+ | ||
+ break | ||
+ case .Ended, .Cancelled: | ||
+ // Reset the anchor point | ||
+ let mid = CGPoint(x: bounds.midX, y: bounds.midY) | ||
+ let newCenter = superview!.convertPoint(mid, fromView: self) | ||
+ animatableCenter.value = newCenter | ||
+ layer.anchorPoint = CGPoint(x: 0.5, y: 0.5) | ||
+ | ||
+ var velocity = SimpleTransform.zero | ||
+ velocity.scale = recognizer.scaleVelocity | ||
+ velocity.rotation = recognizer.rotationVelocity | ||
+ var config = SpringConfiguration() | ||
+ config.threshold = 0.001 | ||
+ animatableTransform.springTo(SimpleTransform(), initialVelocity: velocity, configuration: config) | ||
+ | ||
+ let centerVel = recognizer.translationVelocityInView(superview) | ||
+ var centerConfig = SpringConfiguration() | ||
+ centerConfig.tension = 40.0 | ||
+ centerConfig.damping = 5.0 | ||
+ let c = CGPoint(x: superview!.bounds.midX, y: superview!.bounds.midY) | ||
+ animatableCenter.springTo(c, initialVelocity: centerVel, configuration: centerConfig) | ||
+ break | ||
+ default: | ||
+ break | ||
+ } | ||
+ } | ||
+ | ||
+} |
84
AdvanceSample/GesturesViewController.swift
@@ -0,0 +1,84 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import UIKit | ||
+ | ||
+final class GesturesViewController: DemoViewController { | ||
+ | ||
+ let gestureView = GestureView() | ||
+ | ||
+ required init() { | ||
+ super.init(nibName: nil, bundle: nil) | ||
+ title = "Gestures" | ||
+ } | ||
+ | ||
+ required init?(coder aDecoder: NSCoder) { | ||
+ fatalError("init(coder:) has not been implemented") | ||
+ } | ||
+ | ||
+ override func viewDidLoad() { | ||
+ super.viewDidLoad() | ||
+ | ||
+ note = "Use two fingers to pick up the square." | ||
+ | ||
+ contentView.addSubview(gestureView) | ||
+ | ||
+ // Do any additional setup after loading the view. | ||
+ } | ||
+ | ||
+ override func viewWillAppear(animated: Bool) { | ||
+ super.viewWillAppear(animated) | ||
+ } | ||
+ | ||
+ override func viewDidLayoutSubviews() { | ||
+ super.viewDidLayoutSubviews() | ||
+ var b = CGRect.zero | ||
+ b.size.width = min(view.bounds.width, view.bounds.height) - 64.0 | ||
+ b.size.height = b.size.width | ||
+ gestureView.bounds = b | ||
+ gestureView.animatableCenter.value = CGPoint(x: contentView.bounds.midX, y: contentView.bounds.midY) | ||
+ gestureView.animatableTransform.value = SimpleTransform() | ||
+ } | ||
+ | ||
+ override func didReceiveMemoryWarning() { | ||
+ super.didReceiveMemoryWarning() | ||
+ // Dispose of any resources that can be recreated. | ||
+ } | ||
+ | ||
+ | ||
+ /* | ||
+ // MARK: - Navigation | ||
+ | ||
+ // In a storyboard-based application, you will often want to do a little preparation before navigation | ||
+ override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { | ||
+ // Get the new view controller using segue.destinationViewController. | ||
+ // Pass the selected object to the new view controller. | ||
+ } | ||
+ */ | ||
+ | ||
+} |
81
AdvanceSample/GravityFunction.swift
@@ -0,0 +1,81 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import Advance | ||
+import Foundation | ||
+ | ||
+struct GravityFunction: DynamicFunctionType { | ||
+ | ||
+ typealias Vector = Vector2 | ||
+ | ||
+ var target: Vector | ||
+ | ||
+ var minRadius = 30.0 | ||
+ | ||
+ var threshold: Scalar = 0.1 | ||
+ | ||
+ init(target: Vector) { | ||
+ self.target = target | ||
+ } | ||
+ | ||
+ func acceleration(value: Vector, velocity: Vector) -> Vector { | ||
+ | ||
+ let delta = target - value | ||
+ let heading = atan2(delta.y, delta.x) | ||
+ | ||
+ var distance = hypot(delta.x, delta.y) | ||
+ distance = max(distance, minRadius) | ||
+ | ||
+ let accel = 1000000.0 / (distance*distance) | ||
+ | ||
+ var result = Vector.zero | ||
+ result.x = accel * cos(heading) | ||
+ result.y = accel * sin(heading) | ||
+ return result | ||
+ } | ||
+ | ||
+ func canSettle(value: Vector, velocity: Vector) -> Bool { | ||
+ let min = Vector(scalar: -threshold) | ||
+ let max = Vector(scalar: threshold) | ||
+ | ||
+ if velocity.clamped(min: min, max: max) != velocity { | ||
+ return false | ||
+ } | ||
+ | ||
+ let valueDelta = value - target | ||
+ if valueDelta.clamped(min: min, max: max) != valueDelta { | ||
+ return false | ||
+ } | ||
+ | ||
+ return true | ||
+ } | ||
+ | ||
+ func settledValue(value: Vector, velocity: Vector) -> Vector { | ||
+ return target | ||
+ } | ||
+} |
181
AdvanceSample/GravitySimulation.swift
@@ -0,0 +1,181 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import CoreGraphics | ||
+import Advance | ||
+ | ||
+ | ||
+private enum GravitySimulationNode: Advanceable { | ||
+ case Static(Vector2) | ||
+ case Decay(DynamicSolver<DecayFunction<Vector2>>) | ||
+ case Pull(DynamicSolver<GravityFunction>) | ||
+ | ||
+ var value: Vector2 { | ||
+ switch self { | ||
+ case .Static(let v): | ||
+ return v | ||
+ case .Decay(let d): | ||
+ return d.value | ||
+ case .Pull(let s): | ||
+ return s.value | ||
+ } | ||
+ } | ||
+ | ||
+ var velocity: Vector2 { | ||
+ switch self { | ||
+ case .Static(_): | ||
+ return Vector2.zero | ||
+ case .Decay(let d): | ||
+ return d.velocity | ||
+ case .Pull(let s): | ||
+ return s.velocity | ||
+ } | ||
+ } | ||
+ | ||
+ mutating func advance(elapsed: Double) { | ||
+ switch self { | ||
+ case .Static(_): | ||
+ break | ||
+ case .Decay(var sim): | ||
+ sim.advance(elapsed) | ||
+ self = sim.settled ? .Static(sim.value) : .Decay(sim) | ||
+ case .Pull(var sim): | ||
+ sim.advance(elapsed) | ||
+ self = sim.settled ? .Static(sim.value) : .Pull(sim) | ||
+ } | ||
+ } | ||
+ | ||
+ var settled: Bool { | ||
+ switch self { | ||
+ case .Static(_): | ||
+ return true | ||
+ case .Pull(_), .Decay(_): | ||
+ return false | ||
+ } | ||
+ } | ||
+ | ||
+ mutating func pullTo(point: CGPoint) { | ||
+ if case var .Pull(sim) = self { | ||
+ sim.function.target = point.vector | ||
+ self = .Pull(sim) | ||
+ return | ||
+ } | ||
+ | ||
+ let f = GravityFunction(target: point.vector) | ||
+ let solver = DynamicSolver(function: f, value: value, velocity: velocity) | ||
+ self = .Pull(solver) | ||
+ } | ||
+ | ||
+ mutating func decay() { | ||
+ guard case let .Pull(sim) = self else { return } | ||
+ | ||
+ let f = DecayFunction<Vector2>() | ||
+ let solver = DynamicSolver(function: f, value: sim.value, velocity: sim.velocity) | ||
+ self = .Decay(solver) | ||
+ } | ||
+ | ||
+ mutating func reset(to: CGPoint) { | ||
+ self = .Static(to.vector) | ||
+ } | ||
+} | ||
+ | ||
+ | ||
+struct GravitySimulation: Advanceable { | ||
+ | ||
+ let rows: Int = 10 | ||
+ let cols: Int = 10 | ||
+ | ||
+ init() { | ||
+ for r in 0..<rows { | ||
+ nodes.append([]) | ||
+ for _ in 0..<cols { | ||
+ let node = GravitySimulationNode.Static(Vector2(0.0, 0.0)) | ||
+ nodes[r].append(node) | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ private var nodes: [[GravitySimulationNode]] = [] | ||
+ | ||
+ mutating func reset(layoutBounds: CGRect) { | ||
+ target = nil | ||
+ | ||
+ let rowHeight = (layoutBounds.size.height / CGFloat(rows-1)) | ||
+ let colWidth = (layoutBounds.size.width / CGFloat(cols-1)) | ||
+ | ||
+ for r in 0..<rows { | ||
+ for c in 0..<cols { | ||
+ var p = layoutBounds.origin | ||
+ p.x += CGFloat(c) * colWidth | ||
+ p.y += CGFloat(r) * rowHeight | ||
+ nodes[r][c].reset(p) | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ var target: CGPoint? = nil { | ||
+ didSet { | ||
+ updateNodes() | ||
+ } | ||
+ } | ||
+ | ||
+ private mutating func updateNodes() { | ||
+ for r in 0..<rows { | ||
+ for c in 0..<cols { | ||
+ if let t = target { | ||
+ nodes[r][c].pullTo(t) | ||
+ } else { | ||
+ nodes[r][c].decay() | ||
+ } | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ func getPosition(row: Int, col: Int) -> CGPoint { | ||
+ return CGPoint(vector: nodes[row][col].value) | ||
+ } | ||
+ | ||
+ mutating func advance(elapsed: Double) { | ||
+ for r in 0..<rows { | ||
+ for c in 0..<cols { | ||
+ nodes[r][c].advance(elapsed) | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ var settled: Bool { | ||
+ for r in 0..<rows { | ||
+ for c in 0..<cols { | ||
+ if nodes[r][c].settled == false { | ||
+ return false | ||
+ } | ||
+ } | ||
+ } | ||
+ return true | ||
+ } | ||
+} |
144
AdvanceSample/GravityViewController.swift
@@ -0,0 +1,144 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import UIKit | ||
+import Advance | ||
+ | ||
+class GravityViewController: DemoViewController { | ||
+ | ||
+ var simulation = GravitySimulation() { | ||
+ didSet { | ||
+ if simulation.settled == false && subscription.paused == true { | ||
+ subscription.paused = false | ||
+ } | ||
+ view.setNeedsLayout() | ||
+ } | ||
+ } | ||
+ | ||
+ private lazy var subscription: LoopSubscription = { | ||
+ let s = Loop.shared.subscribe() | ||
+ | ||
+ s.advanced.observe({ [unowned self] (elapsed) -> Void in | ||
+ self.simulation.advance(elapsed) | ||
+ if self.simulation.settled { | ||
+ self.subscription.paused = true | ||
+ } | ||
+ }) | ||
+ | ||
+ return s | ||
+ }() | ||
+ | ||
+ let resetButton = UIButton() | ||
+ | ||
+ private var nodeLayers: [[CALayer]] = [] | ||
+ | ||
+ private var lastLayoutSize: CGSize = CGSize.zero | ||
+ | ||
+ private let recognizer = UILongPressGestureRecognizer() | ||
+ | ||
+ override func viewDidLoad() { | ||
+ super.viewDidLoad() | ||
+ | ||
+ title = "Gravity" | ||
+ note = "Long press to add gravity." | ||
+ | ||
+ recognizer.minimumPressDuration = 0.3 | ||
+ recognizer.addTarget(self, action: "press:") | ||
+ recognizer.enabled = false | ||
+ contentView.addGestureRecognizer(recognizer) | ||
+ | ||
+ resetButton.setTitle("Reset", forState: UIControlState.Normal) | ||
+ resetButton.setTitleColor(UIColor(red: 0.0, green: 196.0/255.0, blue: 1.0, alpha: 1.0), forState: .Normal) | ||
+ resetButton.layer.cornerRadius = 4.0 | ||
+ resetButton.tintColor = UIColor(red: 0.0, green: 196.0/255.0, blue: 1.0, alpha: 1.0) | ||
+ resetButton.layer.borderColor = UIColor(red: 0.0, green: 196.0/255.0, blue: 1.0, alpha: 1.0).CGColor | ||
+ resetButton.layer.borderWidth = 1.0 | ||
+ resetButton.addTarget(self, action: "reset", forControlEvents: .TouchUpInside) | ||
+ resetButton.alpha = 0.0 | ||
+ view.addSubview(resetButton) | ||
+ | ||
+ | ||
+ for r in 0..<simulation.rows { | ||
+ nodeLayers.append([]) | ||
+ for _ in 0..<simulation.cols { | ||
+ let layer = CALayer() | ||
+ layer.backgroundColor = UIColor(red: 0.0, green: 196.0/255.0, blue: 1.0, alpha: 1.0).CGColor | ||
+ layer.bounds = CGRect(x: 0.0, y: 0.0, width: 8.0, height: 8.0) | ||
+ layer.cornerRadius = 4.0 | ||
+ layer.actions = ["position": NSNull()] | ||
+ nodeLayers[r].append(layer) | ||
+ contentView.layer.addSublayer(layer) | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ override func viewDidLayoutSubviews() { | ||
+ super.viewDidLayoutSubviews() | ||
+ if view.bounds.size != lastLayoutSize { | ||
+ lastLayoutSize = view.bounds.size | ||
+ reset() | ||
+ } | ||
+ | ||
+ for r in 0..<simulation.rows { | ||
+ for c in 0..<simulation.cols { | ||
+ let position = simulation.getPosition(r, col: c) | ||
+ nodeLayers[r][c].position = position | ||
+ } | ||
+ } | ||
+ | ||
+ resetButton.bounds = CGRect(x: 0.0, y: 0.0, width: 120.0, height: 44.0) | ||
+ resetButton.center = CGPoint(x: contentView.bounds.midX, y: contentView.bounds.maxY - 64.0) | ||
+ } | ||
+ | ||
+ dynamic func press(recognizer: UILongPressGestureRecognizer) { | ||
+ switch recognizer.state { | ||
+ case .Began, .Changed: | ||
+ simulation.target = recognizer.locationInView(view) | ||
+ case .Ended: | ||
+ simulation.target = nil | ||
+ default: | ||
+ break | ||
+ } | ||
+ } | ||
+ | ||
+ dynamic func reset() { | ||
+ simulation.reset(view.bounds.insetBy(dx: 64.0, dy: 128.0)) | ||
+ } | ||
+ | ||
+ override func didEnterFullScreen() { | ||
+ super.didEnterFullScreen() | ||
+ recognizer.enabled = true | ||
+ resetButton.alpha = 1.0 | ||
+ } | ||
+ | ||
+ override func didLeaveFullScreen() { | ||
+ super.didLeaveFullScreen() | ||
+ recognizer.enabled = false | ||
+ resetButton.alpha = 0.0 | ||
+ } | ||
+} |
38
AdvanceSample/Info.plist
@@ -0,0 +1,38 @@ | ||
+<?xml version="1.0" encoding="UTF-8"?> | ||
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
+<plist version="1.0"> | ||
+<dict> | ||
+ <key>CFBundleDevelopmentRegion</key> | ||
+ <string>en</string> | ||
+ <key>CFBundleExecutable</key> | ||
+ <string>$(EXECUTABLE_NAME)</string> | ||
+ <key>CFBundleIdentifier</key> | ||
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | ||
+ <key>CFBundleInfoDictionaryVersion</key> | ||
+ <string>6.0</string> | ||
+ <key>CFBundleName</key> | ||
+ <string>$(PRODUCT_NAME)</string> | ||
+ <key>CFBundlePackageType</key> | ||
+ <string>APPL</string> | ||
+ <key>CFBundleShortVersionString</key> | ||
+ <string>1.0</string> | ||
+ <key>CFBundleSignature</key> | ||
+ <string>????</string> | ||
+ <key>CFBundleVersion</key> | ||
+ <string>1</string> | ||
+ <key>LSRequiresIPhoneOS</key> | ||
+ <true/> | ||
+ <key>UILaunchStoryboardName</key> | ||
+ <string>LaunchScreen</string> | ||
+ <key>UIRequiredDeviceCapabilities</key> | ||
+ <array> | ||
+ <string>armv7</string> | ||
+ </array> | ||
+ <key>UIStatusBarHidden</key> | ||
+ <true/> | ||
+ <key>UISupportedInterfaceOrientations</key> | ||
+ <array> | ||
+ <string>UIInterfaceOrientationPortrait</string> | ||
+ </array> | ||
+</dict> | ||
+</plist> |
68
AdvanceSample/SimpleTransform.swift
@@ -0,0 +1,68 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import Foundation | ||
+import CoreGraphics | ||
+import Advance | ||
+ | ||
+struct SimpleTransform { | ||
+ var scale: CGFloat = 1.0 | ||
+ var rotation: CGFloat = 0.0 | ||
+ | ||
+ init() {} | ||
+ | ||
+ init(scale: CGFloat, rotation: CGFloat) { | ||
+ self.scale = scale | ||
+ self.rotation = rotation | ||
+ } | ||
+ | ||
+ var affineTransform: CGAffineTransform { | ||
+ var t = CGAffineTransformIdentity | ||
+ t = CGAffineTransformRotate(t, rotation) | ||
+ t = CGAffineTransformScale(t, scale, scale) | ||
+ return t | ||
+ } | ||
+} | ||
+ | ||
+extension SimpleTransform: VectorConvertible { | ||
+ typealias Vector = Vector2 | ||
+ | ||
+ var vector: Vector { | ||
+ return Vector2(Scalar(scale), Scalar(rotation)) | ||
+ } | ||
+ | ||
+ init(vector: Vector) { | ||
+ scale = CGFloat(vector.x) | ||
+ rotation = CGFloat(vector.y) | ||
+ } | ||
+} | ||
+ | ||
+func ==(lhs: SimpleTransform, rhs: SimpleTransform) -> Bool { | ||
+ return lhs.scale == rhs.scale | ||
+ && lhs.rotation == rhs.rotation | ||
+} |
166
AdvanceSample/SpringConfigurationView.swift
@@ -0,0 +1,166 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import Foundation | ||
+import UIKit | ||
+ | ||
+protocol SpringConfigurationViewDelegate: class { | ||
+ func springConfigurationViewDidChange(view: SpringConfigurationView) | ||
+} | ||
+ | ||
+ | ||
+class SpringConfigurationView: UIView { | ||
+ | ||
+ weak var delegate: SpringConfigurationViewDelegate? = nil | ||
+ | ||
+ var tension: CGFloat { | ||
+ get { return CGFloat(tensionSlider.slider.value) } | ||
+ set { tensionSlider.slider.value = Float(newValue) } | ||
+ } | ||
+ | ||
+ var damping: CGFloat { | ||
+ get { return CGFloat(dampingSlider.slider.value) } | ||
+ set { dampingSlider.slider.value = Float(newValue) } | ||
+ } | ||
+ | ||
+ private let tensionSlider = LabeledSliderView() | ||
+ private let dampingSlider = LabeledSliderView() | ||
+ | ||
+ override init(frame: CGRect) { | ||
+ super.init(frame: frame) | ||
+ addSubview(tensionSlider) | ||
+ addSubview(dampingSlider) | ||
+ | ||
+ tensionSlider.slider.addTarget(self, action: "changed", forControlEvents: .ValueChanged) | ||
+ dampingSlider.slider.addTarget(self, action: "changed", forControlEvents: .ValueChanged) | ||
+ | ||
+ tensionSlider.slider.minimumValue = 1.0 | ||
+ tensionSlider.slider.maximumValue = 400.0 | ||
+ | ||
+ dampingSlider.slider.minimumValue = 0.1 | ||
+ dampingSlider.slider.maximumValue = 80.0 | ||
+ | ||
+ tensionSlider.slider.value = 120.0 | ||
+ dampingSlider.slider.value = 10.0 | ||
+ | ||
+ tensionSlider.text = "Tension" | ||
+ dampingSlider.text = "Damping" | ||
+ } | ||
+ | ||
+ required init?(coder aDecoder: NSCoder) { | ||
+ fatalError("init(coder:) has not been implemented") | ||
+ } | ||
+ | ||
+ override func sizeThatFits(size: CGSize) -> CGSize { | ||
+ var s = CGSize.zero | ||
+ s.width = size.width | ||
+ s.height += tensionSlider.sizeThatFits(size).height | ||
+ s.height += dampingSlider.sizeThatFits(size).height | ||
+ return s | ||
+ } | ||
+ | ||
+ override func layoutSubviews() { | ||
+ super.layoutSubviews() | ||
+ | ||
+ var tensionSize = tensionSlider.sizeThatFits(bounds.size) | ||
+ tensionSize.width = bounds.width | ||
+ tensionSlider.frame = CGRect(origin: CGPoint.zero, size: tensionSize) | ||
+ | ||
+ var dampingSize = dampingSlider.sizeThatFits(bounds.size) | ||
+ dampingSize.width = bounds.width | ||
+ dampingSlider.frame = CGRect(x: 0.0, y: tensionSlider.frame.maxY, width: bounds.width, height: dampingSize.height) | ||
+ } | ||
+ | ||
+ private dynamic func changed() { | ||
+ delegate?.springConfigurationViewDidChange(self) | ||
+ } | ||
+} | ||
+ | ||
+ | ||
+private class LabeledSliderView: UIView { | ||
+ | ||
+ var labelWidth: CGFloat = 90.0 { | ||
+ didSet { setNeedsLayout() } | ||
+ } | ||
+ | ||
+ var gutterWidth: CGFloat = 20.0 { | ||
+ didSet { setNeedsLayout() } | ||
+ } | ||
+ | ||
+ var sideMargin: CGFloat = 12.0 { | ||
+ didSet { setNeedsLayout() } | ||
+ } | ||
+ | ||
+ var text: String { | ||
+ get { return label.text ?? "" } | ||
+ set { label.text = newValue } | ||
+ } | ||
+ | ||
+ private let label: UILabel | ||
+ private let slider: UISlider | ||
+ | ||
+ override init(frame: CGRect) { | ||
+ label = UILabel() | ||
+ slider = UISlider() | ||
+ super.init(frame: frame) | ||
+ | ||
+ slider.minimumTrackTintColor = UIColor(red: 0.0, green: 196.0/255.0, blue: 1.0, alpha: 1.0) | ||
+ | ||
+ label.text = "Untitled" | ||
+ label.textColor = UIColor.darkGrayColor() | ||
+ | ||
+ addSubview(label) | ||
+ addSubview(slider) | ||
+ } | ||
+ | ||
+ required init?(coder aDecoder: NSCoder) { | ||
+ fatalError("init(coder:) has not been implemented") | ||
+ } | ||
+ | ||
+ private override func sizeThatFits(size: CGSize) -> CGSize { | ||
+ var s = size | ||
+ s.height = 44.0 | ||
+ return s | ||
+ } | ||
+ | ||
+ private override func layoutSubviews() { | ||
+ super.layoutSubviews() | ||
+ | ||
+ var labelSize = label.sizeThatFits(bounds.size) | ||
+ labelSize.width = min(labelSize.width, labelWidth) | ||
+ label.bounds = CGRect(origin: CGPoint.zero, size: labelSize) | ||
+ label.center = CGPoint(x: sideMargin + labelSize.width/2.0, y: bounds.midY) | ||
+ | ||
+ var sliderFrame = CGRect.zero | ||
+ sliderFrame.size.height = slider.sizeThatFits(bounds.size).height | ||
+ sliderFrame.size.width = bounds.width - (sideMargin * 2.0) - labelWidth - gutterWidth | ||
+ sliderFrame.origin.x = sideMargin + labelWidth + gutterWidth | ||
+ sliderFrame.origin.y = (bounds.height - sliderFrame.height) / 2.0 | ||
+ slider.frame = sliderFrame | ||
+ } | ||
+} |
61
AdvanceSample/SpringView.swift
@@ -0,0 +1,61 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import Foundation | ||
+import UIKit | ||
+import Advance | ||
+ | ||
+final class SpringView: UIView { | ||
+ | ||
+ let centerSpring = Spring(value: CGPoint.zero) | ||
+ | ||
+ override init(frame: CGRect) { | ||
+ super.init(frame: frame) | ||
+ backgroundColor = UIColor(red: 0.0, green: 196.0/255.0, blue: 1.0, alpha: 1.0) | ||
+ | ||
+ let t = arc4random_uniform(120 - 20) + 20 | ||
+ let d = arc4random_uniform(20 - 4) + 4 | ||
+ | ||
+ centerSpring.configuration.tension = Scalar(t) | ||
+ centerSpring.configuration.damping = Scalar(d) | ||
+ | ||
+ centerSpring.changed.observe { [unowned self] (c) -> Void in | ||
+ self.center = c | ||
+ } | ||
+ } | ||
+ | ||
+ required init?(coder aDecoder: NSCoder) { | ||
+ fatalError("init(coder:) has not been implemented") | ||
+ } | ||
+ | ||
+ override func layoutSubviews() { | ||
+ super.layoutSubviews() | ||
+ layer.cornerRadius = min(bounds.width, bounds.height) / 2.0 | ||
+ } | ||
+ | ||
+} |
113
AdvanceSample/SpringsViewController.swift
@@ -0,0 +1,113 @@ | ||
+/* | ||
+ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+this list of conditions and the following disclaimer in the documentation | ||
+and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
+ | ||
+*/ | ||
+ | ||
+import Foundation | ||
+import UIKit | ||
+import Advance | ||
+ | ||
+class SpringsViewController: DemoViewController { | ||
+ | ||
+ private let springView = SpringView() | ||
+ | ||
+ private let configView = SpringConfigurationView() | ||
+ | ||
+ private let tapRecognizer = UITapGestureRecognizer() | ||
+ | ||
+ required init() { | ||
+ super.init(nibName: nil, bundle: nil) | ||
+ title = "Spring" | ||
+ note = "Tap anywhere to move the dot using a spring." | ||
+ } | ||
+ | ||
+ required init?(coder aDecoder: NSCoder) { | ||
+ fatalError("init(coder:) has not been implemented") | ||
+ } | ||
+ | ||
+ override func viewDidLoad() { | ||
+ super.viewDidLoad() | ||
+ view.backgroundColor = UIColor.whiteColor() | ||
+ | ||
+ view.addGestureRecognizer(tapRecognizer) | ||
+ tapRecognizer.addTarget(self, action: "tap:") | ||
+ tapRecognizer.enabled = false | ||
+ | ||
+ springView.bounds = CGRect(x: 0.0, y: 0.0, width: 24.0, height: 24.0) | ||
+ | ||
+ configView.delegate = self | ||
+ configView.alpha = 0.0 | ||
+ contentView.addSubview(springView) | ||
+ contentView.addSubview(configView) | ||
+ | ||
+ updateSprings() | ||
+ } | ||
+ | ||
+ override func viewDidLayoutSubviews() { | ||
+ super.viewDidLayoutSubviews() | ||
+ | ||
+ var configFrame = CGRect.zero | ||
+ configFrame.size.width = view.bounds.width | ||
+ configFrame.size.height = configView.sizeThatFits(view.bounds.size).height | ||
+ configFrame.origin.y = view.bounds.maxY - configFrame.height - bottomLayoutGuide.length | ||
+ configView.frame = configFrame | ||
+ } | ||
+ | ||
+ dynamic func tap(recognizer: UITapGestureRecognizer) { | ||
+ let point = recognizer.locationInView(view) | ||
+ springView.centerSpring.target = point | ||
+ } | ||
+ | ||
+ override func viewWillAppear(animated: Bool) { | ||
+ super.viewWillAppear(animated) | ||
+ springView.centerSpring.reset(CGPoint(x: view.bounds.midX, y: view.bounds.midY)) | ||
+ } | ||
+ | ||
+ private func updateSprings() { | ||
+ springView.centerSpring.configuration.tension = Scalar(configView.tension) | ||
+ springView.centerSpring.configuration.damping = Scalar(configView.damping) | ||
+ } | ||
+ | ||
+ override func didEnterFullScreen() { | ||
+ super.didEnterFullScreen() | ||
+ configView.alpha = 1.0 | ||
+ tapRecognizer.enabled = true | ||
+ } | ||
+ | ||
+ override func didLeaveFullScreen() { | ||
+ super.didLeaveFullScreen() | ||
+ configView.alpha = 0.0 | ||
+ springView.centerSpring.target = CGPoint(x: contentView.bounds.midX, y: contentView.bounds.midY) | ||
+ tapRecognizer.enabled = false | ||
+ } | ||
+} | ||
+ | ||
+ | ||
+extension SpringsViewController: SpringConfigurationViewDelegate { | ||
+ func springConfigurationViewDidChange(view: SpringConfigurationView) { | ||
+ updateSprings() | ||
+ } | ||
+} |
34
AdvanceTests/BasicAnimationTests.swift
@@ -0,0 +1,34 @@ | ||
+import XCTest | ||
+@testable import Advance | ||
+ | ||
+ | ||
+class BasicAnimationTests: XCTestCase { | ||
+ | ||
+ let animation = BasicAnimation(from: Scalar(0.0), to: Scalar(10.0), duration: 2.0, timingFunction: LinearTimingFunction()) | ||
+ | ||
+ func testDuration() { | ||
+ var a = animation | ||
+ var elapsed: Double = 0 | ||
+ while true { | ||
+ elapsed += 0.1 | ||
+ a.advance(0.1) | ||
+ guard elapsed < a.duration else { break } | ||
+ XCTAssert(a.finished == false) | ||
+ } | ||
+ XCTAssert(a.finished == true) | ||
+ } | ||
+ | ||
+ func testInterpolation() { | ||
+ var a = animation | ||
+ var elapsed: Double = 0 | ||
+ while true { | ||
+ elapsed += 0.1 | ||
+ a.advance(0.1) | ||
+ guard elapsed < a.duration else { break } | ||
+ let current = a.from.interpolatedTo(a.to, alpha: elapsed/a.duration) | ||
+ XCTAssert(a.value == current) | ||
+ } | ||
+ XCTAssert(a.value == a.to) | ||
+ } | ||
+ | ||
+} |
20
AdvanceTests/CAMediaTimingFunctionTests.swift
@@ -0,0 +1,20 @@ | ||
+import XCTest | ||
+@testable import Advance | ||
+ | ||
+ | ||
+class CAMediaTimingFunctionTests : XCTestCase { | ||
+ func testConversion() { | ||
+ let p1x: Float = 0.42 | ||
+ let p1y: Float = 0.0 | ||
+ let p2x: Float = 0.58 | ||
+ let p2y: Float = 1.0 | ||
+ | ||
+ let ca = CAMediaTimingFunction(controlPoints: p1x, p1y, p2x, p2y) | ||
+ let ub = ca.unitBezier | ||
+ | ||
+ XCTAssertEqual(ub.p1x, Scalar(p1x)) | ||
+ XCTAssertEqual(ub.p1y, Scalar(p1y)) | ||
+ XCTAssertEqual(ub.p2x, Scalar(p2x)) | ||
+ XCTAssertEqual(ub.p2y, Scalar(p2y)) | ||
+ } | ||
+} |
48
AdvanceTests/EventTests.swift
@@ -0,0 +1,48 @@ | ||
+import XCTest | ||
+@testable import Advance | ||
+ | ||
+ | ||
+class EventTests : XCTestCase { | ||
+ func testEvent() { | ||
+ let payload = 123 | ||
+ let event = Event<Int>() | ||
+ | ||
+ let exp1 = expectationWithDescription("non-keyed") | ||
+ let exp2 = expectationWithDescription("keyed") | ||
+ | ||
+ event.observe { (p) -> Void in | ||
+ XCTAssertEqual(p, payload) | ||
+ exp1.fulfill() | ||
+ } | ||
+ | ||
+ event.observe({ (p) -> Void in | ||
+ XCTAssertEqual(p, payload) | ||
+ exp2.fulfill() | ||
+ }, key: "keyed") | ||
+ | ||
+ event.fire(payload) | ||
+ XCTAssertFalse(event.closed) | ||
+ | ||
+ waitForExpectationsWithTimeout(3.0) { (error) -> Void in | ||
+ guard error == nil else { XCTFail(); return } | ||
+ } | ||
+ } | ||
+ | ||
+ func testClosing() { | ||
+ let payload = 123 | ||
+ let event = Event<Int>() | ||
+ let exp = expectationWithDescription("exp") | ||
+ | ||
+ event.observe { (p) -> Void in | ||
+ XCTAssertEqual(p, payload) | ||
+ exp.fulfill() | ||
+ } | ||
+ | ||
+ event.close(payload) | ||
+ XCTAssertTrue(event.closed) | ||
+ | ||
+ waitForExpectationsWithTimeout(3.0) { (error) -> Void in | ||
+ guard error == nil else { XCTFail(); return } | ||
+ } | ||
+ } | ||
+} |
24
AdvanceTests/Info.plist
@@ -0,0 +1,24 @@ | ||
+<?xml version="1.0" encoding="UTF-8"?> | ||
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
+<plist version="1.0"> | ||
+<dict> | ||
+ <key>CFBundleDevelopmentRegion</key> | ||
+ <string>en</string> | ||
+ <key>CFBundleExecutable</key> | ||
+ <string>$(EXECUTABLE_NAME)</string> | ||
+ <key>CFBundleIdentifier</key> | ||
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | ||
+ <key>CFBundleInfoDictionaryVersion</key> | ||
+ <string>6.0</string> | ||
+ <key>CFBundleName</key> | ||
+ <string>$(PRODUCT_NAME)</string> | ||
+ <key>CFBundlePackageType</key> | ||
+ <string>BNDL</string> | ||
+ <key>CFBundleShortVersionString</key> | ||
+ <string>1.0</string> | ||
+ <key>CFBundleSignature</key> | ||
+ <string>????</string> | ||
+ <key>CFBundleVersion</key> | ||
+ <string>1</string> | ||
+</dict> | ||
+</plist> |
29
AdvanceTests/UnitBezierTests.swift
@@ -0,0 +1,29 @@ | ||
+import XCTest | ||
+@testable import Advance | ||
+ | ||
+ | ||
+class UnitBezierTests : XCTestCase { | ||
+ let eps: Scalar = 0.001 | ||
+ | ||
+ func testLinear() { | ||
+ let values: [Scalar] = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1] | ||
+ let b = UnitBezier(p1x: 0.0, p1y: 0.0, p2x: 1.0, p2y: 1.0) | ||
+ | ||
+ for i in 0..<values.count { | ||
+ let v = b.solve(Scalar(i) / Scalar(values.count - 1), epsilon: eps) | ||
+ let expected = values[i] | ||
+ XCTAssertEqualWithAccuracy(v, expected, accuracy: eps) | ||
+ } | ||
+ } | ||
+ | ||
+ func testCurve() { | ||
+ let values: [Scalar] = [0, 0.01965137076241203, 0.08141362197218191, 0.1871705774437696, 0.331832067207064, 0.5, 0.668167932792936, 0.8128294225562304, 0.9185863780278181, 0.980348629237588, 1] | ||
+ let b = UnitBezier(p1x: 0.42, p1y: 0.0, p2x: 0.58, p2y: 1.0) // ease in/out | ||
+ | ||
+ for i in 0..<values.count { | ||
+ let v = b.solve(Scalar(i) / Scalar(values.count - 1), epsilon: eps) | ||
+ let expected = values[i] | ||
+ XCTAssertEqualWithAccuracy(v, expected, accuracy: eps) | ||
+ } | ||
+ } | ||
+} |
47
AdvanceTests/VectorConvenienceTests.swift
@@ -0,0 +1,47 @@ | ||
+import XCTest | ||
+@testable import Advance | ||
+ | ||
+ | ||
+class VectorConvenienceTests : XCTestCase { | ||
+ func testCGFloatConversion() { | ||
+ let f = CGFloat(12.0) | ||
+ let v = f.vector | ||
+ let f2 = CGFloat(vector: v) | ||
+ XCTAssert(f == f2) | ||
+ } | ||
+ | ||
+ func testDoubleConversion() { | ||
+ let d = Double(12.0) | ||
+ let v = d.vector | ||
+ let d2 = Double(vector: v) | ||
+ XCTAssert(d == d2) | ||
+ } | ||
+ | ||
+ func testCGPointConversion() { | ||
+ let p = CGPoint(x: 12.0, y: 60.0) | ||
+ let v = p.vector | ||
+ let p2 = CGPoint(vector: v) | ||
+ XCTAssert(p == p2) | ||
+ } | ||
+ | ||
+ func testCGSizeConversion() { | ||
+ let s = CGSize(width: 12.0, height: 60.0) | ||
+ let v = s.vector | ||
+ let s2 = CGSize(vector: v) | ||
+ XCTAssert(s == s2) | ||
+ } | ||
+ | ||
+ func testCGVectorConversion() { | ||
+ let cg = CGVector(dx: 12.0, dy: 60.0) | ||
+ let v = cg.vector | ||
+ let cg2 = CGVector(vector: v) | ||
+ XCTAssert(cg == cg2) | ||
+ } | ||
+ | ||
+ func testCGRectConversion() { | ||
+ let r = CGRect(x: 12.0, y: 60.0, width: 100.0, height: 3.0) | ||
+ let v = r.vector | ||
+ let r2 = CGRect(vector: v) | ||
+ XCTAssert(r == r2) | ||
+ } | ||
+} |
113
AdvanceTests/VectorTypeTests.swift
@@ -0,0 +1,113 @@ | ||
+import XCTest | ||
+@testable import Advance | ||
+ | ||
+ | ||
+class VectorTypeTests: XCTestCase { | ||
+ func testVector1() { | ||
+ XCTAssert(Vector1.length == 1) | ||
+ VectorTester<Vector1>.runTests() | ||
+ } | ||
+ | ||
+ func testVector2() { | ||
+ XCTAssert(Vector2.length == 2) | ||
+ VectorTester<Vector2>.runTests() | ||
+ } | ||
+ | ||
+ func testVector3() { | ||
+ XCTAssert(Vector3.length == 3) | ||
+ VectorTester<Vector3>.runTests() | ||
+ } | ||
+ | ||
+ func testVector4() { | ||
+ XCTAssert(Vector4.length == 4) | ||
+ VectorTester<Vector4>.runTests() | ||
+ } | ||
+} | ||
+ | ||
+ | ||
+struct VectorTester<T: VectorType> { | ||
+ static func runTests() { | ||
+ testScalarInit() | ||
+ testZero() | ||
+ testSubscripting() | ||
+ testEquatable() | ||
+ testClamp() | ||
+ testInterpolatable() | ||
+ testMath() | ||
+ } | ||
+ | ||
+ static func testScalarInit() { | ||
+ let v = T(scalar: 5.2) | ||
+ for i in 0..<T.length { | ||
+ XCTAssert(v[i] == 5.2) | ||
+ } | ||
+ } | ||
+ | ||
+ static func testZero() { | ||
+ XCTAssert(T(scalar: 0.0) == T.zero) | ||
+ } | ||
+ | ||
+ static func testSubscripting() { | ||
+ var v = T.zero | ||
+ for i in 0..<T.length { | ||
+ v[i] = Scalar(i) | ||
+ } | ||
+ for i in 0..<T.length { | ||
+ XCTAssert(v[i] == Scalar(i)) | ||
+ } | ||
+ } | ||
+ | ||
+ static func testEquatable() { | ||
+ let v1 = T(scalar: 123.0) | ||
+ let v2 = T(scalar: 123.0) | ||
+ let v3 = T(scalar: 10.0) | ||
+ XCTAssert(v1 == v2) | ||
+ XCTAssert(v1 != v3) | ||
+ } | ||
+ | ||
+ static func testClamp() { | ||
+ let min = T(scalar: -10.0) | ||
+ let max = T(scalar: 20.0) | ||
+ | ||
+ let v1 = T(scalar: -20.0) | ||
+ let v2 = T(scalar: 30.0) | ||
+ let v3 = T(scalar: 15.0) | ||
+ | ||
+ XCTAssert(v1.clamped(min: min, max: max) == min) | ||
+ XCTAssert(v2.clamped(min: min, max: max) == max) | ||
+ XCTAssert(v3.clamped(min: min, max: max) == v3) | ||
+ } | ||
+ | ||
+ static func testInterpolatable() { | ||
+ let v1 = T(scalar: 0.0) | ||
+ let v2 = T(scalar: 10.0) | ||
+ XCTAssert(v1.interpolatedTo(v2, alpha: 0.0) == v1) | ||
+ XCTAssert(v1.interpolatedTo(v2, alpha: 0.55) == T(scalar: 5.5)) | ||
+ XCTAssert(v1.interpolatedTo(v2, alpha: 1.0) == v2) | ||
+ } | ||
+ | ||
+ static func testMath() { | ||
+ let s1 = Scalar(9.0) | ||
+ let s2 = Scalar(17.3) | ||
+ let v1 = T(scalar: s1) | ||
+ let v2 = T(scalar: s2) | ||
+ | ||
+ XCTAssert(v1 + v2 == T(scalar: s1 + s2)) | ||
+ XCTAssert(v1 - v2 == T(scalar: s1 - s2)) | ||
+ XCTAssert(v1 * v2 == T(scalar: s1 * s2)) | ||
+ XCTAssert(v1 / v2 == T(scalar: s1 / s2)) | ||
+ | ||
+ XCTAssert(Scalar(2.0) * v2 == T(scalar: 2.0 * s2)) | ||
+ | ||
+ func testInPlaceMath(function: (inout T, T) -> Void, expectedValue: T) { | ||
+ var m = v1 | ||
+ function(&m, v2) | ||
+ XCTAssert(m == expectedValue) | ||
+ } | ||
+ | ||
+ testInPlaceMath(+=, expectedValue: v1 + v2) | ||
+ testInPlaceMath(-=, expectedValue: v1 - v2) | ||
+ testInPlaceMath(*=, expectedValue: v1 * v2) | ||
+ testInPlaceMath(/=, expectedValue: v1 / v2) | ||
+ } | ||
+} |
BIN
Assets/logo.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN
Assets/logo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN
Assets/nav.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23
LICENSE
@@ -0,0 +1,23 @@ | ||
+Copyright (c) 2016, Storehouse Media Inc. | ||
+All rights reserved. | ||
+ | ||
+Redistribution and use in source and binary forms, with or without | ||
+modification, are permitted provided that the following conditions are met: | ||
+ | ||
+* Redistributions of source code must retain the above copyright notice, this | ||
+ list of conditions and the following disclaimer. | ||
+ | ||
+* Redistributions in binary form must reproduce the above copyright notice, | ||
+ this list of conditions and the following disclaimer in the documentation | ||
+ and/or other materials provided with the distribution. | ||
+ | ||
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
3
Package.swift
@@ -0,0 +1,3 @@ | ||
+import PackageDescription | ||
+ | ||
+let package = Package(name: “Advance”) |
23
Playground.playground/Pages/Animatable.xcplaygroundpage/Contents.swift
@@ -0,0 +1,23 @@ | ||
+//: [Previous](@previous) | ||
+ | ||
+import UIKit | ||
+import XCPlayground | ||
+import Advance | ||
+ | ||
+XCPlaygroundPage.currentPage.needsIndefiniteExecution = true | ||
+ | ||
+let a = Animatable(value: 0.0) | ||
+ | ||
+a.changed.observe { (val) in | ||
+ NSLog("value: \(val)") | ||
+} | ||
+ | ||
+a.value = 4.0 | ||
+ | ||
+a.value = 20.0 | ||
+ | ||
+a.animateTo(10.0) { (finished) in | ||
+ var cfg = SpringConfiguration() | ||
+ cfg.damping = 2.0 | ||
+ a.springTo(0.0, initialVelocity: 0.0, configuration: cfg, completion: nil) | ||
+} |
19
Playground.playground/Pages/BasicAnimation.xcplaygroundpage/Contents.swift
@@ -0,0 +1,19 @@ | ||
+//: [Previous](@previous) | ||
+ | ||
+import UIKit | ||
+import XCPlayground | ||
+import Advance | ||
+ | ||
+ | ||
+let numberOfTicks = 100.0 | ||
+ | ||
+let timingFunction = UnitBezier(preset: .EaseInEaseOut) | ||
+var a = BasicAnimation(from: 0.0, to: 10.0, duration: 1.0, timingFunction: timingFunction) | ||
+ | ||
+ | ||
+XCPlaygroundPage.currentPage.captureValue(a.value, withIdentifier: "Basic Animation") | ||
+while a.finished == false { | ||
+ a.advance(1.0/numberOfTicks) | ||
+ XCPlaygroundPage.currentPage.captureValue(a.value, withIdentifier: "Basic Animation") | ||
+ NSLog("Basic animation: \(a.value)") | ||
+} |
33
Playground.playground/Pages/Dynamics.xcplaygroundpage/Contents.swift
@@ -0,0 +1,33 @@ | ||
+//: [Previous](@previous) | ||
+ | ||
+import UIKit | ||
+import XCPlayground | ||
+import Advance | ||
+ | ||
+ | ||
+var f = SpringFunction(target: 0.0) | ||
+var sim = DynamicSimulation(function: f, value: 0.0) | ||
+ | ||
+sim.velocity = 800.0 | ||
+ | ||
+while sim.settled == false { | ||
+ sim.advance(0.016) | ||
+ XCPlaygroundPage.currentPage.captureValue(sim.value, withIdentifier: "Sprig sim") | ||
+} | ||
+ | ||
+ | ||
+ | ||
+sim.function.target = 20.0 | ||
+ | ||
+while sim.settled == false { | ||
+ sim.advance(0.016) | ||
+ XCPlaygroundPage.currentPage.captureValue(sim.value, withIdentifier: "Sprig sim") | ||
+} | ||
+ | ||
+sim.function.configuration.damping = 1.0 | ||
+sim.function.target = 0.0 | ||
+ | ||
+while sim.settled == false { | ||
+ sim.advance(0.016) | ||
+ XCPlaygroundPage.currentPage.captureValue(sim.value, withIdentifier: "Sprig sim") | ||
+} |
16
Playground.playground/Pages/Simple.xcplaygroundpage/Contents.swift
@@ -0,0 +1,16 @@ | ||
+//: Playground - noun: a place where people can play | ||
+ | ||
+import UIKit | ||
+import XCPlayground | ||
+import Advance | ||
+ | ||
+ | ||
+XCPlaygroundPage.currentPage.needsIndefiniteExecution = true | ||
+ | ||
+ | ||
+let initial = CGSize(width: 32.0, height: 32.0) | ||
+let final = UIScreen.mainScreen().bounds.size | ||
+ | ||
+0.0.animateTo(3.0, duration: 2.0, timingFunction: LinearTimingFunction()) { (value) in | ||
+ NSLog("val: \(value)") | ||
+} |
24
Playground.playground/Pages/Spring.xcplaygroundpage/Contents.swift
@@ -0,0 +1,24 @@ | ||
+//: [Previous](@previous) | ||
+ | ||
+import UIKit | ||
+import XCPlayground | ||
+import Advance | ||
+ | ||
+XCPlaygroundPage.currentPage.needsIndefiniteExecution = true | ||
+ | ||
+ | ||
+let s = Spring(value: 0.0) | ||
+s.configuration.tension = 200.0 | ||
+s.configuration.damping = 20.0 | ||
+s.configuration.threshold = 1.0 | ||
+ | ||
+s.changed.observe { (val) in | ||
+XCPlaygroundPage.currentPage.captureValue(val, withIdentifier: "Spring value") | ||
+} | ||
+ | ||
+s.target = 200.0 | ||
+ | ||
+var t = dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC)*2) | ||
+dispatch_after(t, dispatch_get_main_queue()) { | ||
+ s.target = 0.0 | ||
+} |
10
Playground.playground/contents.xcplayground
@@ -0,0 +1,10 @@ | ||
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||
+<playground version='6.0' target-platform='ios'> | ||
+ <pages> | ||
+ <page name='Animatable'/> | ||
+ <page name='Dynamics'/> | ||
+ <page name='Simple'/> | ||
+ <page name='Spring'/> | ||
+ <page name='BasicAnimation'/> | ||
+ </pages> | ||
+</playground> |
@@ -0,0 +1,235 @@ | ||
+<br/><img src="https://github.com/storehouse/Advance/raw/master/Assets/logo.png" width="302"> | ||
+ | ||
+A powerful animation framework for iOS. | ||
+ | ||
+--- | ||
+ | ||
+##### What is it? | ||
+Advance is a pure Swift framework that enables advanced animations and physics-based interactions. | ||
+ | ||
+Originally developed to power the animations throughout the Storehouse app for iOS, Advance has evolved into a composable set of tools with a [simple API](http://storehouse.github.io/Advance/docs). It uses a `CADisplayLink` instance to drive animations and simulations that advance on each frame. | ||
+ | ||
+##### Should I use it? | ||
+Advance shines when: | ||
+ | ||
+* You are building gesture-based interactions that use physics to reflect the behavior of the real world. | ||
+* You need to animate custom types with per-frame callbacks. | ||
+* ???. Advance is extensible, making it easy to build custom animation and simulation types. | ||
+ | ||
+You can accomplish amazing things with Advance. But you should be sensitive to the performance impact of performing per-frame animations on the main thread. Check out [this article](https://www.objc.io/issues/12-animations/interactive-animations/) on objc.io for a good overview. | ||
+ | ||
+*If you simply want to animate the basic properties of a view, it's probably best to stick with UIView animations / Core Animation. It is able to run your animations on a high priority background thread and is not as sensitive to blocking the main thread.* | ||
+ | ||
+##### Requirements | ||
+* iOS 8+ | ||
+* Swift 2+ | ||
+ | ||
+ | ||
+### Examples | ||
+ | ||
+##### Check out the sample app. | ||
+Located in the project: | ||
+ | ||
+![Sample app](https://github.com/storehouse/Advance/raw/master/Assets/nav.gif) | ||
+![Sample app](https://github.com/storehouse/Advance/raw/master/Assets/logo.gif) | ||
+ | ||
+**** | ||
+ | ||
+##### Animate between two numbers. | ||
+ | ||
+```swift | ||
+import Advance | ||
+ | ||
+0.0.animateTo(3.0, duration: 2.0, timingFunction: LinearTimingFunction()) { (value) in | ||
+ print("value: \(value)") | ||
+ // Do something with value | ||
+} | ||
+``` | ||
+This will create and run an animation from `0.0` to `3.0`, calling the closure at each step of the animation. | ||
+ | ||
+**** | ||
+ | ||
+##### Use `Animatable` to contain values that can be animated with any animation type. | ||
+ | ||
+```swift | ||
+import Advance | ||
+ | ||
+class MyClass { | ||
+ | ||
+ let center: Animatable<CGPoint> | ||
+ | ||
+ init() { | ||
+ ... | ||
+ center.changed.observe { (value) in | ||
+ // Do something every time the center value changes | ||
+ } | ||
+ } | ||
+} | ||
+ | ||
+let foo = MyClass() | ||
+ | ||
+// foo.center.animateTo(...) | ||
+// foo.center.springTo(...) | ||
+// foo.center.decay(...) | ||
+// foo.center.animate(<custom animation type>) | ||
+``` | ||
+ | ||
+**** | ||
+ | ||
+##### Use `Spring` to contain values that will only be driven by spring physics. | ||
+ | ||
+Instances of `Spring` should be used in situations where spring physics are the only animation type required, or when convenient access to the properties of a running spring simulation is needed. | ||
+ | ||
+The focused API of this class makes it more convenient in such cases than using an `Animatable` instance, where a new spring animation would have to be added each time the spring needed to be modified. | ||
+ | ||
+The `Spring` class also serves as an example of how some of the internal components of the framework can be composed to build new components. | ||
+ | ||
+```swift | ||
+import Advance | ||
+ | ||
+let s = Spring(value: CGPoint.zero) | ||
+ | ||
+s.changed.observe { (value) in | ||
+ // do something with the value when it changes | ||
+} | ||
+ | ||
+s.target = CGPoint(x: 100.0, y: 200.0) | ||
+// The spring will begin moving to the new value | ||
+ | ||
+// Some time in the future... | ||
+ | ||
+s.target = CGPoint.zero | ||
+// The value will come back to zero. | ||
+``` | ||
+ | ||
+### Animatable types | ||
+ | ||
+Any type conforming to `VectorConvertible` can be animated. | ||
+ | ||
+```swift | ||
+public protocol VectorConvertible: Equatable, Interpolatable { | ||
+ typealias Vector: VectorType | ||
+ init(vector: Vector) | ||
+ var vector: Vector { get } | ||
+} | ||
+``` | ||
+ | ||
+`VectorConvertible` is a simple protocol that allows instances of the the conforming type to: | ||
+ | ||
+* Define an associated type called `Vector`, which must be a concrete implementation of `VectorType`. Implementations of 1, 2, 3, and 4 component vectors are provided. | ||
+* Be initialized with a `Vector` instance. | ||
+* Provide a `Vector` representation of itself. | ||
+ | ||
+The associated type `Vector` is a concrete implementation of `VectorType`. For example, `CGSize` defines the associated type as `Vector2` (a two component vector, as size has a width and a height component): | ||
+ | ||
+```swift | ||
+extension CGSize: VectorConvertible { | ||
+ | ||
+ /// The underlying vector type. | ||
+ public typealias Vector = Vector2 | ||
+ | ||
+ /// Creates a new instance from a vector. | ||
+ public init(vector: Vector) { | ||
+ self.init(width: CGFloat(vector.x), height: CGFloat(vector.y)) | ||
+ } | ||
+ | ||
+ /// Returns the vector representation. | ||
+ public var vector: Vector { | ||
+ return Vector(Scalar(width), Scalar(height)) | ||
+ } | ||
+} | ||
+``` | ||
+ | ||
+Extensions are provided to add `VectorConvertible` conformance to many of the types used in the UI layer. Animating custom types is as easy as adding an extension similar to the example above. | ||
+ | ||
+### Design | ||
+ | ||
+Advance embraces composition and value types throughout. | ||
+ | ||
+There are a few foundational types at the core of the Advance framework: | ||
+ | ||
+ | ||
+##### `Advanceable` protocol | ||
+Conforming types can be advanced on each frame by a given duration. | ||
+ | ||
+```swift | ||
+public protocol Advanceable { | ||
+ mutating func advance(elapsed: Double) | ||
+} | ||
+``` | ||
+ | ||
+`Advanceable` is the mechanism by which all animations are performed. A typical device updates the screen 60 times per second – this means each frame has a duration of 0.016 seconds (1/60). | ||
+ | ||
+##### `AnimationType` protocol | ||
+Inherits from `Advanceable`. | ||
+ | ||
+```swift | ||
+public protocol AnimationType: Advanceable { | ||
+ var finished: Bool { get } | ||
+} | ||
+``` | ||
+ | ||
+Animations have a finite length (defined by each implimentation). When the animation is complete, it returns `true` for the `finished` property. | ||
+ | ||
+##### `ValueAnimationType` protocol | ||
+Inherits from `AnimationType`. | ||
+ | ||
+```swift | ||
+public protocol ValueAnimationType: AnimationType { | ||
+ typealias Value: VectorConvertible | ||
+ var value: Value { get } | ||
+ var velocity: Value { get } | ||
+} | ||
+``` | ||
+ | ||
+Types conforming to `ValueAnimationType` can be used to animate values of any type conforming to `VectorConvertible`, as they support querying for the current value and velocity on each frame. | ||
+ | ||
+The framework includes the following animations: | ||
+ | ||
+* `BasicAnimation` uses a specified timing function and duration. | ||
+* `SpringAnimation` uses a `DynamicSolver`/`SpringFunction` internally to animate to the final value. | ||
+* `DecayAnimation` uses a `DynamicSolver`/`DecayFunction` internally to animate to the final value. | ||
+ | ||
+ | ||
+ | ||
+**** | ||
+ | ||
+[Please see the documentation](http://storehouse.github.io/Advance/docs) and check out the sample app (AdvanceSample) in the project for more details. | ||
+ | ||
+### Notes | ||
+ | ||
+##### Performance | ||
+ | ||
+Advance uses Swift generics extensively. This has many benefits, including the ability to natively animate nearly any value type without casting (or using `NSValue` wrappers, etc). | ||
+ | ||
+The Swift compiler can heavily optimize generics through the process of *specialization* – in which it generates specific implementations of generic functions based on the way your code calls them. Unfortunately, specialization cannot occur between modules. This means that the compiler will be unable to fully optimize when Advance is built as a framework. | ||
+ | ||
+**This probably does not matter in your application**. Even with the small amount of overhead required to call generic functions, modern CPUs are fast; running a few animations will have little impact. If your application has many simultaneous animations, however, and you are looking for ways to optimize, you may see an improvement by bringing in all of the .swift files and compiling Advance as part of your project. | ||
+ | ||
+##### Thread safety | ||
+ | ||
+Advance classes are **not** thread safe, and expect to be used from the main thread only. | ||
+ | ||
+With that said, the majority of the framework is implemented as a set of protocols and value types. Because value semantics are implicitly safe to pass between threads, you can safely accomplish many things asynchronously. You just need to be sure that classes such as `Animator`, `Animatable`, and `Spring` are accessed from the main thread only. | ||
+ | ||
+### Status | ||
+- **Platforms:** Advance is currently iOS only, but I would like to add support for additional platforms. It likely works out of the box on tvOS, but it has not been tested. OS X support should also be fairly trivial, though it will take some additions to `Loop` (`CADisplayLink` is unavailable). | ||
+ | ||
+- **Tests:** More tests are needed. Test coverage is provided for some of the core types, but it is not yet comprehensive enough. | ||
+ | ||
+- **Documentation:** Nearly every type is briefly commented, but expanding the documentation to include discussion and examples where appropriate is a goal of the project. | ||
+ | ||
+- **API Stability** I currently consider the API to be in draft form (thus the < 1.0 version of the project). | ||
+ | ||
+*Suggestions / pull requests / etc are welcome!* | ||
+ | ||
+### Usage | ||
+To use Advance as a framework, simply add the `Advance.xcodeproj` to your project and add `Advance.framework` as an "Embedded Binary" to your application target (under General in target settings). From there, add `import Advance` to your code and you're good to go. | ||
+ | ||
+Advance also supports Carthage (add `github "storehouse/Advance"` to your `Cartfile`) and Swift Package Manager. | ||
+ | ||
+### Documentation | ||
+API documentation is [available here](http://storehouse.github.io/Advance/docs). | ||
+ | ||
+### License | ||
+This project is released under the [BSD 2-clause license](https://github.com/storehouse/Advance/blob/master/LICENSE). |
0 comments on commit
b0070e0