Commit fc98f8b5 by Daniel Dahan

development: added Motion submodule

parent 0c31a0a6
[submodule "Frameworks/External/Motion"]
path = Frameworks/External/Motion
url = https://github.com/CosmicMind/Motion.git
Subproject commit f371f1db0f6f0044d60e3c136eb7831190bb39d7
......@@ -8,7 +8,6 @@
/* Begin PBXBuildFile section */
9606CFAC1E957AC3006B4E74 /* TabsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9606CFAB1E957AC3006B4E74 /* TabsController.swift */; };
960E35061EB0FC5700EB124A /* Material+MotionDynamics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 960E35051EB0FC5700EB124A /* Material+MotionDynamics.swift */; };
961409B01E43D15C00E7BA99 /* CollectionViewCard.swift in Headers */ = {isa = PBXBuildFile; fileRef = 961730591E145DE900A9A297 /* CollectionViewCard.swift */; settings = {ATTRIBUTES = (Public, ); }; };
961409B11E43D15C00E7BA99 /* FABMenu.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96A183621E0C6CE200083C30 /* FABMenu.swift */; settings = {ATTRIBUTES = (Public, ); }; };
961409B21E43D15C00E7BA99 /* FABMenuController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96A183641E0C6DD400083C30 /* FABMenuController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
......@@ -44,7 +43,6 @@
9639526D1EC3882F004BA9DE /* EventsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9639526B1EC3882F004BA9DE /* EventsController.swift */; };
964335B71EC9432400FA9954 /* Events.swift in Headers */ = {isa = PBXBuildFile; fileRef = 9639526A1EC3882F004BA9DE /* Events.swift */; settings = {ATTRIBUTES = (Public, ); }; };
964335B81EC9432400FA9954 /* EventsController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 9639526B1EC3882F004BA9DE /* EventsController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
964335B91EC9432400FA9954 /* Material+MotionDynamics.swift in Headers */ = {isa = PBXBuildFile; fileRef = 960E35051EB0FC5700EB124A /* Material+MotionDynamics.swift */; settings = {ATTRIBUTES = (Public, ); }; };
964335BA1EC9432400FA9954 /* TabsController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 9606CFAB1E957AC3006B4E74 /* TabsController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
965E80CC1DD4C50600D61E4B /* Bar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7981CB40DC500C806FE /* Bar.swift */; };
965E80CD1DD4C50600D61E4B /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7701CB40DC500C806FE /* Button.swift */; };
......@@ -176,10 +174,6 @@
96BCB8561CB4115200C806FE /* Switch.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB7881CB40DC500C806FE /* Switch.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BCB8571CB4115200C806FE /* View.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB78C1CB40DC500C806FE /* View.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BFC1541E5E486F0075DE1F /* SpringAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 965532281E47E388005C2792 /* SpringAnimation.swift */; };
96BFC1681E61D9FD0075DE1F /* Material+Motion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BFC1671E61D9FD0075DE1F /* Material+Motion.swift */; };
96BFC16A1E61DAA10075DE1F /* Material+MotionAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BFC1691E61DAA10075DE1F /* Material+MotionAnimation.swift */; };
96BFC16D1E63C10A0075DE1F /* Material+Motion.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BFC1671E61D9FD0075DE1F /* Material+Motion.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BFC16E1E63C10A0075DE1F /* Material+MotionAnimation.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BFC1691E61DAA10075DE1F /* Material+MotionAnimation.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BFC16F1E63C10A0075DE1F /* SpringAnimation.swift in Headers */ = {isa = PBXBuildFile; fileRef = 965532281E47E388005C2792 /* SpringAnimation.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96D88C321C1328D800B91418 /* Material.h in Headers */ = {isa = PBXBuildFile; fileRef = 96D88C091C1328D800B91418 /* Material.h */; settings = {ATTRIBUTES = (Public, ); }; };
96E3C3951D3A1CC20086A024 /* IconButton.swift in Headers */ = {isa = PBXBuildFile; fileRef = 9658F2161CD6FA4700B902C1 /* IconButton.swift */; settings = {ATTRIBUTES = (Public, ); }; };
......@@ -190,9 +184,25 @@
96E3C39C1D3A1CC20086A024 /* Offset.swift in Headers */ = {isa = PBXBuildFile; fileRef = 968C99461D377849000074FF /* Offset.swift */; settings = {ATTRIBUTES = (Public, ); }; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
966A7F141EEC5B3500A2DAAC /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 966A7F101EEC5B3500A2DAAC /* Motion.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 96C98DD11E424AB000B22906;
remoteInfo = "Motion iOS";
};
966A7F161EEC5B6300A2DAAC /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 966A7F101EEC5B3500A2DAAC /* Motion.xcodeproj */;
proxyType = 1;
remoteGlobalIDString = 96C98DD01E424AB000B22906;
remoteInfo = "Motion iOS";
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
9606CFAB1E957AC3006B4E74 /* TabsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabsController.swift; sourceTree = "<group>"; };
960E35051EB0FC5700EB124A /* Material+MotionDynamics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+MotionDynamics.swift"; sourceTree = "<group>"; };
961276621DCD8B1800A7D920 /* CharacterAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharacterAttribute.swift; sourceTree = "<group>"; };
961730591E145DE900A9A297 /* CollectionViewCard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCard.swift; sourceTree = "<group>"; };
961E6BDE1DDA2A95004E6C93 /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
......@@ -215,6 +225,7 @@
963FBEFC1D669510008F8512 /* Snackbar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Snackbar.swift; sourceTree = "<group>"; };
965532281E47E388005C2792 /* SpringAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpringAnimation.swift; sourceTree = "<group>"; };
9658F2161CD6FA4700B902C1 /* IconButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IconButton.swift; sourceTree = "<group>"; };
966A7F101EEC5B3500A2DAAC /* Motion.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Motion.xcodeproj; path = Frameworks/External/Motion/Motion.xcodeproj; sourceTree = "<group>"; };
966ECF291CF4C20100BB0BDF /* CollectionReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionReusableView.swift; sourceTree = "<group>"; };
96717B0D1DBE6AF600DA84DB /* Capture.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Capture.swift; sourceTree = "<group>"; };
96717B0E1DBE6AF600DA84DB /* CaptureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaptureController.swift; sourceTree = "<group>"; };
......@@ -280,8 +291,6 @@
96BCB7F01CB40DE900C806FE /* Roboto-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Medium.ttf"; sourceTree = "<group>"; };
96BCB7F11CB40DE900C806FE /* Roboto-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Regular.ttf"; sourceTree = "<group>"; };
96BCB7F21CB40DE900C806FE /* Roboto-Thin.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Thin.ttf"; sourceTree = "<group>"; };
96BFC1671E61D9FD0075DE1F /* Material+Motion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+Motion.swift"; sourceTree = "<group>"; };
96BFC1691E61DAA10075DE1F /* Material+MotionAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+MotionAnimation.swift"; sourceTree = "<group>"; };
96C1C8801D42C62800E6608F /* Material+Array.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+Array.swift"; sourceTree = "<group>"; };
96D88BFC1C1328D800B91418 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
96D88BFD1C1328D800B91418 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
......@@ -385,6 +394,7 @@
9638322C1B88DFD80015F710 = {
isa = PBXGroup;
children = (
966A7F0D1EEC5AF400A2DAAC /* Frameworks */,
96D88BF41C1328D800B91418 /* Sources */,
963832371B88DFD80015F710 /* Products */,
);
......@@ -452,6 +462,38 @@
name = SearchBar;
sourceTree = "<group>";
};
966A7F0D1EEC5AF400A2DAAC /* Frameworks */ = {
isa = PBXGroup;
children = (
966A7F0E1EEC5AFD00A2DAAC /* External */,
);
name = Frameworks;
sourceTree = "<group>";
};
966A7F0E1EEC5AFD00A2DAAC /* External */ = {
isa = PBXGroup;
children = (
966A7F0F1EEC5B0F00A2DAAC /* Motion */,
);
name = External;
sourceTree = "<group>";
};
966A7F0F1EEC5B0F00A2DAAC /* Motion */ = {
isa = PBXGroup;
children = (
966A7F101EEC5B3500A2DAAC /* Motion.xcodeproj */,
);
name = Motion;
sourceTree = "<group>";
};
966A7F111EEC5B3500A2DAAC /* Products */ = {
isa = PBXGroup;
children = (
966A7F151EEC5B3500A2DAAC /* Motion.framework */,
);
name = Products;
sourceTree = "<group>";
};
966ECF2B1CF4C21B00BB0BDF /* Table */ = {
isa = PBXGroup;
children = (
......@@ -521,7 +563,6 @@
96BCB8081CB4105E00C806FE /* Icon */,
96BCB80D1CB410FD00C806FE /* Layer */,
96BCB8041CB40F6C00C806FE /* Layout */,
96BCB8091CB4107700C806FE /* Motion */,
96BCB8011CB40F1700C806FE /* Navigation */,
961E6BEF1DDA4B04004E6C93 /* NavigationDrawer */,
96717B151DBE6B1800DA84DB /* Photos */,
......@@ -656,16 +697,6 @@
name = Icon;
sourceTree = "<group>";
};
96BCB8091CB4107700C806FE /* Motion */ = {
isa = PBXGroup;
children = (
96BFC1671E61D9FD0075DE1F /* Material+Motion.swift */,
96BFC1691E61DAA10075DE1F /* Material+MotionAnimation.swift */,
960E35051EB0FC5700EB124A /* Material+MotionDynamics.swift */,
);
name = Motion;
sourceTree = "<group>";
};
96BCB80A1CB410A100C806FE /* Extension */ = {
isa = PBXGroup;
children = (
......@@ -826,12 +857,9 @@
961409B01E43D15C00E7BA99 /* CollectionViewCard.swift in Headers */,
961409B11E43D15C00E7BA99 /* FABMenu.swift in Headers */,
961409B21E43D15C00E7BA99 /* FABMenuController.swift in Headers */,
96BFC16D1E63C10A0075DE1F /* Material+Motion.swift in Headers */,
96BFC16E1E63C10A0075DE1F /* Material+MotionAnimation.swift in Headers */,
96BFC16F1E63C10A0075DE1F /* SpringAnimation.swift in Headers */,
964335B71EC9432400FA9954 /* Events.swift in Headers */,
964335B81EC9432400FA9954 /* EventsController.swift in Headers */,
964335B91EC9432400FA9954 /* Material+MotionDynamics.swift in Headers */,
964335BA1EC9432400FA9954 /* TabsController.swift in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
......@@ -846,10 +874,12 @@
963832311B88DFD80015F710 /* Sources */,
963832331B88DFD80015F710 /* Headers */,
963832341B88DFD80015F710 /* Resources */,
966A7F0C1EEC582F00A2DAAC /* ShellScript */,
);
buildRules = (
);
dependencies = (
966A7F171EEC5B6300A2DAAC /* PBXTargetDependency */,
);
name = "Material iOS";
productName = FocusKit;
......@@ -886,6 +916,12 @@
mainGroup = 9638322C1B88DFD80015F710;
productRefGroup = 963832371B88DFD80015F710 /* Products */;
projectDirPath = "";
projectReferences = (
{
ProductGroup = 966A7F111EEC5B3500A2DAAC /* Products */;
ProjectRef = 966A7F101EEC5B3500A2DAAC /* Motion.xcodeproj */;
},
);
projectRoot = "";
targets = (
963832351B88DFD80015F710 /* Material iOS */,
......@@ -893,6 +929,16 @@
};
/* End PBXProject section */
/* Begin PBXReferenceProxy section */
966A7F151EEC5B3500A2DAAC /* Motion.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = Motion.framework;
remoteRef = 966A7F141EEC5B3500A2DAAC /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */
963832341B88DFD80015F710 /* Resources */ = {
isa = PBXResourcesBuildPhase;
......@@ -909,6 +955,22 @@
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
966A7F0C1EEC582F00A2DAAC /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "git submodule update --init --recursive";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
963832311B88DFD80015F710 /* Sources */ = {
isa = PBXSourcesBuildPhase;
......@@ -925,7 +987,6 @@
965E81041DD4D5C800D61E4B /* CollectionViewCell.swift in Sources */,
965E81071DD4D5C800D61E4B /* CollectionViewLayout.swift in Sources */,
965E81081DD4D5C800D61E4B /* CollectionReusableView.swift in Sources */,
96BFC1681E61D9FD0075DE1F /* Material+Motion.swift in Sources */,
965E81091DD4D5C800D61E4B /* DataSourceItem.swift in Sources */,
965E810A1DD4D5C800D61E4B /* Font.swift in Sources */,
965E810B1DD4D5C800D61E4B /* RobotoFont.swift in Sources */,
......@@ -953,7 +1014,6 @@
965E81221DD4D5C800D61E4B /* TextView.swift in Sources */,
965E80E61DD4C55200D61E4B /* Material+Obj-C.swift in Sources */,
965E80E71DD4C55200D61E4B /* Material+UIView.swift in Sources */,
960E35061EB0FC5700EB124A /* Material+MotionDynamics.swift in Sources */,
965E80E81DD4C55200D61E4B /* Material+CALayer.swift in Sources */,
965E80E91DD4C55200D61E4B /* Material+String.swift in Sources */,
965E80F71DD4D59500D61E4B /* Card.swift in Sources */,
......@@ -993,7 +1053,6 @@
965E80DB1DD4C50600D61E4B /* InterimSpace.swift in Sources */,
965E80DC1DD4C50600D61E4B /* Depth.swift in Sources */,
965E80DD1DD4C50600D61E4B /* EdgeInsets.swift in Sources */,
96BFC16A1E61DAA10075DE1F /* Material+MotionAnimation.swift in Sources */,
965E80DE1DD4C50600D61E4B /* Gravity.swift in Sources */,
965E80DF1DD4C50600D61E4B /* CornerRadius.swift in Sources */,
965E80FB1DD4D59500D61E4B /* SearchBar.swift in Sources */,
......@@ -1006,6 +1065,14 @@
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
966A7F171EEC5B6300A2DAAC /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = "Motion iOS";
targetProxy = 966A7F161EEC5B6300A2DAAC /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
9638324A1B88DFD80015F710 /* Debug */ = {
isa = XCBuildConfiguration;
......
......@@ -29,6 +29,7 @@
*/
import UIKit
import Motion
open class Button: UIButton, Pulseable, PulseableLayer {
/**
......
......@@ -474,7 +474,7 @@ open class Capture: View {
}
/// The session quality preset.
open var capturePreset = CapturePreset.presetHigh {
open var capturePreset = CapturePreset.presetPhoto {
didSet {
session.sessionPreset = CapturePresetToString(preset: capturePreset)
}
......
......@@ -29,6 +29,7 @@
*/
import UIKit
import Motion
@objc(CollectionReusableView)
open class CollectionReusableView: UICollectionReusableView, Pulseable, PulseableLayer {
......
......@@ -29,6 +29,7 @@
*/
import UIKit
import Motion
@objc(CollectionViewCell)
open class CollectionViewCell: UICollectionViewCell, Pulseable, PulseableLayer {
......
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* 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.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* 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
@objc(MotionAnimationFillMode)
public enum MotionAnimationFillMode: Int {
case forwards
case backwards
case both
case removed
}
/**
Converts the MotionAnimationFillMode enum value to a corresponding String.
- Parameter mode: An MotionAnimationFillMode enum value.
*/
public func MotionAnimationFillModeToValue(mode: MotionAnimationFillMode) -> String {
switch mode {
case .forwards:
return kCAFillModeForwards
case .backwards:
return kCAFillModeBackwards
case .both:
return kCAFillModeBoth
case .removed:
return kCAFillModeRemoved
}
}
@objc(MotionAnimationTimingFunction)
public enum MotionAnimationTimingFunction: Int {
case `default`
case linear
case easeIn
case easeOut
case easeInEaseOut
}
/**
Converts the MotionAnimationTimingFunction enum value to a corresponding CAMediaTimingFunction.
- Parameter function: An MotionAnimationTimingFunction enum value.
- Returns: A CAMediaTimingFunction.
*/
public func MotionAnimationTimingFunctionToValue(timingFunction: MotionAnimationTimingFunction) -> CAMediaTimingFunction {
switch timingFunction {
case .default:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault)
case .linear:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
case .easeIn:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
case .easeOut:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
case .easeInEaseOut:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
}
}
public typealias MotionDelayCancelBlock = (Bool) -> Void
fileprivate var MotionInstanceKey: UInt8 = 0
fileprivate var MotionInstanceControllerKey: UInt8 = 0
fileprivate struct MotionInstance {
fileprivate var identifier: String
fileprivate var animations: [MotionAnimation]
/// A reference to the panGesture for interactive transitions.
fileprivate var panGesture: UIPanGestureRecognizer?
}
fileprivate struct MotionInstanceController {
fileprivate var isMotionEnabled: Bool
fileprivate var isInteractiveMotionEnabled: Bool
fileprivate weak var delegate: MotionDelegate?
}
extension UIViewController: MotionDelegate, UIViewControllerTransitioningDelegate, UINavigationControllerDelegate, UITabBarControllerDelegate {
/// MotionInstanceController reference.
fileprivate var motionInstanceController: MotionInstanceController {
get {
return AssociatedObject(base: self, key: &MotionInstanceControllerKey) {
return MotionInstanceController(isMotionEnabled: false, isInteractiveMotionEnabled: false, delegate: nil)
}
}
set(value) {
AssociateObject(base: self, key: &MotionInstanceControllerKey, value: value)
}
}
/// A boolean that indicates whether motion is enabled.
open var isMotionEnabled: Bool {
get {
return motionInstanceController.isMotionEnabled
}
set(value) {
if value {
prepareMotionDelegation()
}
motionInstanceController.isMotionEnabled = value
}
}
/// A boolean that indicates whether interactive motion is enabled.
open var isInteractiveMotionEnabled: Bool {
get {
return motionInstanceController.isInteractiveMotionEnabled
}
set(value) {
if value {
prepareMotionDelegation()
view.prepareInteractiveTransitionPanGesture()
motionInstanceController.isMotionEnabled = true
}
motionInstanceController.isInteractiveMotionEnabled = value
}
}
/// A reference to the MotionDelegate.
open weak var motionDelegate: MotionDelegate? {
get {
return motionInstanceController.delegate
}
set(value) {
motionInstanceController.delegate = value
}
}
}
extension UIViewController {
/// Prepares the motionDelegate.
fileprivate func prepareMotionDelegation() {
modalPresentationStyle = .custom
transitioningDelegate = self
motionDelegate = self
(self as? UINavigationController)?.delegate = self
(self as? UITabBarController)?.delegate = self
}
}
extension UIViewController {
/**
Determines whether to use a Motion instance for transitions.
- Parameter forPresented presented: A UIViewController.
- Parameter presenting: A UIViewController.
- Parameter source: A UIViewController.
- Returns: An optional UIViewControllerAnimatedTransitioning.
*/
open func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
print("animationController(forPresented")
return isMotionEnabled ? PresentingMotion(isPresenting: true, isInteractive: isInteractiveMotionEnabled) : nil
}
/**
Determines whether to use a Motion instance for transitions.
- Parameter forDismissed dismissed: A UIViewController.
- Returns: An optional UIViewControllerAnimatedTransitioning.
*/
open func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
print("animationController(forDismissed")
return isMotionEnabled ? DismissingMotion(isPresenting: true, isInteractive: isInteractiveMotionEnabled) : nil
}
/**
Determines whether to use a MotionPresentationController for transitions.
- Parameter forPresented presented: A UIViewController.
- Parameter presenting: A UIViewController.
- Parameter source: A UIViewController.
- Returns: An optional UIPresentationController.
*/
open func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
print("presentationController:forPresented")
return isMotionEnabled ? MotionPresentationController(presentedViewController: presented, presenting: presenting) : nil
}
open func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
print("interactionControllerForPresentation")
return isInteractiveMotionEnabled ? InteractivePresentingMotion(animator: animator) : nil
}
open func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
print("interactionControllerForDismissal")
return isInteractiveMotionEnabled ? InteractiveDismissingMotion(animator: animator) : nil
}
}
extension UIViewController {
/**
Determines whether to use a Motion instance for transitions.
- Parameter _ navigationController: A UINavigationController.
- Parameter animationControllerFor operation: A UINavigationControllerOperation.
- Parameter from fromVC: A UIViewController that is being transitioned from.
- Parameter to toVC: A UIViewController that is being transitioned to.
- Returns: An optional UIViewControllerAnimatedTransitioning.
*/
open func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
print("navigationController(_ navigationController")
return fromVC.isMotionEnabled ? Motion(isPresenting: operation == .push, isInteractive: fromVC.isInteractiveMotionEnabled) : nil
}
/**
Determines whether to use a Motion instance for transitions.
- Parameter _ tabBarController: A UITabBarController.
- Parameter animationControllerForTransitionFrom fromVC: A UIViewController that is being transitioned from.
- Parameter to toVC: A UIViewController that is being transitioned to.
- Returns: An optional UIViewControllerAnimatedTransitioning.
*/
open func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
print("tabBarController(_ tabBarController")
return fromVC.isMotionEnabled ? Motion(isPresenting: true, isInteractive: fromVC.isInteractiveMotionEnabled) : nil
}
}
extension UIView {
/// MotionInstance reference.
fileprivate var motionInstance: MotionInstance {
get {
return AssociatedObject(base: self, key: &MotionInstanceKey) {
return MotionInstance(identifier: "", animations: [], panGesture: nil)
}
}
set(value) {
AssociateObject(base: self, key: &MotionInstanceKey, value: value)
}
}
/// An identifier value used to connect views across UIViewControllers.
open var motionIdentifier: String {
get {
return motionInstance.identifier
}
set(value) {
motionInstance.identifier = value
}
}
/// The animations to run while in transition.
open var motionAnimations: [MotionAnimation] {
get {
return motionInstance.animations
}
set(value) {
motionInstance.animations = value
}
}
}
extension UIView {
/// Prepares the interactive pan gesture.
fileprivate func prepareInteractiveTransitionPanGesture() {
motionInstance.panGesture = UIPanGestureRecognizer()
motionInstance.panGesture?.maximumNumberOfTouches = 1
addGestureRecognizer(motionInstance.panGesture!)
}
}
extension UIView {
/**
Snapshots the view instance for animations during transitions.
- Parameter afterUpdates: A boolean indicating whether to snapshot the view
after a render update, or as is.
- Parameter shouldHide: A boolean indicating whether the view should be hidden
after the snapshot is taken.
- Returns: A UIView instance that is a snapshot of the given UIView.
*/
open func transitionSnapshot(afterUpdates: Bool, shouldHide: Bool = true) -> UIView {
isHidden = false
// Material specific.
(self as? PulseableLayer)?.pulseLayer?.isHidden = true
let oldCornerRadius = layer.cornerRadius
layer.cornerRadius = 0
var oldBackgroundColor: UIColor?
if shouldHide {
oldBackgroundColor = backgroundColor
backgroundColor = .clear
}
let oldTransform = motionTransform
motionTransform = CATransform3DIdentity
let v = snapshotView(afterScreenUpdates: afterUpdates)!
layer.cornerRadius = oldCornerRadius
if shouldHide {
backgroundColor = oldBackgroundColor
}
motionTransform = oldTransform
let contentView = v.subviews.first!
contentView.layer.cornerRadius = layer.cornerRadius
contentView.layer.masksToBounds = true
v.motionIdentifier = motionIdentifier
v.layer.position = motionPosition
v.bounds = bounds
v.layer.cornerRadius = layer.cornerRadius
v.layer.zPosition = layer.zPosition
v.layer.opacity = layer.opacity
v.isOpaque = isOpaque
v.layer.anchorPoint = layer.anchorPoint
v.layer.masksToBounds = layer.masksToBounds
v.layer.borderColor = layer.borderColor
v.layer.borderWidth = layer.borderWidth
v.layer.shadowRadius = layer.shadowRadius
v.layer.shadowOpacity = layer.shadowOpacity
v.layer.shadowColor = layer.shadowColor
v.layer.shadowOffset = layer.shadowOffset
v.contentMode = contentMode
v.motionTransform = motionTransform
v.backgroundColor = backgroundColor
// Material specific.
(self as? PulseableLayer)?.pulseLayer?.isHidden = false
isHidden = shouldHide
return v
}
}
open class MotionPresentationController: UIPresentationController {
open override func presentationTransitionWillBegin() {
guard nil != containerView else {
return
}
presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in })
}
open override func presentationTransitionDidEnd(_ completed: Bool) {}
open override func dismissalTransitionWillBegin() {
guard nil != containerView else {
return
}
presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in })
}
open override func dismissalTransitionDidEnd(_ completed: Bool) {}
open override var frameOfPresentedViewInContainerView: CGRect {
return containerView?.bounds ?? .zero
}
}
@objc(MotionDelegate)
public protocol MotionDelegate {
@objc
optional func motion(motion: Motion, willTransition fromView: UIView, toView: UIView)
@objc
optional func motion(motion: Motion, didTransition fromView: UIView, toView: UIView)
@objc
optional func motionDelayTransitionByTimeInterval(motion: Motion) -> TimeInterval
@objc
optional func motionWillBeginPresentation(presentationController: UIPresentationController)
@objc
optional func motionAnimateAlongsideTransition(presentationController: UIPresentationController)
}
open class MotionAnimator: NSObject {
/// A boolean indicating whether Motion is presenting a view controller.
open fileprivate(set) var isPresenting: Bool
/// A boolean indicating whether Motion is interactive.
open fileprivate(set) var isInteractive: Bool
/**
An Array of UIView pairs with common motionIdentifiers in
the from and to view controllers.
*/
open fileprivate(set) var transitionPairs = [(UIView, UIView)]()
/// A reference to the transition snapshot.
open var transitionSnapshot: UIView!
/// A reference to the transition background view.
open let transitionBackgroundView = UIView()
/// The transition context for the current transition.
open fileprivate(set) var transitionContext: UIViewControllerContextTransitioning!
/// The transition delay time.
open fileprivate(set) var delay: TimeInterval = 0
/// The transition duration time.
open fileprivate(set) var duration: TimeInterval = 0.35
/// The transition container view.
open fileprivate(set) var containerView: UIView!
/// The view that is used to animate the transitions between view controllers.
open fileprivate(set) var transitionView = UIView()
/// A reference to the view controller that is being transitioned to.
open var toViewController: UIViewController {
return transitionContext.viewController(forKey: .to)!
}
/// A reference to the view controller that is being transitioned from.
open var fromViewController: UIViewController {
return transitionContext.viewController(forKey: .from)!
}
/// The view that is being transitioned to.
open var toView: UIView {
return toViewController.view
}
/// The subviews of the view being transitioned to.
open var toSubviews: [UIView] {
return Motion.subviews(of: toView)
}
/// The view that is being transitioned from.
open var fromView: UIView {
return fromViewController.view
}
/// The subviews of the view being transitioned from.
open var fromSubviews: [UIView] {
return Motion.subviews(of: fromView)
}
/// The default initializer.
public override init() {
isPresenting = false
isInteractive = false
super.init()
}
/**
An initializer to modify the presenting and container state.
- Parameter isPresenting: A boolean value indicating if the
Motion instance is presenting the view controller.
- Parameter isInteractive: A boolean value indicating if the
Motion instance is an interactive animation.
*/
public init(isPresenting: Bool, isInteractive: Bool) {
self.isPresenting = isPresenting
self.isInteractive = isInteractive
super.init()
}
/// Returns an Array of subviews for a given subview.
fileprivate class func subviews(of view: UIView) -> [UIView] {
var views: [UIView] = []
Motion.subviews(of: view, views: &views)
return views
}
/// Populates an Array of views that are subviews of a given view.
fileprivate class func subviews(of view: UIView, views: inout [UIView]) {
for v in view.subviews {
if 0 < v.motionIdentifier.utf16.count {
views.append(v)
}
subviews(of: v, views: &views)
}
}
/**
Executes a block of code after a time delay.
- Parameter duration: An animation duration time.
- Parameter animations: An animation block.
- Parameter execute block: A completion block that is executed once
the animations have completed.
*/
@discardableResult
open class func delay(_ time: TimeInterval, execute block: @escaping () -> Void) -> MotionDelayCancelBlock? {
func asyncAfter(completion: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + time, execute: completion)
}
var cancelable: MotionDelayCancelBlock?
let delayed: MotionDelayCancelBlock = {
if !$0 {
DispatchQueue.main.async(execute: block)
}
cancelable = nil
}
cancelable = delayed
asyncAfter {
cancelable?(false)
}
return cancelable
}
/**
Cancels the delayed MotionDelayCancelBlock.
- Parameter delayed completion: An MotionDelayCancelBlock.
*/
open class func cancel(delayed completion: MotionDelayCancelBlock) {
completion(true)
}
/**
Disables the default animations set on CALayers.
- Parameter animations: A callback that wraps the animations to disable.
*/
open class func disable(_ animations: (() -> Void)) {
animate(duration: 0, animations: animations)
}
/**
Runs an animation with a specified duration.
- Parameter duration: An animation duration time.
- Parameter animations: An animation block.
- Parameter timingFunction: An MotionAnimationTimingFunction value.
- Parameter completion: A completion block that is executed once
the animations have completed.
*/
open class func animate(duration: CFTimeInterval, timingFunction: MotionAnimationTimingFunction = .easeInEaseOut, animations: (() -> Void), completion: (() -> Void)? = nil) {
CATransaction.begin()
CATransaction.setAnimationDuration(duration)
CATransaction.setCompletionBlock(completion)
CATransaction.setAnimationTimingFunction(MotionAnimationTimingFunctionToValue(timingFunction: timingFunction))
animations()
CATransaction.commit()
}
}
extension MotionAnimator: UIViewControllerAnimatedTransitioning {
/**
The animation method that is used to coordinate the transition.
- Parameter using transitionContext: A UIViewControllerContextTransitioning.
*/
@objc(animateTransition:)
open func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
self.transitionContext = transitionContext
}
/**
Returns the transition duration time interval.
- Parameter using transitionContext: An optional UIViewControllerContextTransitioning.
- Returns: A TimeInterval that is the total animation time including delays.
*/
@objc(transitionDuration:)
open func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return delay + duration
}
}
extension MotionAnimator {
/// Adds the available transition animations.
fileprivate func addTransitionAnimations() {
for (from, to) in transitionPairs {
var snapshotAnimations = [CABasicAnimation]()
var snapshotChildAnimations = [CABasicAnimation]()
let sizeAnimation = MotionBasicAnimation.size(to.bounds.size)
let cornerRadiusAnimation = MotionBasicAnimation.corner(radius: to.layer.cornerRadius)
snapshotAnimations.append(sizeAnimation)
snapshotAnimations.append(cornerRadiusAnimation)
snapshotAnimations.append(MotionBasicAnimation.position(to: to.motionPosition))
snapshotAnimations.append(MotionBasicAnimation.transform(transform: to.motionTransform))
snapshotAnimations.append(MotionBasicAnimation.background(color: to.backgroundColor ?? .clear))
snapshotAnimations.append(MotionBasicAnimation.border(color: to.borderColor ?? .clear))
snapshotAnimations.append(MotionBasicAnimation.border(width: to.borderWidth))
if let path = to.layer.shadowPath {
let shadowPath = MotionBasicAnimation.shadow(path: path)
shadowPath.fromValue = fromView.layer.shadowPath
snapshotAnimations.append(shadowPath)
}
let shadowOffset = MotionBasicAnimation.shadow(offset: to.layer.shadowOffset)
shadowOffset.fromValue = fromView.layer.shadowOffset
snapshotAnimations.append(shadowOffset)
let shadowOpacity = MotionBasicAnimation.shadow(opacity: to.layer.shadowOpacity)
shadowOpacity.fromValue = fromView.layer.shadowOpacity
snapshotAnimations.append(shadowOpacity)
let shadowRadius = MotionBasicAnimation.shadow(radius: to.layer.shadowRadius)
shadowRadius.fromValue = fromView.layer.shadowRadius
snapshotAnimations.append(shadowRadius)
snapshotChildAnimations.append(cornerRadiusAnimation)
snapshotChildAnimations.append(sizeAnimation)
snapshotChildAnimations.append(MotionBasicAnimation.position(x: to.bounds.width / 2, y: to.bounds.height / 2))
let d = calculateAnimationTransitionDuration(animations: to.motionAnimations)
let snapshot = from.transitionSnapshot(afterUpdates: true)
transitionView.addSubview(snapshot)
Motion.delay(calculateAnimationDelayTimeInterval(animations: to.motionAnimations)) { [weak self, weak to] in
guard let s = self else {
return
}
guard let v = to else {
return
}
let tf = s.calculateAnimationTimingFunction(animations: v.motionAnimations)
let snapshotGroup = Motion.animate(group: snapshotAnimations, duration: d)
snapshotGroup.fillMode = MotionAnimationFillModeToValue(mode: .forwards)
snapshotGroup.isRemovedOnCompletion = false
snapshotGroup.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: tf)
if s.isInteractive {
snapshotGroup.speed = 0
}
let snapshotChildGroup = Motion.animate(group: snapshotChildAnimations, duration: d)
snapshotChildGroup.fillMode = MotionAnimationFillModeToValue(mode: .forwards)
snapshotChildGroup.isRemovedOnCompletion = false
snapshotChildGroup.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: tf)
if s.isInteractive {
snapshotChildGroup.speed = 0
}
snapshot.animate(snapshotGroup)
snapshot.subviews.first?.animate(snapshotChildGroup)
}
}
}
/// Adds the background animation.
fileprivate func addBackgroundAnimation() {
transitionBackgroundView.motion(.backgroundColor(isPresenting ? toView.backgroundColor ?? .clear : .clear), .duration(transitionDuration(using: transitionContext)))
}
}
extension MotionAnimator {
/**
Calculates the animation delay time based on the given Array of MotionAnimations.
- Parameter animations: An Array of MotionAnimations.
- Returns: A TimeInterval.
*/
fileprivate func calculateAnimationDelayTimeInterval(animations: [MotionAnimation]) -> TimeInterval {
var t: TimeInterval = 0
for a in animations {
switch a {
case let .delay(time):
if time > delay {
delay = time
}
t = time
default:break
}
}
return t
}
/**
Calculates the animation transition duration based on the given Array of MotionAnimations.
- Parameter animations: An Array of MotionAnimations.
- Returns: A TimeInterval.
*/
fileprivate func calculateAnimationTransitionDuration(animations: [MotionAnimation]) -> TimeInterval {
var t: TimeInterval = 0.35
for a in animations {
switch a {
case let .duration(time):
if time > duration {
duration = time
}
t = time
default:break
}
}
return t
}
/**
Calculates the animation timing function based on the given Array of MotionAnimations.
- Parameter animations: An Array of MotionAnimations.
- Returns: A MotionAnimationTimingFunction.
*/
fileprivate func calculateAnimationTimingFunction(animations: [MotionAnimation]) -> MotionAnimationTimingFunction {
var t = MotionAnimationTimingFunction.easeInEaseOut
for a in animations {
switch a {
case let .timingFunction(timingFunction):
t = timingFunction
default:break
}
}
return t
}
}
open class Motion: MotionAnimator {
/// A time value to delay the transition animation by.
fileprivate var delayTransitionByTimeInterval: TimeInterval {
return fromViewController.motionDelegate?.motionDelayTransitionByTimeInterval?(motion: self) ?? 0
}
/**
The animation method that is used to coordinate the transition.
- Parameter using transitionContext: A UIViewControllerContextTransitioning.
*/
@objc(animateTransition:)
open override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
super.animateTransition(using: transitionContext)
fromViewController.motionDelegate?.motion?(motion: self, willTransition: fromView, toView: toView)
Motion.delay(delayTransitionByTimeInterval) { [weak self] in
guard let s = self else {
return
}
s.prepareContainerView()
s.prepareTransitionSnapshot()
s.prepareTransitionPairs()
s.prepareTransitionView()
s.prepareTransitionBackgroundView()
s.prepareToView()
s.prepareTransitionAnimation()
}
}
/// Prepares the toView.
fileprivate func prepareToView() {
toView.isHidden = isPresenting
containerView.insertSubview(toView, belowSubview: transitionView)
toView.frame = fromView.frame
toView.updateConstraints()
toView.setNeedsLayout()
toView.layoutIfNeeded()
}
}
extension Motion {
/// Prepares the containerView.
fileprivate func prepareContainerView() {
containerView = transitionContext.containerView
}
/// Prepares the transitionSnapshot.
fileprivate func prepareTransitionSnapshot() {
transitionSnapshot = fromView.transitionSnapshot(afterUpdates: true, shouldHide: false)
transitionSnapshot.frame = fromView.frame
containerView.addSubview(transitionSnapshot)
}
/// Prepares the transitionPairs.
fileprivate func prepareTransitionPairs() {
for from in fromSubviews {
for to in toSubviews {
guard to.motionIdentifier == from.motionIdentifier else {
continue
}
transitionPairs.append((from, to))
}
}
}
/// Prepares the transitionView.
fileprivate func prepareTransitionView() {
transitionView.frame = toView.bounds
transitionView.isUserInteractionEnabled = false
containerView.insertSubview(transitionView, belowSubview: transitionSnapshot)
}
/// Prepares the transitionBackgroundView.
fileprivate func prepareTransitionBackgroundView() {
transitionBackgroundView.backgroundColor = isPresenting ? .clear : fromView.backgroundColor ?? .clear
transitionBackgroundView.frame = transitionView.bounds
transitionView.addSubview(transitionBackgroundView)
}
/// Prepares the transition animation.
fileprivate func prepareTransitionAnimation() {
addTransitionAnimations()
addBackgroundAnimation()
cleanUpAnimation()
removeTransitionSnapshot()
}
}
extension Motion {
/**
Creates a CAAnimationGroup.
- Parameter animations: An Array of CAAnimation objects.
- Parameter timingFunction: An MotionAnimationTimingFunction value.
- Parameter duration: An animation duration time for the group.
- Returns: A CAAnimationGroup.
*/
open class func animate(group animations: [CAAnimation], timingFunction: MotionAnimationTimingFunction = .easeInEaseOut, duration: CFTimeInterval = 0.5) -> CAAnimationGroup {
let group = CAAnimationGroup()
group.fillMode = MotionAnimationFillModeToValue(mode: .forwards)
group.isRemovedOnCompletion = false
group.animations = animations
group.duration = duration
group.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: timingFunction)
return group
}
}
extension Motion {
/// Cleans up the animation transition.
fileprivate func cleanUpAnimation() {
guard !isInteractive else {
return
}
Motion.delay(transitionDuration(using: transitionContext) + delayTransitionByTimeInterval) { [weak self] in
guard let s = self else {
return
}
s.showToSubviews()
s.removeTransitionView()
s.clearTransitionPairs()
s.completeTransition()
}
}
/// Removes the transitionSnapshot from its superview.
fileprivate func removeTransitionSnapshot() {
guard !isInteractive else {
return
}
Motion.delay(delay) { [weak self] in
self?.transitionSnapshot.removeFromSuperview()
}
}
/// Shows the toView and its subviews.
fileprivate func showToSubviews() {
toSubviews.forEach {
$0.isHidden = false
}
toView.isHidden = false
}
/// Clears the transitionPairs Array.
fileprivate func clearTransitionPairs() {
transitionPairs.removeAll()
}
/// Removes the transitionView.
fileprivate func removeTransitionView() {
transitionView.removeFromSuperview()
}
/// Calls the completionTransition method.
fileprivate func completeTransition() {
toViewController.motionDelegate?.motion?(motion: self, didTransition: fromView, toView: toView)
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
open class PresentingMotion: Motion {}
open class DismissingMotion: Motion {
/// Prepares the toView.
fileprivate override func prepareToView() {
toView.isHidden = true
toView.frame = fromView.frame
toView.updateConstraints()
toView.setNeedsLayout()
toView.layoutIfNeeded()
}
}
open class InteractiveMotion: UIPercentDrivenInteractiveTransition {
/// A reference to the view controller that is being transitioned to.
open var toViewController: UIViewController {
return transitionContext.viewController(forKey: .to)!
}
/// A reference to the view controller that is being transitioned from.
open var fromViewController: UIViewController {
return transitionContext.viewController(forKey: .from)!
}
/// The transition context for the current transition.
open fileprivate(set) var transitionContext: UIViewControllerContextTransitioning!
/// A reference to the panGesture for transitions.
open fileprivate(set) var panGesture: UIPanGestureRecognizer!
/// The transition container view.
open fileprivate(set) var containerView: UIView!
open fileprivate(set) var animator: UIViewControllerAnimatedTransitioning!
/**
An initializer that accepts an animator object.
- Parameter animator: UIViewControllerAnimatedTransitioning.
*/
public init(animator: UIViewControllerAnimatedTransitioning) {
super.init()
self.animator = animator
}
open override func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
super.startInteractiveTransition(transitionContext)
self.transitionContext = transitionContext
preparePanGesture()
}
//
//
// optional public var completionSpeed: CGFloat { get }
//
// optional public var completionCurve: UIViewAnimationCurve { get }
//
//
// /// In 10.0, if an object conforming to UIViewControllerAnimatedTransitioning is
// /// known to be interruptible, it is possible to start it as if it was not
// /// interactive and then interrupt the transition and interact with it. In this
// /// case, implement this method and return NO. If an interactor does not
// /// implement this method, YES is assumed.
// @available(iOS 10.0, *)
// optional public var wantsInteractiveStart: Bool { get }
}
extension InteractiveMotion {
fileprivate func preparePanGesture() {
(animator as? PresentingMotion)?.fromView.motionInstance.panGesture?.addTarget(self, action: #selector(handleInteractiveTransition(gestureRecognizer:)))
}
}
extension InteractiveMotion {
@objc
fileprivate func handleInteractiveTransition(gestureRecognizer: UIGestureRecognizer) {
switch gestureRecognizer.state {
case .began:
panGesture.setTranslation(.zero, in: containerView)
print("began")
case .changed:
let translation = panGesture.translation(in: containerView)
let percentage = fabs(translation.y / containerView.bounds.height)
update(percentage)
print("changed", percentage)
case .ended:
finish()
containerView.removeGestureRecognizer(panGesture)
print("ended")
default:break
}
}
}
open class InteractivePresentingMotion: InteractiveMotion {
}
open class InteractiveDismissingMotion: InteractiveMotion {
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* 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.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* 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
public enum MotionAnimationKeyPath: String {
case backgroundColor
case barTintColor
case borderColor
case borderWidth
case cornerRadius
case transform
case rotation = "transform.rotation"
case rotationX = "transform.rotation.x"
case rotationY = "transform.rotation.y"
case rotationZ = "transform.rotation.z"
case scale = "transform.scale"
case scaleX = "transform.scale.x"
case scaleY = "transform.scale.y"
case scaleZ = "transform.scale.z"
case translation = "transform.translation"
case translationX = "transform.translation.x"
case translationY = "transform.translation.y"
case translationZ = "transform.translation.z"
case position
case opacity
case zPosition
case width = "bounds.size.width"
case height = "bounds.size.height"
case size = "bounds.size"
case shadowPath
case shadowOffset
case shadowOpacity
case shadowRadius
}
public enum MotionAnimation {
case delay(TimeInterval)
case timingFunction(MotionAnimationTimingFunction)
case duration(TimeInterval)
case custom(CABasicAnimation)
case backgroundColor(UIColor)
case barTintColor(UIColor)
case borderColor(UIColor)
case borderWidth(CGFloat)
case cornerRadius(CGFloat)
case transform(CATransform3D)
case rotationAngle(CGFloat)
case rotationAngleX(CGFloat)
case rotationAngleY(CGFloat)
case rotationAngleZ(CGFloat)
case spin(CGFloat)
case spinX(CGFloat)
case spinY(CGFloat)
case spinZ(CGFloat)
case scale(CGFloat)
case scaleX(CGFloat)
case scaleY(CGFloat)
case scaleZ(CGFloat)
case translate(x: CGFloat, y: CGFloat)
case translateX(CGFloat)
case translateY(CGFloat)
case translateZ(CGFloat)
case x(CGFloat)
case y(CGFloat)
case point(x: CGFloat, y: CGFloat)
case position(x: CGFloat, y: CGFloat)
case fade(Double)
case zPosition(Int)
case width(CGFloat)
case height(CGFloat)
case size(width: CGFloat, height: CGFloat)
case shadowPath(CGPath)
case shadowOffset(CGSize)
case shadowOpacity(Float)
case shadowRadius(CGFloat)
case depth(shadowOffset: CGSize, shadowOpacity: Float, shadowRadius: CGFloat)
}
@available(iOS 10, *)
extension CALayer: CAAnimationDelegate {}
extension CALayer {
/**
A function that accepts CAAnimation objects and executes them on the
view's backing layer.
- Parameter animation: A CAAnimation instance.
*/
open func animate(_ animations: CAAnimation...) {
animate(animations)
}
/**
A function that accepts CAAnimation objects and executes them on the
view's backing layer.
- Parameter animation: A CAAnimation instance.
*/
open func animate(_ animations: [CAAnimation]) {
for animation in animations {
if nil == animation.delegate {
animation.delegate = self
}
if let a = animation as? CABasicAnimation {
a.fromValue = (presentation() ?? self).value(forKeyPath: a.keyPath!)
}
if let a = animation as? CAPropertyAnimation {
add(a, forKey: a.keyPath!)
} else if let a = animation as? CAAnimationGroup {
add(a, forKey: nil)
} else if let a = animation as? CATransition {
add(a, forKey: kCATransition)
}
}
}
open func animationDidStart(_ anim: CAAnimation) {}
/**
A delegation function that is executed when the backing layer stops
running an animation.
- Parameter animation: The CAAnimation instance that stopped running.
- Parameter flag: A boolean that indicates if the animation stopped
because it was completed or interrupted. True if completed, false
if interrupted.
*/
open func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
guard let a = anim as? CAPropertyAnimation else {
if let a = (anim as? CAAnimationGroup)?.animations {
for x in a {
animationDidStop(x, finished: true)
}
}
return
}
guard let b = a as? CABasicAnimation else {
return
}
guard let v = b.toValue else {
return
}
guard let k = b.keyPath else {
return
}
setValue(v, forKeyPath: k)
removeAnimation(forKey: k)
}
/**
A function that accepts a list of MotionAnimation values and executes them.
- Parameter animations: A list of MotionAnimation values.
*/
open func motion(_ animations: MotionAnimation...) {
motion(animations)
}
/**
A function that accepts an Array of MotionAnimation values and executes them.
- Parameter animations: An Array of MotionAnimation values.
- Parameter completion: An optional completion block.
*/
open func motion(_ animations: [MotionAnimation], completion: (() -> Void)? = nil) {
motion(delay: 0, duration: 0.35, timingFunction: .easeInEaseOut, animations: animations, completion: completion)
}
/**
A function that executes an Array of MotionAnimation values.
- Parameter delay: The animation delay TimeInterval.
- Parameter duration: The animation duration TimeInterval.
- Parameter timingFunction: The animation MotionAnimationTimingFunction.
- Parameter animations: An Array of MotionAnimations.
- Parameter completion: An optional completion block.
*/
fileprivate func motion(delay: TimeInterval, duration: TimeInterval, timingFunction: MotionAnimationTimingFunction, animations: [MotionAnimation], completion: (() -> Void)? = nil) {
var t = delay
for v in animations {
switch v {
case let .delay(time):
t = time
default:break
}
}
Motion.delay(t) { [weak self] in
guard let s = self else {
return
}
var a = [CABasicAnimation]()
var tf = timingFunction
var d = duration
var w: CGFloat = s.bounds.width
var h: CGFloat = s.bounds.height
for v in animations {
switch v {
case let .width(width):
w = width
case let .height(height):
h = height
case let .size(width, height):
w = width
h = height
default:break
}
}
var px: CGFloat = s.position.x
var py: CGFloat = s.position.y
for v in animations {
switch v {
case let .x(x):
px = x + w / 2
case let .y(y):
py = y + h / 2
case let .point(x, y):
px = x + w / 2
py = y + h / 2
default:break
}
}
for v in animations {
switch v {
case let .timingFunction(timingFunction):
tf = timingFunction
case let .duration(duration):
d = duration
case let .custom(animation):
a.append(animation)
case let .backgroundColor(color):
a.append(MotionBasicAnimation.background(color: color))
case let .barTintColor(color):
a.append(MotionBasicAnimation.barTint(color: color))
case let .borderColor(color):
a.append(MotionBasicAnimation.border(color: color))
case let .borderWidth(width):
a.append(MotionBasicAnimation.border(width: width))
case let .cornerRadius(radius):
a.append(MotionBasicAnimation.corner(radius: radius))
case let .transform(transform):
a.append(MotionBasicAnimation.transform(transform: transform))
case let .rotationAngle(angle):
let rotate = MotionBasicAnimation.rotation(angle: angle)
a.append(rotate)
case let .rotationAngleX(angle):
a.append(MotionBasicAnimation.rotationX(angle: angle))
case let .rotationAngleY(angle):
a.append(MotionBasicAnimation.rotationY(angle: angle))
case let .rotationAngleZ(angle):
a.append(MotionBasicAnimation.rotationZ(angle: angle))
case let .spin(rotations):
a.append(MotionBasicAnimation.spin(rotations: rotations))
case let .spinX(rotations):
a.append(MotionBasicAnimation.spinX(rotations: rotations))
case let .spinY(rotations):
a.append(MotionBasicAnimation.spinY(rotations: rotations))
case let .spinZ(rotations):
a.append(MotionBasicAnimation.spinZ(rotations: rotations))
case let .scale(to):
a.append(MotionBasicAnimation.scale(to: to))
case let .scaleX(to):
a.append(MotionBasicAnimation.scaleX(to: to))
case let .scaleY(to):
a.append(MotionBasicAnimation.scaleY(to: to))
case let .scaleZ(to):
a.append(MotionBasicAnimation.scaleZ(to: to))
case let .translate(x, y):
a.append(MotionBasicAnimation.translate(to: CGPoint(x: x, y: y)))
case let .translateX(to):
a.append(MotionBasicAnimation.translateX(to: to))
case let .translateY(to):
a.append(MotionBasicAnimation.translateY(to: to))
case let .translateZ(to):
a.append(MotionBasicAnimation.translateZ(to: to))
case .x(_), .y(_), .point(_, _):
let position = MotionBasicAnimation.position(to: CGPoint(x: px, y: py))
a.append(position)
case let .position(x, y):
a.append(MotionBasicAnimation.position(to: CGPoint(x: x, y: y)))
case let .fade(opacity):
let fade = MotionBasicAnimation.fade(opacity: opacity)
fade.fromValue = s.value(forKey: MotionAnimationKeyPath.opacity.rawValue) ?? NSNumber(floatLiteral: 1)
a.append(fade)
case let .zPosition(index):
let zPosition = MotionBasicAnimation.zPosition(index: index)
zPosition.fromValue = s.value(forKey: MotionAnimationKeyPath.zPosition.rawValue) ?? NSNumber(integerLiteral: 0)
a.append(zPosition)
case .width(_), .height(_), .size(_, _):
a.append(MotionBasicAnimation.size(CGSize(width: w, height: h)))
case let .shadowPath(path):
let shadowPath = MotionBasicAnimation.shadow(path: path)
shadowPath.fromValue = s.shadowPath
a.append(shadowPath)
case let .shadowOffset(offset):
let shadowOffset = MotionBasicAnimation.shadow(offset: offset)
shadowOffset.fromValue = s.shadowOffset
a.append(shadowOffset)
case let .shadowOpacity(opacity):
let shadowOpacity = MotionBasicAnimation.shadow(opacity: opacity)
shadowOpacity.fromValue = s.shadowOpacity
a.append(shadowOpacity)
case let .shadowRadius(radius):
let shadowRadius = MotionBasicAnimation.shadow(radius: radius)
shadowRadius.fromValue = s.shadowRadius
a.append(shadowRadius)
case let .depth(offset, opacity, radius):
if let path = s.shadowPath {
let shadowPath = MotionBasicAnimation.shadow(path: path)
shadowPath.fromValue = s.shadowPath
a.append(shadowPath)
}
let shadowOffset = MotionBasicAnimation.shadow(offset: offset)
shadowOffset.fromValue = s.shadowOffset
a.append(shadowOffset)
let shadowOpacity = MotionBasicAnimation.shadow(opacity: opacity)
shadowOpacity.fromValue = s.shadowOpacity
a.append(shadowOpacity)
let shadowRadius = MotionBasicAnimation.shadow(radius: radius)
shadowRadius.fromValue = s.shadowRadius
a.append(shadowRadius)
default:break
}
}
let g = Motion.animate(group: a, duration: d)
g.fillMode = MotionAnimationFillModeToValue(mode: .forwards)
g.isRemovedOnCompletion = false
g.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: tf)
s.animate(g)
guard let execute = completion else {
return
}
Motion.delay(d, execute: execute)
}
}
}
extension UIView {
/// Computes the rotation of the view.
open var motionRotationAngle: CGFloat {
get {
return CGFloat(atan2f(Float(transform.b), Float(transform.a))) * 180 / CGFloat(Double.pi)
}
set(value) {
transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi) * value / 180)
}
}
/// The global position of a view.
open var motionPosition: CGPoint {
return superview?.convert(layer.position, to: nil) ?? layer.position
}
/// The layer.transform of a view.
open var motionTransform: CATransform3D {
get {
return layer.transform
}
set(value) {
layer.transform = value
}
}
/// Computes the scale X axis value of the view.
open var motionScaleX: CGFloat {
return transform.a
}
/// Computes the scale Y axis value of the view.
open var motionScaleY: CGFloat {
return transform.b
}
/**
A function that accepts CAAnimation objects and executes them on the
view's backing layer.
- Parameter animations: A list of CAAnimations.
*/
open func animate(_ animations: CAAnimation...) {
layer.animate(animations)
}
/**
A function that accepts an Array of CAAnimation objects and executes
them on the view's backing layer.
- Parameter animations: An Array of CAAnimations.
*/
open func animate(_ animations: [CAAnimation]) {
layer.animate(animations)
}
/**
A function that accepts a list of MotionAnimation values and executes
them on the view's backing layer.
- Parameter animations: A list of MotionAnimation values.
*/
open func motion(_ animations: MotionAnimation...) {
layer.motion(animations)
}
/**
A function that accepts an Array of MotionAnimation values and executes
them on the view's backing layer.
- Parameter animations: An Array of MotionAnimation values.
- Parameter completion: An optional completion block.
*/
open func motion(_ animations: [MotionAnimation], completion: (() -> Void)? = nil) {
layer.motion(animations, completion: completion)
}
}
extension CABasicAnimation {
/**
A convenience initializer that takes a given MotionAnimationKeyPath.
- Parameter keyPath: An MotionAnimationKeyPath.
*/
public convenience init(keyPath: MotionAnimationKeyPath) {
self.init(keyPath: keyPath.rawValue)
}
}
extension CAKeyframeAnimation {
/**
A convenience initializer that takes a given MotionAnimationKeyPath.
- Parameter keyPath: An MotionAnimationKeyPath.
*/
public convenience init(keyPath: MotionAnimationKeyPath) {
self.init(keyPath: keyPath.rawValue)
}
}
public struct MotionKeyframeAnimation {
/**
Creates a CABasicAnimation for the backgroundColor key path.
- Parameter color: A UIColor.
- Returns: A CABasicAnimation.
*/
public static func background(color: UIColor) -> CAKeyframeAnimation {
let animation = CAKeyframeAnimation(keyPath: .backgroundColor)
animation.values = [color.cgColor]
return animation
}
}
public struct MotionBasicAnimation {
/**
Creates a CABasicAnimation for the backgroundColor key path.
- Parameter color: A UIColor.
- Returns: A CABasicAnimation.
*/
public static func background(color: UIColor) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .backgroundColor)
animation.toValue = color.cgColor
return animation
}
/**
Creates a CABasicAnimation for the barTintColor key path.
- Parameter color: A UIColor.
- Returns: A CABasicAnimation.
*/
public static func barTint(color: UIColor) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .barTintColor)
animation.toValue = color.cgColor
return animation
}
/**
Creates a CABasicAnimation for the borderColor key path.
- Parameter color: A UIColor.
- Returns: A CABasicAnimation.
*/
public static func border(color: UIColor) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .borderColor)
animation.toValue = color.cgColor
return animation
}
/**
Creates a CABasicAnimation for the borderWidth key path.
- Parameter width: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func border(width: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .borderWidth)
animation.toValue = width
return animation
}
/**
Creates a CABasicAnimation for the cornerRadius key path.
- Parameter radius: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func corner(radius: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .cornerRadius)
animation.toValue = radius
return animation
}
/**
Creates a CABasicAnimation for the transform key path.
- Parameter transform: A CATransform3D object.
- Returns: A CABasicAnimation.
*/
public static func transform(transform: CATransform3D) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .transform)
animation.toValue = NSValue(caTransform3D: transform)
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation key path.
- Parameter angle: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func rotation(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotation)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * angle / 180))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation.x key path.
- Parameter angle: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func rotationX(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationX)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * angle / 180))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation.y key path.
- Parameter angle: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func rotationY(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationY)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * angle / 180))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation.z key path.
- Parameter angle: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func rotationZ(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationZ)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * angle / 180))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation key path.
- Parameter rotations: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func spin(rotations: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotation)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * 2 * rotations))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation.x key path.
- Parameter rotations: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func spinX(rotations: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationX)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * 2 * rotations))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation.y key path.
- Parameter rotations: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func spinY(rotations: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationY)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * 2 * rotations))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation.z key path.
- Parameter rotations: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func spinZ(rotations: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationZ)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * 2 * rotations))
return animation
}
/**
Creates a CABasicAnimation for the transform.scale key path.
- Parameter to scale: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func scale(to scale: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .scale)
animation.toValue = NSNumber(value: Double(scale))
return animation
}
/**
Creates a CABasicAnimation for the transform.scale.x key path.
- Parameter to scale: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func scaleX(to scale: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .scaleX)
animation.toValue = NSNumber(value: Double(scale))
return animation
}
/**
Creates a CABasicAnimation for the transform.scale.y key path.
- Parameter to scale: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func scaleY(to scale: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .scaleY)
animation.toValue = NSNumber(value: Double(scale))
return animation
}
/**
Creates a CABasicAnimation for the transform.scale.z key path.
- Parameter to scale: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func scaleZ(to scale: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .scaleZ)
animation.toValue = NSNumber(value: Double(scale))
return animation
}
/**
Creates a CABasicAnimation for the transform.translation key path.
- Parameter point: A CGPoint.
- Returns: A CABasicAnimation.
*/
public static func translate(to point: CGPoint) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .translation)
animation.toValue = NSValue(cgPoint: point)
return animation
}
/**
Creates a CABasicAnimation for the transform.translation.x key path.
- Parameter to translation: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func translateX(to translation: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .translationX)
animation.toValue = NSNumber(value: Double(translation))
return animation
}
/**
Creates a CABasicAnimation for the transform.translation.y key path.
- Parameter to translation: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func translateY(to translation: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .translationY)
animation.toValue = NSNumber(value: Double(translation))
return animation
}
/**
Creates a CABasicAnimation for the transform.translation.z key path.
- Parameter to translation: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func translateZ(to translation: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .translationZ)
animation.toValue = NSNumber(value: Double(translation))
return animation
}
/**
Creates a CABasicAnimation for the position key path.
- Parameter x: A CGFloat.
- Parameter y: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func position(x: CGFloat, y: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .position)
animation.toValue = NSValue(cgPoint: CGPoint(x: x, y: y))
return animation
}
/**
Creates a CABasicAnimation for the position key path.
- Parameter to point: A CGPoint.
- Returns: A CABasicAnimation.
*/
public static func position(to point: CGPoint) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .position)
animation.toValue = NSValue(cgPoint: point)
return animation
}
/**
Creates a CABasicAnimation for the opacity key path.
- Parameter opacity: A Double.
- Returns: A CABasicAnimation.
*/
public static func fade(opacity: Double) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .opacity)
animation.toValue = NSNumber(floatLiteral: opacity)
return animation
}
/**
Creates a CABasicaAnimation for the zPosition key path.
- Parameter index: An Int.
- Returns: A CABasicAnimation.
*/
public static func zPosition(index: Int) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .zPosition)
animation.toValue = NSNumber(integerLiteral: index)
return animation
}
/**
Creates a CABasicaAnimation for the width key path.
- Parameter width: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func width(_ width: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .width)
animation.toValue = NSNumber(floatLiteral: Double(width))
return animation
}
/**
Creates a CABasicaAnimation for the height key path.
- Parameter height: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func height(_ height: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .height)
animation.toValue = NSNumber(floatLiteral: Double(height))
return animation
}
/**
Creates a CABasicaAnimation for the height key path.
- Parameter size: A CGSize.
- Returns: A CABasicAnimation.
*/
public static func size(_ size: CGSize) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .size)
animation.toValue = NSValue(cgSize: size)
return animation
}
/**
Creates a CABasicAnimation for the shadowPath key path.
- Parameter path: A CGPath.
- Returns: A CABasicAnimation.
*/
public static func shadow(path: CGPath) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .shadowPath)
animation.toValue = path
return animation
}
/**
Creates a CABasicAnimation for the shadowOffset key path.
- Parameter offset: CGSize.
- Returns: A CABasicAnimation.
*/
public static func shadow(offset: CGSize) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .shadowOffset)
animation.toValue = NSValue(cgSize: offset)
return animation
}
/**
Creates a CABasicAnimation for the shadowOpacity key path.
- Parameter opacity: Float.
- Returns: A CABasicAnimation.
*/
public static func shadow(opacity: Float) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .shadowOpacity)
animation.toValue = NSNumber(floatLiteral: Double(opacity))
return animation
}
/**
Creates a CABasicAnimation for the shadowRadius key path.
- Parameter radius: CGFloat.
- Returns: A CABasicAnimation.
*/
public static func shadow(radius: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .shadowRadius)
animation.toValue = NSNumber(floatLiteral: Double(radius))
return animation
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* 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.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* 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
open class MotionDynamics: UIDynamicBehavior {
open var targetPoint: CGPoint {
get {
return attachmentBehavior.anchorPoint
}
set(value) {
attachmentBehavior.anchorPoint = value
}
}
open var velocity: CGPoint {
get {
return dynamicItemBehavior.linearVelocity(for: item)
}
set(value) {
let v = velocity
dynamicItemBehavior.addLinearVelocity(CGPoint(x: value.x - v.x, y: value.y - v.y), for: item)
}
}
open fileprivate(set) var item: UIDynamicItem
open fileprivate(set) var attachmentBehavior: UIAttachmentBehavior!
open fileprivate(set) var dynamicItemBehavior: UIDynamicItemBehavior!
public init(item: UIDynamicItem) {
self.item = item
}
open func prepare() {
prepareAttachmentBehavior()
prepareDynamicItemBehavior()
}
}
extension MotionDynamics {
fileprivate func prepareAttachmentBehavior() {
attachmentBehavior = UIAttachmentBehavior(item: item, attachedToAnchor: .zero)
attachmentBehavior.frequency = 3.5
attachmentBehavior.damping = 0.4
attachmentBehavior.length = 0
addChildBehavior(attachmentBehavior)
}
fileprivate func prepareDynamicItemBehavior() {
dynamicItemBehavior = UIDynamicItemBehavior(items: [item])
dynamicItemBehavior.density = 100
dynamicItemBehavior.resistance = 10
addChildBehavior(dynamicItemBehavior)
}
}
public enum PaneState: Int {
case opened
case closed
}
@objc(MotionDynamicsAnimatorDelegate)
public protocol MotionDynamicsAnimatorDelegate {
// - (void)animationTick:(CFTimeInterval)dt finished:(BOOL *)finished;
}
open class PaneMotionDynamicsAnimator: MotionDynamicsAnimatorDelegate {
// - (void)animationTick:(CFTimeInterval)dt finished:(BOOL *)finished;
}
open class PaneController: UIViewController {
fileprivate var motionDynamics: MotionDynamics?
fileprivate var pane: UIView!
fileprivate var paneMotionDynamicsAnimator: PaneMotionDynamicsAnimator!
fileprivate var animator: UIDynamicItemBehavior!
open override func viewDidLoad() {
super.viewDidLoad()
prepare()
}
open func prepare() {
prepareAnimator()
preparePane()
prepareMotionDynamics()
preparePaneMotionDynamicsAnimator()
}
}
extension PaneController {
fileprivate func prepareAnimator() {
animator = UIDynamicItemBehavior()
}
fileprivate func preparePane() {
pane = UIView()
}
fileprivate func prepareMotionDynamics() {
guard nil == motionDynamics else {
return
}
motionDynamics = MotionDynamics(item: pane)
}
fileprivate func preparePaneMotionDynamicsAnimator() {
paneMotionDynamicsAnimator = PaneMotionDynamicsAnimator()
}
}
extension PaneController {
fileprivate func targetPointFor(state: PaneState) -> CGPoint {
return .zero
}
}
extension PaneController {
fileprivate func draggable(view: UIView, draggingEndedWith velocity: CGPoint) {
animatePaneTo(state: velocity.y < 0 ? .opened : .closed, initialVelocity: velocity)
}
fileprivate func animatePaneTo(state: PaneState, initialVelocity: CGPoint) {
prepareMotionDynamics()
motionDynamics?.targetPoint = targetPointFor(state: state)
if initialVelocity.equalTo(.zero) {
motionDynamics?.velocity = initialVelocity
}
// animator.addChildBehavior(motionDynamics)
}
}
......@@ -29,6 +29,7 @@
*/
import UIKit
import Motion
@objc(PulseAnimation)
public enum PulseAnimation: Int {
......
......@@ -29,6 +29,7 @@
*/
import UIKit
import Motion
open class PulseView: View, Pulseable, PulseableLayer {
/// A Pulse reference.
......
......@@ -29,6 +29,7 @@
*/
import UIKit
import Motion
@objc(SnackbarControllerDelegate)
public protocol SnackbarControllerDelegate {
......
......@@ -29,6 +29,7 @@
*/
import UIKit
import Motion
open class TableViewCell: UITableViewCell, Pulseable, PulseableLayer {
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment