Commit eb81228c by Daniel Dahan

initial commit

parents
#OS X
.DS_Store
# Xcode
build/*
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
profile
*.moved-aside
*.playground
*.framework
# Contributing Guidelines
This document contains information and guidelines about contributing to this project.
Please read it before you start participating.
**Topics**
* [Pull Request Submissions](#pull-request-submissions)
* [Asking Questions](#asking-questions)
* [Reporting Security Issues](#reporting-security-issues)
* [Reporting Issues](#reporting-other-issues)
* [Developers Certificate of Origin](#developers-certificate-of-origin)
* [Code of Conduct](#code-of-conduct)
<a href="#pull-request-submissions"></a>
## Pull Request Submissions.
All pull requests should be made to the development branch. Details should be available describing your fix. Before submitting a pull request, please confirm that merge issues are resolved.
<a href="#asking-questions"></a>
## Asking Questions
We don't use GitHub as a support forum.
For any usage questions that are not specific to the project itself,
please ask on [Stack Overflow](http://stackoverflow.com/questions/tagged/cosmicmind) instead.
By doing so, you'll be more likely to quickly solve your problem,
and you'll allow anyone else with the same question to find the answer.
This also allows maintainers to focus on improving the project for others.
<a href="#reporting-security-issues"></a>
## Reporting Security Issues
CosmicMind takes security seriously.
If you discover a security issue, please bring it to our attention right away!
Please **DO NOT** file a public issue,
instead send your report privately to <support@cosmicmind.com>.
This will help ensure that any vulnerabilities that _are_ found
can be [disclosed responsibly](http://en.wikipedia.org/wiki/Responsible_disclosure)
to any affected parties.
<a href="#reporting-other-issues"></a>
## Reporting Other Issues
A great way to contribute to the project
is to send a detailed issue when you encounter an problem.
We always appreciate a well-written, thorough bug report.
Check that the project issues database
doesn't already include that problem or suggestion before submitting an issue.
If you find a match, add a quick "+1" or "I have this problem too."
Doing this helps prioritize the most common problems and requests.
When reporting issues, please include the following:
* The version of Xcode you're using
* The version of iOS you're targeting
* The full output of any stack trace or compiler error
* A code snippet that reproduces the described behavior, if applicable
* Any other details that would be useful in understanding the problem
This information will help us review and fix your issue faster.
<a href="#developers-certificate-of-origin"></a>
## Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
- (a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
- (b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
- (c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
- (d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
---
*Some of the ideas and wording for the statements above were based on work by the [Alamofire](https://github.com/Alamofire/Alamofire/blob/master/CONTRIBUTING.md), [Docker](https://github.com/docker/docker/blob/master/CONTRIBUTING.md) and [Linux](http://elinux.org/Developer_Certificate_Of_Origin) communities. We commend them for their efforts to facilitate collaboration in their projects.*
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.
Pod::Spec.new do |s|
s.name = 'Motion'
s.version = '1.0.0'
s.license = 'BSD-3-Clause'
s.summary = 'A seamless animation and transition framework in Swift.'
s.homepage = 'http://cosmicmind.com/motion'
s.social_media_url = 'https://www.facebook.com/cosmicmindcom'
s.authors = { 'CosmicMind, Inc.' => 'support@cosmicmind.com' }
s.source = { :git => 'https://github.com/CosmicMind/Motion.git', :tag => s.version }
s.platform = :ios, '8.0'
s.default_subspec = 'Core'
s.subspec 'Core' do |s|
s.ios.deployment_target = '8.0'
s.ios.source_files = 'Sources/**/*.swift'
s.requires_arc = true
end
end
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
96C98DE01E424B9000B22906 /* Motion.h in Headers */ = {isa = PBXBuildFile; fileRef = 96C98DDE1E424B9000B22906 /* Motion.h */; };
96C98DE41E4382B100B22906 /* Motion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C98DE31E4382B100B22906 /* Motion.swift */; };
96C98DE61E43848500B22906 /* MotionAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C98DE51E43848500B22906 /* MotionAnimation.swift */; };
96C98DE81E43849B00B22906 /* MotionTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C98DE71E43849B00B22906 /* MotionTransition.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
96C98DD11E424AB000B22906 /* Motion.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Motion.framework; sourceTree = BUILT_PRODUCTS_DIR; };
96C98DDD1E424B9000B22906 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
96C98DDE1E424B9000B22906 /* Motion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Motion.h; sourceTree = "<group>"; };
96C98DE21E43809D00B22906 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
96C98DE31E4382B100B22906 /* Motion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Motion.swift; sourceTree = "<group>"; };
96C98DE51E43848500B22906 /* MotionAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionAnimation.swift; sourceTree = "<group>"; };
96C98DE71E43849B00B22906 /* MotionTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionTransition.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
96C98DCD1E424AB000B22906 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
96C98DC71E424AB000B22906 = {
isa = PBXGroup;
children = (
96C98DDC1E424B9000B22906 /* Sources */,
96C98DD21E424AB000B22906 /* Products */,
);
sourceTree = "<group>";
};
96C98DD21E424AB000B22906 /* Products */ = {
isa = PBXGroup;
children = (
96C98DD11E424AB000B22906 /* Motion.framework */,
);
name = Products;
sourceTree = "<group>";
};
96C98DDC1E424B9000B22906 /* Sources */ = {
isa = PBXGroup;
children = (
96C98DE21E43809D00B22906 /* LICENSE */,
96C98DDD1E424B9000B22906 /* Info.plist */,
96C98DDE1E424B9000B22906 /* Motion.h */,
96C98DE31E4382B100B22906 /* Motion.swift */,
96C98DE51E43848500B22906 /* MotionAnimation.swift */,
96C98DE71E43849B00B22906 /* MotionTransition.swift */,
);
path = Sources;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
96C98DCE1E424AB000B22906 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
96C98DE01E424B9000B22906 /* Motion.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
96C98DD01E424AB000B22906 /* Motion iOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 96C98DD91E424AB000B22906 /* Build configuration list for PBXNativeTarget "Motion iOS" */;
buildPhases = (
96C98DCC1E424AB000B22906 /* Sources */,
96C98DCD1E424AB000B22906 /* Frameworks */,
96C98DCE1E424AB000B22906 /* Headers */,
96C98DCF1E424AB000B22906 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "Motion iOS";
productName = Motion;
productReference = 96C98DD11E424AB000B22906 /* Motion.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
96C98DC81E424AB000B22906 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0820;
ORGANIZATIONNAME = "CosmicMind, Inc.";
TargetAttributes = {
96C98DD01E424AB000B22906 = {
CreatedOnToolsVersion = 8.2.1;
LastSwiftMigration = 0820;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 96C98DCB1E424AB000B22906 /* Build configuration list for PBXProject "Motion" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 96C98DC71E424AB000B22906;
productRefGroup = 96C98DD21E424AB000B22906 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
96C98DD01E424AB000B22906 /* Motion iOS */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
96C98DCF1E424AB000B22906 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
96C98DCC1E424AB000B22906 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
96C98DE61E43848500B22906 /* MotionAnimation.swift in Sources */,
96C98DE41E4382B100B22906 /* Motion.swift in Sources */,
96C98DE81E43849B00B22906 /* MotionTransition.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
96C98DD71E424AB000B22906 /* 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_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
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 = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
96C98DD81E424AB000B22906 /* 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_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
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 = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
96C98DDA1E424AB000B22906 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.cosmicmind.Motion;
PRODUCT_MODULE_NAME = Motion;
PRODUCT_NAME = Motion;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
};
name = Debug;
};
96C98DDB1E424AB000B22906 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.cosmicmind.Motion;
PRODUCT_MODULE_NAME = Motion;
PRODUCT_NAME = Motion;
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
96C98DCB1E424AB000B22906 /* Build configuration list for PBXProject "Motion" */ = {
isa = XCConfigurationList;
buildConfigurations = (
96C98DD71E424AB000B22906 /* Debug */,
96C98DD81E424AB000B22906 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
96C98DD91E424AB000B22906 /* Build configuration list for PBXNativeTarget "Motion iOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
96C98DDA1E424AB000B22906 /* Debug */,
96C98DDB1E424AB000B22906 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 96C98DC81E424AB000B22906 /* Project object */;
}
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:Motion.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0820"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "96C98DD01E424AB000B22906"
BuildableName = "Motion.framework"
BlueprintName = "Motion iOS"
ReferencedContainer = "container:Motion.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<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 = "96C98DD01E424AB000B22906"
BuildableName = "Motion.framework"
BlueprintName = "Motion iOS"
ReferencedContainer = "container:Motion.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "96C98DD01E424AB000B22906"
BuildableName = "Motion.framework"
BlueprintName = "Motion iOS"
ReferencedContainer = "container:Motion.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
![Material](http://www.cosmicmind.com/material/github/material-logo.png)
## Welcome to Material
Material is an animation and graphics framework for Material Design in Swift.
![Material Sample](http://cosmicmind.com/samples/github/page-tab-bar-controller-2.png)
* [Download the latest sample](https://github.com/CosmicMind/Samples/tree/master/Graph/CardTableView).
## About Material 2
The first version of Material was to bring Material Design to iOS. We considered that a great starting point, but not the entire story. Material 2 is the next chapter, which goes deeper into iOS with refined APIs that simplify Architecture, Photo Library, Reminders, Text Editing, Photo & Video, and much more. In addition to Material Design, we love Apple’s flat UI. Having this in mind, we made it possible to accomplish both UI styles with ease.
## Why A Separate Samples Repo?
We moved all sample projects to a separate repo named [Samples](https://github.com/CosmicMind/Samples) to allow their development to be independent of the Material framework. There has been instances where we needed to update the versions of the framework to accommodate changes that only occurred in the sample projects.
## Features
- [x] Completely Customizable
- [x] Animations
- [x] Grid System
- [x] Layout Library
- [x] Color Library
- [x] Photo Library
- [x] Photo & Video
- [x] Cards
- [x] Menus
- [x] Icons
- [x] TextField
- [X] Snackbar
- [x] TabBar
- [x] PageTabBar
- [X] SearchBar
- [x] NavigationController
- [x] NavigationDrawer
- [x] BottomNavigationBar
- [x] Sample Projects
- [x] And More...
## Releasing January 2017 - Material 2.5.0
- [x] Reminders
- [x] Text Editor
- [x] Toasts
- [x] Dialogs & Alerts
- [x] Bottom Sheets
- [x] Transitions
- [x] Updated Motion API
- [x] And More...
## Requirements
* iOS 8.0+ / Mac OS X 10.9+
* Xcode 8.0+
## Communication
- If you **need help**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/cosmicmind). (Tag 'cosmicmind')
- If you'd like to **ask a general question**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/cosmicmind).
- If you **found a bug**, _and can provide steps to reliably reproduce it_, open an issue.
- If you **have a feature request**, open an issue.
- If you **want to contribute**, submit a pull request.
## Installation
> **Embedded frameworks require a minimum deployment target of iOS 8 or OS X Mavericks (10.9).**
> - [Download Material](https://github.com/CosmicMind/Material/archive/master.zip)
Visit the [Installation](https://github.com/CosmicMind/Material/wiki/Installation) page to learn how to install Material using [CocoaPods](http://cocoapods.org) and [Carthage](https://github.com/Carthage/Carthage).
## Changelog
Material is a growing project and will encounter changes throughout its development. It is recommended that the [Changelog](https://github.com/CosmicMind/Material/wiki/Changelog) be reviewed prior to updating versions.
# Samples
The following are samples to see how Material may be used within your applications.
* Visit the [Samples](https://github.com/CosmicMind/Samples) repo to see sample projects using Graph.
## Icons
Icons is a library of Google and CosmicMind icons that are available for use within your iOS applications.
![Icon](http://www.cosmicmind.com/gifs/marketplace/icons.png)
[Learn More](http://cosmicmind.com/material/icons)
## Colors
Try the Material Colors app to see the wonderful colors available in Material, or use the online version at [MaterialColors.io](http://materialcolors.io).
![MaterialColors](http://www.cosmicmind.com/gifs/shared/colors.gif)
[Get Material Colors on the AppStore](https://itunes.apple.com/app/x/id1111994400?mt=8)
## TextField
A TextField is an excellent way to improve UX. It allows for a placeholder and additional hint details.
![TextField](http://www.cosmicmind.com/gifs/white/text-field.gif)
* Download the complete [TextField sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/TextField).
* Learn more about [TextField](http://cosmicmind.com/material/textfield).
## Button
A button is used to trigger an action through a touch event. Material comes with a foundational button, and 4 specialized buttons that can be stylized in any way.
![Material Image](http://www.cosmicmind.com/material/white/button.gif)
* Download the complete [Button sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/Button).
* Learn more about [Button](http://cosmicmind.com/material/button).
## Switch
A switch is a control component that toggles between on and off states.
![Material Image](http://www.cosmicmind.com/material/white/switch.gif)
* Download the complete [Switch sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/Switch).
* Learn more about [Switch](http://cosmicmind.com/material/switch).
## Card
A Card is a flexible component that may be configured in any way you like. It has a Toolbar, Bar, and content area that may utilize any UIView type.
![Material Image](http://www.cosmicmind.com/gifs/white/card.gif)
* Download the complete [Card sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/Card).
* Learn more about [Card](http://cosmicmind.com/material/card).
## ImageCard
An ImageCard is an expansion of the base Card. The Toolbar overlays an image area that sits above the dynamic content area.
![Material Image](http://www.cosmicmind.com/gifs/white/image-card.gif)
* Download the complete [ImageCard sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/ImageCard).
* Learn more about [ImageCard](http://cosmicmind.com/material/imagecard).
* Learn how to make the ImageCard data-driven with [Graph's ImageCard sample](https://github.com/CosmicMind/Samples/tree/master/Graph/ImageCard).
## PresenterCard
The PresenterCard is a completely new card style. It allows for a primary presentation area that may be any UIView type in addition to the content area, Toolbar, and Bar components. The options for this card are endless.
![Material Image](http://www.cosmicmind.com/gifs/white/presenter-card.gif)
* Download the complete [PresenterCard sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/PresenterCard).
* Learn more about [PresenterCard](http://cosmicmind.com/material/presentercard).
## Menu
A Menu manages a collection of views. A new MenuItem type has been added that manages a title and button to improve UX and visual beauty.
![Material Image](http://www.cosmicmind.com/material/white/menu-controller.gif)
* Download the complete [Menu sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/MenuController).
* Learn more about [Menu](http://cosmicmind.com/material/menu).
## Toolbar
Toolbars are super flexible and add excellent control to your navigation flow. They manage a set of left and right views with auto aligning title and detail labels.
![Material Image](http://www.cosmicmind.com/gifs/white/toolbar-controller.gif)
* Download the complete [Toolbar sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/ToolbarController).
* Learn more about [Toolbar](http://cosmicmind.com/material/toolbar).
## SearchBar
A SearchBar is a powerful navigation tool that allows for user's input with an instant visual response. A set of left and right views may be added to expand functionality.
![Material Image](http://www.cosmicmind.com/gifs/shared/search-bar-controller.gif)
* Download the complete [SearchBar sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/SearchBarController).
* Learn more about [SearchBar](http://cosmicmind.com/material/searchbar).
* Learn how to make the SearchBar data-driven with [Graph's Search sample](https://github.com/CosmicMind/Samples/tree/master/Graph/Search).
## PageTabBar
A PageTabBar is a new component that links a customizable TabBar to a UIPageViewController making a powerful and visually pleasing component to have in any application. The TabBar can be aligned at the top or bottom of the view controller.
![Material Image](http://www.cosmicmind.com/material/white/page-tab-bar-controller.gif)
* Download the complete [PageTabBar sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/PageTabBarController).
* Learn more about [PageTabBar](http://cosmicmind.com/material/pagetabbar).
## NavigationController
A NavigationController is a specialized view controller that manages a hierarchy of content efficiently, making it easier for users to move within an application.
![Material Image](http://www.cosmicmind.com/gifs/white/navigation-controller.gif)
* Download the complete [NavigationController sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/NavigationController).
* Learn more about [NavigationController](http://cosmicmind.com/material/navigationcontroller).
## NavigationDrawer
A NavigationDrawer slides in from the left or right and contains the navigation destinations for your application.
![Material Image](http://www.cosmicmind.com/material/shared/navigation-drawer-controller.gif)
* Download the complete [NavigationDrawer sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/NavigationDrawerController).
* Learn more about [NavigationDrawer](http://cosmicmind.com/material/navigationdrawer).
## Snackbar
A Snackbar is a new component that is very simple in its behavior and very powerful in its message. It can be used application wide, or isolated to specific view controllers.
![Material Image](http://www.cosmicmind.com/material/white/snackbar-controller.gif)
* Download the complete [Snackbar sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/SnackbarController).
* Learn more about [Snackbar](http://cosmicmind.com/material/snackbar).
## PhotoLibrary
PhotoLibrary is a new component that simplifies the Photos framework and allows for beautiful photos found in the Photos application to be used within your application.
![Material Image](http://www.cosmicmind.com/material/shared/photolibrary-controller.png)
* Download the complete [PhotoLibrary sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/PhotoLibraryController).
* Learn more about [PhotoLibrary](http://cosmicmind.com/material/photolibrary).
## Capture
Capture is an API that simplifies iOS' AVFoundation framework. It allows for photos and video to easily be captured while managing all the complexities.
![Material Image](http://www.cosmicmind.com/material/shared/capture-controller.png)
* Download the complete [Capture sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/CaptureController).
* Learn more about [Capture](http://cosmicmind.com/material/capture).
## Sticker Sheet
To help template your project, checkout Material Sticker Sheet.
![MaterialStickerSheet](http://www.cosmicmind.com/MK/material_iso_1.png)
[Get Material Sticker Sheet](http://www.materialup.com/posts/material-design-sticker-sheets)
## Much More...
So much more inside. Enjoy!
## License
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.
<?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>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
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.
/*
* 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 <Foundation/Foundation.h>
//! Project version number for Motion.
FOUNDATION_EXPORT double MotionVersionNumber;
//! Project version string for Motion.
FOUNDATION_EXPORT const unsigned char MotionVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <Motion/PublicHeader.h>
/*
* 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
public struct Motion {
/**
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
public static func delay(_ time: TimeInterval, execute block: @escaping () -> Void) -> MotionDelayCancelBlock? {
func asyncAfter(completion: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.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.
*/
public static func cancel(delayed completion: MotionDelayCancelBlock) {
completion(true)
}
/**
Disables the default animations set on CALayers.
- Parameter animations: A callback that wraps the animations to disable.
*/
public static 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.
*/
public static 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()
}
/**
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.
*/
public static 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
}
/**
Executes an animation block with a given delay and duration.
- Parameter delay time: A CFTimeInterval.
- Parameter duration: An animation duration time.
- Parameter animations: An animation block.
- Parameter completion: A completion block that is executed once
the animations have completed.
*/
public static func animate(delay time: CFTimeInterval, duration: CFTimeInterval, animations: @escaping (() -> Void), completion: (() -> Void)? = nil) {
delay(time) {
animate(duration: duration, animations: animations, completion: completion)
}
}
}
/*
* 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 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 shadowPath
case opacity
case zPosition
case width = "bounds.size.width"
case height = "bounds.size.height"
case size = "bounds.size"
}
public enum MotionAnimation {
case delay(TimeInterval)
case timingFunction(MotionAnimationTimingFunction)
case duration(TimeInterval)
case custom(CABasicAnimation)
case backgroundColor(UIColor)
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 shadow(path: CGPath)
case fade(Double)
case zPosition(Int)
case width(CGFloat)
case height(CGFloat)
case size(width: CGFloat, height: CGFloat)
}
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 {
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)
}
}
}
/**
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(_ animation: CAAnimation, finished flag: Bool) {
guard let a = animation as? CAPropertyAnimation else {
if let a = (animation 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.
*/
open func motion(_ animations: [MotionAnimation]) {
motion(delay: 0, duration: 0.25, timingFunction: .easeInEaseOut, animations: animations)
}
/**
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.
*/
fileprivate func motion(delay: TimeInterval, duration: TimeInterval, timingFunction: MotionAnimationTimingFunction, animations: [MotionAnimation]) {
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(Motion.background(color: color))
case let .cornerRadius(radius):
a.append(Motion.corner(radius: radius))
case let .transform(transform):
a.append(Motion.transform(transform: transform))
case let .rotationAngle(angle):
let rotate = Motion.rotation(angle: angle)
a.append(rotate)
case let .rotationAngleX(angle):
a.append(Motion.rotationX(angle: angle))
case let .rotationAngleY(angle):
a.append(Motion.rotationY(angle: angle))
case let .rotationAngleZ(angle):
a.append(Motion.rotationZ(angle: angle))
case let .spin(rotations):
a.append(Motion.spin(rotations: rotations))
case let .spinX(rotations):
a.append(Motion.spinX(rotations: rotations))
case let .spinY(rotations):
a.append(Motion.spinY(rotations: rotations))
case let .spinZ(rotations):
a.append(Motion.spinZ(rotations: rotations))
case let .scale(to):
a.append(Motion.scale(to: to))
case let .scaleX(to):
a.append(Motion.scaleX(to: to))
case let .scaleY(to):
a.append(Motion.scaleY(to: to))
case let .scaleZ(to):
a.append(Motion.scaleZ(to: to))
case let .translate(x, y):
a.append(Motion.translate(to: CGPoint(x: x, y: y)))
case let .translateX(to):
a.append(Motion.translateX(to: to))
case let .translateY(to):
a.append(Motion.translateY(to: to))
case let .translateZ(to):
a.append(Motion.translateZ(to: to))
case .x(_), .y(_), .point(_, _):
let position = Motion.position(to: CGPoint(x: px, y: py))
a.append(position)
case let .position(x, y):
a.append(Motion.position(to: CGPoint(x: x, y: y)))
case let .shadow(path):
a.append(Motion.shadow(path: path))
case let .fade(opacity):
let fade = Motion.fade(opacity: opacity)
fade.fromValue = s.value(forKey: MotionAnimationKeyPath.opacity.rawValue) ?? NSNumber(floatLiteral: 1)
a.append(fade)
case let .zPosition(index):
let zPosition = Motion.zPosition(index: index)
zPosition.fromValue = s.value(forKey: MotionAnimationKeyPath.zPosition.rawValue) ?? NSNumber(integerLiteral: 0)
a.append(zPosition)
case .width(_), .height(_), .size(_, _):
a.append(Motion.size(CGSize(width: w, height: h)))
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)
}
}
}
@available(iOS 10, *)
extension CALayer: CAAnimationDelegate {}
extension UIView {
/// Computes the rotation of the view.
open var motionRotationAngle: CGFloat {
get {
return CGFloat(atan2f(Float(transform.b), Float(transform.a))) * 180 / CGFloat(M_PI)
}
set(value) {
transform = CGAffineTransform(rotationAngle: CGFloat(M_PI) * value / 180)
}
}
/// 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.
*/
open func motion(_ animations: [MotionAnimation]) {
layer.motion(animations)
}
}
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 Motion {
/**
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 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(M_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(M_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(M_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(M_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(M_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(M_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(M_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(M_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 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 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
}
}
/*
* 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
fileprivate var MotionTransitionInstanceKey: UInt8 = 0
fileprivate var MotionTransitionInstanceControllerKey: UInt8 = 0
fileprivate struct MotionTransitionInstance {
fileprivate var identifier: String
fileprivate var animations: [MotionAnimation]
}
fileprivate struct MotionTransitionInstanceController {
fileprivate var isEnabled: Bool
}
extension UIViewController: UIViewControllerTransitioningDelegate {
/// MotionTransitionInstanceController Reference.
fileprivate var motionTransition: MotionTransitionInstanceController {
get {
return AssociatedObject(base: self, key: &MotionTransitionInstanceControllerKey) {
return MotionTransitionInstanceController(isEnabled: false)
}
}
set(value) {
AssociateObject(base: self, key: &MotionTransitionInstanceControllerKey, value: value)
}
}
open var isMotionTransitionEnabled: Bool {
get {
return motionTransition.isEnabled
}
set(value) {
if value {
modalPresentationStyle = .custom
transitioningDelegate = self
}
motionTransition.isEnabled = value
}
}
}
extension UIViewController {
open func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return isMotionTransitionEnabled ? MotionTransition(isPresenting: true) : nil
}
open func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return isMotionTransitionEnabled ? MotionTransition() : nil
}
open func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return isMotionTransitionEnabled ? MotionTransitionPresentationController(presentedViewController: presented, presenting: presenting) : nil
}
}
extension UIViewController {
open func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return isMotionTransitionEnabled ? MotionTransition(isPresenting: operation == .push) : nil
}
}
extension UIViewController {
open func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return isMotionTransitionEnabled ? MotionTransition() : nil
}
}
extension UIView {
/// The global position of a view.
open var motionPosition: CGPoint {
return superview?.convert(position, to: nil) ?? position
}
/// MaterialTransitionItem Reference.
fileprivate var motionTransition: MotionTransitionInstance {
get {
return AssociatedObject(base: self, key: &MotionTransitionInstanceKey) {
return MotionTransitionInstance(identifier: "", animations: [])
}
}
set(value) {
AssociateObject(base: self, key: &MotionTransitionInstanceKey, value: value)
}
}
open var motionTransitionIdentifier: String {
get {
return motionTransition.identifier
}
set(value) {
motionTransition.identifier = value
}
}
open var motionTransitionAnimations: [MotionAnimation] {
get {
return motionTransition.animations
}
set(value) {
motionTransition.animations = value
}
}
open func motionTransitionSnapshot(afterUpdates: Bool) -> UIView {
isHidden = false
(self as? Pulseable)?.pulse.pulseLayer?.isHidden = true
let oldCornerRadius = cornerRadius
cornerRadius = 0
let oldBackgroundColor = backgroundColor
backgroundColor = .clear
let oldTransform = transform
transform = .identity
let v = snapshotView(afterScreenUpdates: afterUpdates)!
cornerRadius = oldCornerRadius
backgroundColor = oldBackgroundColor
transform = oldTransform
let contentView = v.subviews.first!
contentView.cornerRadius = cornerRadius
contentView.masksToBounds = true
v.motionTransitionIdentifier = motionTransitionIdentifier
v.position = motionPosition
v.bounds = bounds
v.cornerRadius = cornerRadius
v.zPosition = zPosition
v.opacity = opacity
v.isOpaque = isOpaque
v.anchorPoint = anchorPoint
v.masksToBounds = masksToBounds
v.borderColor = borderColor
v.borderWidth = borderWidth
v.shadowRadius = shadowRadius
v.shadowOpacity = shadowOpacity
v.shadowColor = shadowColor
v.shadowOffset = shadowOffset
v.contentMode = contentMode
v.transform = transform
v.backgroundColor = backgroundColor
isHidden = true
(self as? Pulseable)?.pulse.pulseLayer?.isHidden = false
return v
}
}
open class MotionTransitionPresentationController: UIPresentationController {
open override func presentationTransitionWillBegin() {
guard nil != containerView else {
return
}
presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in
print("Animating")
})
print("presentationTransitionWillBegin")
}
open override func presentationTransitionDidEnd(_ completed: Bool) {
print("presentationTransitionDidEnd")
}
open override func dismissalTransitionWillBegin() {
guard nil != containerView else {
return
}
presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in
print("Animating")
})
print("dismissalTransitionWillBegin")
}
open override func dismissalTransitionDidEnd(_ completed: Bool) {
print("dismissalTransitionDidEnd")
}
open override var frameOfPresentedViewInContainerView: CGRect {
return containerView?.bounds ?? .zero
}
}
open class MotionTransition: NSObject {
open var isPresenting: Bool
open fileprivate(set) var transitionPairs = [(UIView, UIView)]()
open var transitionSnapshot: UIView!
open let transitionBackgroundView = UIView()
open var toViewController: UIViewController!
open var fromViewController: UIViewController!
open var transitionContext: UIViewControllerContextTransitioning!
open var delay: TimeInterval = 0
open var duration: TimeInterval = 0.35
open var containerView: UIView!
open var transitionView = UIView()
public override init() {
isPresenting = false
super.init()
}
public init(isPresenting: Bool) {
self.isPresenting = isPresenting
super.init()
}
open var toView: UIView {
return toViewController.view
}
open var toSubviews: [UIView] {
return subviews(of: toView)
}
open var fromView: UIView {
return fromViewController.view
}
open var fromSubviews: [UIView] {
return subviews(of: fromView)
}
open func subviews(of view: UIView) -> [UIView] {
var views: [UIView] = []
subviews(of: view, views: &views)
return views
}
open func subviews(of view: UIView, views: inout [UIView]) {
for v in view.subviews {
if 0 < v.motionTransitionIdentifier.utf16.count {
views.append(v)
}
subviews(of: v, views: &views)
}
}
}
extension MotionTransition: UIViewControllerAnimatedTransitioning {
@objc(animateTransition:)
open func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
self.transitionContext = transitionContext
prepareToViewController()
prepareFromViewController()
prepareContainerView()
prepareTransitionSnapshot()
prepareTransitionPairs()
prepareTransitionView()
prepareTransitionBackgroundView()
prepareTransitionToView()
prepareTransitionAnimation()
}
@objc(transitionDuration:)
open func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return delay + duration
}
}
extension MotionTransition {
fileprivate func prepareToViewController() {
guard let v = transitionContext.viewController(forKey: .to) else {
return
}
toViewController = v
}
fileprivate func prepareFromViewController() {
guard let v = transitionContext.viewController(forKey: .from) else {
return
}
fromViewController = v
}
fileprivate func prepareContainerView() {
containerView = transitionContext.containerView
}
fileprivate func prepareTransitionSnapshot() {
transitionSnapshot = fromView.motionTransitionSnapshot(afterUpdates: true)
transitionSnapshot.frame = containerView.bounds
containerView.addSubview(transitionSnapshot)
}
fileprivate func prepareTransitionPairs() {
for from in fromSubviews {
guard 0 < from.motionTransitionIdentifier.utf16.count else {
continue
}
for to in toSubviews {
guard to.motionTransitionIdentifier == from.motionTransitionIdentifier else {
continue
}
transitionPairs.append((from, to))
}
}
}
fileprivate func prepareTransitionView() {
transitionView.frame = containerView.bounds
containerView.insertSubview(transitionView, belowSubview: transitionSnapshot)
}
fileprivate func prepareTransitionBackgroundView() {
transitionBackgroundView.backgroundColor = fromView.backgroundColor
transitionBackgroundView.frame = transitionView.bounds
transitionView.addSubview(transitionBackgroundView)
}
fileprivate func prepareTransitionToView() {
if isPresenting {
containerView.insertSubview(toView, belowSubview: transitionView)
}
toView.isHidden = false
toView.updateConstraints()
toView.setNeedsLayout()
toView.layoutIfNeeded()
}
fileprivate func prepareTransitionAnimation() {
addTransitionAnimations()
addBackgroundMotionAnimation()
cleanupAnimation()
hideFromView()
removeTransitionSnapshot()
}
}
extension MotionTransition {
fileprivate func addTransitionAnimations() {
for (from, to) in transitionPairs {
var snapshotAnimations = [CABasicAnimation]()
var snapshotChildAnimations = [CABasicAnimation]()
let sizeAnimation = Motion.size(to.bounds.size)
let cornerRadiusAnimation = Motion.corner(radius: to.cornerRadius)
snapshotAnimations.append(sizeAnimation)
snapshotAnimations.append(cornerRadiusAnimation)
snapshotAnimations.append(Motion.position(to: to.motionPosition))
snapshotAnimations.append(Motion.rotation(angle: to.motionRotationAngle))
snapshotAnimations.append(Motion.background(color: to.backgroundColor ?? .clear))
snapshotChildAnimations.append(cornerRadiusAnimation)
snapshotChildAnimations.append(sizeAnimation)
snapshotChildAnimations.append(Motion.position(x: to.bounds.width / 2, y: to.bounds.height / 2))
let d = motionDuration(animations: to.motionTransitionAnimations)
let snapshot = from.motionTransitionSnapshot(afterUpdates: true)
transitionView.addSubview(snapshot)
Motion.delay(motionDelay(animations: to.motionTransitionAnimations)) { [weak self, weak to] in
guard let s = self else {
return
}
guard let v = to else {
return
}
let tf = s.motionTimingFunction(animations: v.motionTransitionAnimations)
let snapshotGroup = Motion.animate(group: snapshotAnimations, duration: d)
snapshotGroup.fillMode = MotionAnimationFillModeToValue(mode: .forwards)
snapshotGroup.isRemovedOnCompletion = false
snapshotGroup.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: tf)
let snapshotChildGroup = Motion.animate(group: snapshotChildAnimations, duration: d)
snapshotChildGroup.fillMode = MotionAnimationFillModeToValue(mode: .forwards)
snapshotChildGroup.isRemovedOnCompletion = false
snapshotChildGroup.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: tf)
snapshot.animate(snapshotGroup)
snapshot.subviews.first!.animate(snapshotChildGroup)
}
}
}
fileprivate func addBackgroundMotionAnimation() {
transitionBackgroundView.motion(.backgroundColor(toView.backgroundColor ?? .clear), .duration(transitionDuration(using: transitionContext)))
}
}
extension MotionTransition {
fileprivate func motionDelay(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
}
fileprivate func motionDuration(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
}
fileprivate func motionTimingFunction(animations: [MotionAnimation]) -> MotionAnimationTimingFunction {
var t = MotionAnimationTimingFunction.easeInEaseOut
for a in animations {
switch a {
case let .timingFunction(timingFunction):
t = timingFunction
default:break
}
}
return t
}
}
extension MotionTransition {
fileprivate func cleanupAnimation() {
Motion.delay(transitionDuration(using: transitionContext)) { [weak self] in
guard let s = self else {
return
}
s.hideToSubviews()
s.clearTransitionView()
s.clearTransitionPairs()
s.completeTransition()
}
}
fileprivate func hideFromView() {
Motion.delay(delay) { [weak self] in
self?.fromView.isHidden = true
}
}
fileprivate func removeTransitionSnapshot() {
Motion.delay(delay) { [weak self] in
self?.transitionSnapshot.removeFromSuperview()
}
}
fileprivate func hideToSubviews() {
toSubviews.forEach {
$0.isHidden = false
}
}
fileprivate func clearTransitionPairs() {
transitionPairs.removeAll()
}
fileprivate func clearTransitionView() {
transitionView.removeFromSuperview()
transitionView.subviews.forEach {
$0.removeFromSuperview()
}
}
fileprivate func completeTransition() {
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
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