Commit eb9f06fa by Daniel Dahan Committed by GitHub

Merge branch 'development' into development

parents 58a5469c 9dc1261d
...@@ -16,3 +16,12 @@ profile ...@@ -16,3 +16,12 @@ profile
*.moved-aside *.moved-aside
*.playground *.playground
*.framework *.framework
Sources/Frameworks/Motion/.git*
Sources/Frameworks/Motion/.gitignore
Sources/Frameworks/Motion/.swift-version
Sources/Frameworks/Motion/CONTRIBUTING.md
Sources/Frameworks/Motion/Motion.podspec
Sources/Frameworks/Motion/Motion.xcodeproj/
Sources/Frameworks/Motion/Sources/Info.plist
Sources/Frameworks/Motion/Sources/Motion.h
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = 'Material' s.name = 'Material'
s.version = '2.6.3' s.version = '2.9.4'
s.license = 'BSD-3-Clause' s.license = 'BSD-3-Clause'
s.summary = 'An animation and graphics framework for Material Design in Swift.' s.summary = 'A Material Design library for creating beautiful applications.'
s.homepage = 'http://materialswift.com' s.homepage = 'http://materialswift.com'
s.social_media_url = 'https://www.facebook.com/cosmicmindcom' s.social_media_url = 'https://www.facebook.com/cosmicmindcom'
s.authors = { 'CosmicMind, Inc.' => 'support@cosmicmind.com' } s.authors = { 'CosmicMind, Inc.' => 'support@cosmicmind.com' }
...@@ -13,27 +13,11 @@ Pod::Spec.new do |s| ...@@ -13,27 +13,11 @@ Pod::Spec.new do |s|
s.subspec 'Core' do |s| s.subspec 'Core' do |s|
s.ios.deployment_target = '8.0' s.ios.deployment_target = '8.0'
s.ios.source_files = 'Sources/iOS/*.swift' s.ios.source_files = 'Sources/**/*.swift'
s.requires_arc = true s.requires_arc = true
s.resource_bundles = { s.resource_bundles = {
'com.cosmicmind.material.icons' => ['Sources/**/*.xcassets'], 'com.cosmicmind.material.icons' => ['Sources/**/*.xcassets'],
'com.cosmicmind.material.fonts' => ['Sources/**/*.ttf'] 'com.cosmicmind.material.fonts' => ['Sources/**/*.ttf']
} }
end end
s.subspec 'Capture' do |capture|
capture.source_files = 'Sources/iOS/Capture/*.swift'
capture.dependency 'Material/Core'
end
s.subspec 'Photos' do |photos|
photos.source_files = 'Sources/iOS/Photos/*.swift'
photos.dependency 'Material/Core'
end
s.subspec 'Reminders' do |reminders|
reminders.source_files = 'Sources/iOS/Reminders/*.swift'
reminders.dependency 'Material/Core'
end
end end
{
"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "9CF35BC641478FBD765F7442D4034ED362261E0A",
"DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
},
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
"9CF35BC641478FBD765F7442D4034ED362261E0A" : 9223372036854775807,
"0F3E254D46E5A5B90D1542854F510B7D145A9F31" : 9223372036854775807
},
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "0F44C283-777E-4E3F-9E10-FC52C3C270CE",
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
"9CF35BC641478FBD765F7442D4034ED362261E0A" : "Material\/",
"0F3E254D46E5A5B90D1542854F510B7D145A9F31" : "Material\/Sources\/Frameworks\/Motion\/"
},
"DVTSourceControlWorkspaceBlueprintNameKey" : "Material",
"DVTSourceControlWorkspaceBlueprintVersion" : 204,
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Material.xcodeproj",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/CosmicMind\/Motion.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "0F3E254D46E5A5B90D1542854F510B7D145A9F31"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/CosmicMind\/Material.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "9CF35BC641478FBD765F7442D4034ED362261E0A"
}
]
}
\ No newline at end of file
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "963832351B88DFD80015F710" BlueprintIdentifier = "963832351B88DFD80015F710"
BuildableName = "Material.framework" BuildableName = "Material.framework"
BlueprintName = "Material iOS" BlueprintName = "Material"
ReferencedContainer = "container:Material.xcodeproj"> ReferencedContainer = "container:Material.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildActionEntry> </BuildActionEntry>
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "963832351B88DFD80015F710" BlueprintIdentifier = "963832351B88DFD80015F710"
BuildableName = "Material.framework" BuildableName = "Material.framework"
BlueprintName = "Material iOS" BlueprintName = "Material"
ReferencedContainer = "container:Material.xcodeproj"> ReferencedContainer = "container:Material.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "963832351B88DFD80015F710" BlueprintIdentifier = "963832351B88DFD80015F710"
BuildableName = "Material.framework" BuildableName = "Material.framework"
BlueprintName = "Material iOS" BlueprintName = "Material"
ReferencedContainer = "container:Material.xcodeproj"> ReferencedContainer = "container:Material.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "963832351B88DFD80015F710" BlueprintIdentifier = "963832351B88DFD80015F710"
BuildableName = "Material.framework" BuildableName = "Material.framework"
BlueprintName = "Material iOS" BlueprintName = "Material"
ReferencedContainer = "container:Material.xcodeproj"> ReferencedContainer = "container:Material.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
......
The MIT License (MIT)
Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
![Motion Logo](http://www.cosmicmind.com/motion/logo/motion_logo.png)
# Motion
Welcome to **Motion,** a library used to create beautiful animations and transitions for views, layers, and view controllers.
## Photos Sample
Take a look at a sample [Photos](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/Photos) project to get started.
![Photos](http://www.cosmicmind.com/motion/projects/photos.gif)
* [Photos Sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/Photos)
### Who is Motion for?
Motion is designed for beginner to expert developers. For beginners, you will be exposed to very powerful APIs that would take time and experience to develop on your own, and experts will appreciate the time saved by using Motion.
### What you will learn
You will learn how to use Motion with a general introduction to fundamental concepts and easy to use code snippets.
# Transitions
Motion transitions a source view to a destination view using a linking identifier property named `motionIdentifier`.
| Match | Translate | Rotate | Arc | Scale |
|:---:|:---:|:---:|:---:|:---:|
| ![Match](http://www.cosmicmind.com/motion/transitions_identifier/match.gif) | ![Translate](http://www.cosmicmind.com/motion/transitions_identifier/translate.gif) | ![Rotate](http://www.cosmicmind.com/motion/transitions_identifier/rotate.gif) | ![Arc](http://www.cosmicmind.com/motion/transitions_identifier/arc.gif) | ![Scale](http://www.cosmicmind.com/motion/transitions_identifier/scale.gif) |
### Example Usage
An example of transitioning from one view controller to another with transitions:
#### View Controller 1
```swift
greyView.motionIdentifier = "foo"
orangeView.motionIdentifier = "bar"
```
#### View Controller 2
```swift
isMotionEnabled = true
greyView.motionIdentifier = "foo"
orangeView.motionIdentifier = "bar"
orangeView.transition(.translate(x: -100))
```
The above code snippet tells the source views in `view controller 1` to link to the destination views in `view controller 2` using the `motionIdentifier`. Animations may be added to views during a transition using the **transition** method. The *transition* method accepts MotionTransition structs that configure the view's animation.
* [MotionTransition API](https://cosmicmind.gitbooks.io/motion/content/motion_transition_api.html)
* [Code Samples](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/TransitionsWithIdentifier)
## UINavigationController, UITabBarController, and UIViewController Transitions
Motion offers default transitions that may be used by UINavigationControllers, UITabBarControllers, and presenting UIViewControllers.
| Push | Slide | ZoomSlide | Cover | Page | Fade | Zoom |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| ![Push](http://www.cosmicmind.com/motion/transitions/push.gif) | ![Slide](http://www.cosmicmind.com/motion/transitions/slide.gif)| ![Zoom Slide](http://www.cosmicmind.com/motion/transitions/zoom_slide.gif) | ![Cover](http://www.cosmicmind.com/motion/transitions/cover.gif) | ![Page](http://www.cosmicmind.com/motion/transitions/page_in.gif) | ![Fade](http://www.cosmicmind.com/motion/transitions/fade.gif) | ![Zoom](http://www.cosmicmind.com/motion/transitions/zoom.gif)|
### Example Usage
An example of transitioning from one view controller to another using a UINavigationController with a zoom transition:
#### UINavigationController
```swift
class AppNavigationController: UINavigationController {
open override func viewDidLoad() {
super.viewDidLoad()
isMotionEnabled = true
motionNavigationTransitionType = .zoom
}
}
```
To add an automatic reverse transition, use `autoReverse`.
```swift
motionNavigationTransitionType = .autoReverse(presenting: .zoom)
```
* [Code Samples](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/Transitions)
# Animations
Motion provides the building blocks necessary to create stunning animations without much effort. Motion's animation API will make maintenance a breeze and changes even easier. To create an animation, use the **animate** method of a view or layer and pass in a list of MotionAnimation structs. MotionAnimation structs are configurable values that describe how to animate a property or group of properties.
| Background Color | Corder Radius | Fade | Rotate | Size | Spring |
|:---:|:---:|:---:|:---:|:---:|:---:|
| ![Background Color](http://www.cosmicmind.com/motion/animations/background_color.gif) | ![Corner Radius](http://www.cosmicmind.com/motion/animations/corner_radius.gif) | ![Fade](http://www.cosmicmind.com/motion/animations/fade.gif) | ![Rotate](http://www.cosmicmind.com/motion/animations/rotate.gif) | ![Size](http://www.cosmicmind.com/motion/animations/size.gif) | ![Spring](http://www.cosmicmind.com/motion/animations/spring.gif) |
| Border Color & Border Width | Depth | Position | Scale | Spin | Translate |
|:---:|:---:|:---:|:---:|:---:|:---:|
|![Border Color & Border Width](http://www.cosmicmind.com/motion/animations/border_color.gif) | ![Depth](http://www.cosmicmind.com/motion/animations/depth.gif) | ![Position](http://www.cosmicmind.com/motion/animations/position.gif) | ![Scale](http://www.cosmicmind.com/motion/animations/scale.gif) | ![Spin](http://www.cosmicmind.com/motion/animations/spin.gif) | ![Translate](http://www.cosmicmind.com/motion/animations/translate.gif) |
### Example Usage
```swift
let box = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
box.backgroundColor = .blue
box.animate(.background(color: .red), .rotate(180), .delay(1))
```
In the above code example, a box view is created with a width of 100, height of 100, and an initial background color of blue. Following the general creation of the view, the _Motion animate_ method is passed _MotionAnimation structs_ that tell the view to animate to a red background color and rotate 180 degrees after a delay of 1 second. That's pretty much the general idea of creating animations.
* [MotionAnimation API](https://cosmicmind.gitbooks.io/motion/content/motion_animation_api.html)
* [Code Samples](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/Animations)
## Requirements
* iOS 8.0+
* 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.**
> - [Download Motion](https://github.com/CosmicMind/Motion/archive/master.zip)
Visit the [Installation](https://cosmicmind.gitbooks.io/motion/content/installation.html) page to learn how to install Motion using [CocoaPods](http://cocoapods.org) and [Carthage](https://github.com/Carthage/Carthage).
## Change Log
Motion is a growing project and will encounter changes throughout its development. It is recommended that the [Change Log](https://cosmicmind.gitbooks.io/motion/content/change_log.html) be reviewed prior to updating versions.
## License
The MIT License (MIT)
Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
public protocol MotionAnimator: class {
/// A reference to a MotionContext.
weak var context: MotionContext! { get set }
/// Cleans the contexts.
func clean()
/**
A function that determines if a view can be animated.
- Parameter view: A UIView.
- Parameter isAppearing: A boolean that determines whether the
view is appearing.
*/
func canAnimate(view: UIView, isAppearing: Bool) -> Bool
/**
Animates the fromViews to the toViews.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
- Returns: A TimeInterval.
*/
func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval
/**
Moves the view's animation to the given elapsed time.
- Parameter to elapsedTime: A TimeInterval.
*/
func seek(to elapsedTime: TimeInterval)
/**
Resumes the animation with a given elapsed time and
optional reversed boolean.
- Parameter at elapsedTime: A TimeInterval.
- Parameter isReversed: A boolean to reverse the animation
or not.
*/
func resume(at elapsedTime: TimeInterval, isReversed: Bool) -> TimeInterval
/**
Applies the given state to the given view.
- Parameter state: A MotionTransitionState.
- Parameter to view: A UIView.
*/
func apply(state: MotionTransitionState, to view: UIView)
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
internal class MotionAnimatorViewContext {
/// An optional reference to a MotionAnimator.
var animator: MotionAnimator?
/// A reference to the snapshot UIView.
var snapshot: UIView
/// Animation duration time.
var duration: TimeInterval = 0
/// The animation target state.
var targetState: MotionTransitionState
/// The computed current time of the snapshot layer.
var currentTime: TimeInterval {
return snapshot.layer.convertTime(CACurrentMediaTime(), from: nil)
}
/// A container view for the transition.
var container: UIView? {
return animator?.context.container
}
/**
An initializer.
- Parameter animator: A MotionAnimator.
- Parameter snapshot: A UIView.
- Parameter targetState: A MotionTransitionState.
*/
required init(animator: MotionAnimator, snapshot: UIView, targetState: MotionTransitionState) {
self.animator = animator
self.snapshot = snapshot
self.targetState = targetState
}
/// Cleans the context.
func clean() {
animator = nil
}
/**
A class function that determines if a view can be animated
to a given state.
- Parameter view: A UIView.
- Parameter state: A MotionTransitionState.
- Parameter isAppearing: A boolean that determines whether the
view is appearing.
*/
class func canAnimate(view: UIView, state: MotionTransitionState, isAppearing: Bool) -> Bool {
return false
}
/**
Resumes the animation with a given elapsed time and
optional reversed boolean.
- Parameter at elapsedTime: A TimeInterval.
- Parameter isReversed: A boolean to reverse the animation
or not.
*/
func resume(at elapsedTime: TimeInterval, isReversed: Bool) {}
/**
Moves the animation to the given elapsed time.
- Parameter to elapsedTime: A TimeInterval.
*/
func seek(to elapsedTime: TimeInterval) {}
/**
Applies the given state to the target state.
- Parameter state: A MotionTransitionState.
*/
func apply(state: MotionTransitionState) {}
/**
Starts the animations with an appearing boolean flag.
- Parameter isAppearing: A boolean value whether the view
is appearing or not.
*/
func startAnimations(isAppearing: Bool) {}
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
internal protocol MotionHasInsertOrder: class {
/// A boolean indicating whether to insert the toView first or not.
var insertToViewFirst: Bool { get set }
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
internal class MotionTransitionAnimator<T: MotionAnimatorViewContext>: MotionAnimator, MotionHasInsertOrder {
/// A reference to a MotionContext.
weak public var context: MotionContext!
/// An index of views to their corresponding animator context.
var viewToContexts = [UIView: T]()
var insertToViewFirst = false
}
extension MotionTransitionAnimator {
/**
Animates a given view.
- Parameter view: A UIView.
- Parameter isAppearing: A boolean that determines whether the
view is appearing.
*/
fileprivate func animate(view: UIView, isAppearing: Bool) {
let s = context.snapshotView(for: view)
let v = T(animator: self, snapshot: s, targetState: context[view]!)
viewToContexts[view] = v
v.startAnimations(isAppearing: isAppearing)
}
}
extension MotionTransitionAnimator {
/// Cleans the contexts.
func clean() {
for v in viewToContexts.values {
v.clean()
}
viewToContexts.removeAll()
insertToViewFirst = false
}
/**
A function that determines if a view can be animated.
- Parameter view: A UIView.
- Parameter isAppearing: A boolean that determines whether the
view is appearing.
*/
func canAnimate(view: UIView, isAppearing: Bool) -> Bool {
guard let state = context[view] else {
return false
}
return T.canAnimate(view: view, state: state, isAppearing: isAppearing)
}
/**
Animates the fromViews to the toViews.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
- Returns: A TimeInterval.
*/
func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval {
var duration: TimeInterval = 0
if insertToViewFirst {
for v in toViews {
animate(view: v, isAppearing: true)
}
for v in fromViews {
animate(view: v, isAppearing: false)
}
} else {
for v in fromViews {
animate(view: v, isAppearing: false)
}
for v in toViews {
animate(view: v, isAppearing: true)
}
}
for v in viewToContexts.values {
duration = max(duration, v.duration)
}
return duration
}
/**
Moves the view's animation to the given elapsed time.
- Parameter to elapsedTime: A TimeInterval.
*/
func seek(to elapsedTime: TimeInterval) {
for v in viewToContexts.values {
v.seek(to: elapsedTime)
}
}
/**
Resumes the animation with a given elapsed time and
optional reversed boolean.
- Parameter at elapsedTime: A TimeInterval.
- Parameter isReversed: A boolean to reverse the animation
or not.
*/
func resume(at elapsedTime: TimeInterval, isReversed: Bool) -> TimeInterval {
var duration: TimeInterval = 0
for (_, v) in viewToContexts {
v.resume(at: elapsedTime, isReversed: isReversed)
duration = max(duration, v.duration)
}
return duration
}
/**
Applies the given state to the given view.
- Parameter state: A MotionTransitionState.
- Parameter to view: A UIView.
*/
func apply(state: MotionTransitionState, to view: UIView) {
guard let v = viewToContexts[view] else {
return
}
v.apply(state: state)
}
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
@available(iOS 10, tvOS 10, *)
internal class MotionViewPropertyViewContext: MotionAnimatorViewContext {
/// A reference to the UIViewPropertyAnimator.
fileprivate var viewPropertyAnimator: UIViewPropertyAnimator?
override class func canAnimate(view: UIView, state: MotionTransitionState, isAppearing: Bool) -> Bool {
return view is UIVisualEffectView && nil != state.opacity
}
override func resume(at elapsedTime: TimeInterval, isReversed: Bool) {
viewPropertyAnimator?.finishAnimation(at: isReversed ? .start : .end)
}
override func seek(to elapsedTime: TimeInterval) {
viewPropertyAnimator?.pauseAnimation()
viewPropertyAnimator?.fractionComplete = CGFloat(elapsedTime / duration)
}
override func clean() {
super.clean()
viewPropertyAnimator?.stopAnimation(true)
viewPropertyAnimator = nil
}
override func startAnimations(isAppearing: Bool) {
guard let v = snapshot as? UIVisualEffectView else {
return
}
let appearedEffect = v.effect
let disappearedEffect = 0 == targetState.opacity ? nil : v.effect
v.effect = isAppearing ? disappearedEffect : appearedEffect
duration = targetState.duration!
viewPropertyAnimator = UIViewPropertyAnimator(duration: duration, curve: .easeInOut) {
v.effect = isAppearing ? appearedEffect : disappearedEffect
}
viewPropertyAnimator?.startAnimation()
}
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
internal extension Array {
/**
Retrieves the element at the given index if it exists.
- Parameter _ index: An Int.
- Returns: An optional Element value.
*/
func get(_ index: Int) -> Element? {
if index < count {
return self[index]
}
return nil
}
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
public extension CAMediaTimingFunction {
// Default
static let linear = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
static let easeIn = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
static let easeOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
static let easeInOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
// Material
static let standard = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 0.2, 1.0)
static let deceleration = CAMediaTimingFunction(controlPoints: 0.0, 0.0, 0.2, 1)
static let acceleration = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 1, 1)
static let sharp = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 0.6, 1)
// Easing.net
static let easeOutBack = CAMediaTimingFunction(controlPoints: 0.175, 0.885, 0.32, 1.75)
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import MetalKit
extension CGSize {
/// THe center point based on width and height.
var center: CGPoint {
return CGPoint(x: width / 2, y: height / 2)
}
/// Top left point based on the size.
var topLeft: CGPoint {
return .zero
}
/// Top right point based on the size.
var topRight: CGPoint {
return CGPoint(x: width, y: 0)
}
/// Bottom left point based on the size.
var bottomLeftPoint: CGPoint {
return CGPoint(x: 0, y: height)
}
/// Bottom right point based on the size.
var bottomRight: CGPoint {
return CGPoint(x: width, y: height)
}
/**
Retrieves the size based on a given CGAffineTransform.
- Parameter _ t: A CGAffineTransform.
- Returns: A CGSize.
*/
func transform(_ t: CGAffineTransform) -> CGSize {
return applying(t)
}
/**
Retrieves the size based on a given CATransform3D.
- Parameter _ t: A CGAffineTransform.
- Returns: A CGSize.
*/
func transform(_ t: CATransform3D) -> CGSize {
return applying(CATransform3DGetAffineTransform(t))
}
}
extension CGRect {
/// A center point based on the origin and size values.
var center: CGPoint {
return CGPoint(x: origin.x + size.width / 2, y: origin.y + size.height / 2)
}
/// The bounding box size based from from the frame's rect.
var bounds: CGRect {
return CGRect(origin: .zero, size: size)
}
/**
An initializer with a given point and size.
- Parameter center: A CGPoint.
- Parameter size: A CGSize.
*/
init(center: CGPoint, size: CGSize) {
self.init(x: center.x - size.width / 2, y: center.y - size.height / 2, width: size.width, height: size.height)
}
}
extension CGFloat {
/**
Calculates the limiting position to an area.
- Parameter _ a: A CGFloat.
- Parameter _ b: A CGFloat.
- Returns: A CGFloat.
*/
func clamp(_ a: CGFloat, _ b: CGFloat) -> CGFloat {
return self < a ? a : self > b ? b : self
}
}
extension CGPoint {
/**
Calculates a translation point based on the origin value.
- Parameter _ dx: A CGFloat.
- Parameter _ dy: A CGFloat.
- Returns: A CGPoint.
*/
func translate(_ dx: CGFloat, dy: CGFloat) -> CGPoint {
return CGPoint(x: x + dx, y: y + dy)
}
/**
Calculates a transform point based on a given CGAffineTransform.
- Parameter _ t: CGAffineTransform.
- Returns: A CGPoint.
*/
func transform(_ t: CGAffineTransform) -> CGPoint {
return applying(t)
}
/**
Calculates a transform point based on a given CATransform3D.
- Parameter _ t: CATransform3D.
- Returns: A CGPoint.
*/
func transform(_ t: CATransform3D) -> CGPoint {
return applying(CATransform3DGetAffineTransform(t))
}
/**
Calculates the distance between the CGPoint and given CGPoint.
- Parameter _ b: A CGPoint.
- Returns: A CGFloat.
*/
func distance(_ b: CGPoint) -> CGFloat {
return sqrt(pow(x - b.x, 2) + pow(y - b.y, 2))
}
}
/**
A handler for the (+) operator.
- Parameter left: A CGPoint.
- Parameter right: A CGPoint.
- Returns: A CGPoint.
*/
func +(left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
/**
A handler for the (-) operator.
- Parameter left: A CGPoint.
- Parameter right: A CGPoint.
- Returns: A CGPoint.
*/
func -(left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x - right.x, y: left.y - right.y)
}
/**
A handler for the (/) operator.
- Parameter left: A CGPoint.
- Parameter right: A CGFloat.
- Returns: A CGPoint.
*/
func /(left: CGPoint, right: CGFloat) -> CGPoint {
return CGPoint(x: left.x / right, y: left.y / right)
}
/**
A handler for the (/) operator.
- Parameter left: A CGPoint.
- Parameter right: A CGPoint.
- Returns: A CGPoint.
*/
func /(left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x / right.x, y: left.y / right.y)
}
/**
A handler for the (/) operator.
- Parameter left: A CGSize.
- Parameter right: A CGSize.
- Returns: A CGSize.
*/
func /(left: CGSize, right: CGSize) -> CGSize {
return CGSize(width: left.width / right.width, height: left.height / right.height)
}
/**
A handler for the (*) operator.
- Parameter left: A CGPoint.
- Parameter right: A CGFloat.
- Returns: A CGPoint.
*/
func *(left: CGPoint, right: CGFloat) -> CGPoint {
return CGPoint(x: left.x * right, y: left.y * right)
}
/**
A handler for the (*) operator.
- Parameter left: A CGPoint.
- Parameter right: A CGSize.
- Returns: A CGPoint.
*/
func *(left: CGPoint, right: CGSize) -> CGPoint {
return CGPoint(x: left.x * right.width, y: left.y * right.width)
}
/**
A handler for the (*) operator.
- Parameter left: A CGFloat.
- Parameter right: A CGPoint.
- Returns: A CGPoint.
*/
func *(left: CGFloat, right: CGPoint) -> CGPoint {
return right * left
}
/**
A handler for the (*) operator.
- Parameter left: A CGPoint.
- Parameter right: A CGPoint.
- Returns: A CGPoint.
*/
func *(left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x * right.x, y: left.y * right.y)
}
/**
A handler for the (*) prefix.
- Parameter left: A CGSize.
- Parameter right: A CGFloat.
- Returns: A CGSize.
*/
func *(left: CGSize, right: CGFloat) -> CGSize {
return CGSize(width: left.width * right, height: left.height * right)
}
/**
A handler for the (*) prefix.
- Parameter left: A CGSize.
- Parameter right: A CGSize.
- Returns: A CGSize.
*/
func *(left: CGSize, right: CGSize) -> CGSize {
return CGSize(width: left.width * right.width, height: left.height * right.width)
}
/**
A handler for the (==) operator.
- Parameter lhs: A CATransform3D.
- Parameter rhs: A CATransform3D.
- Returns: A Bool.
*/
func ==(lhs: CATransform3D, rhs: CATransform3D) -> Bool {
var lhs = lhs
var rhs = rhs
return memcmp(&lhs, &rhs, MemoryLayout<CATransform3D>.size) == 0
}
/**
A handler for the (!=) operator.
- Parameter lhs: A CATransform3D.
- Parameter rhs: A CATransform3D.
- Returns: A Bool.
*/
func !=(lhs: CATransform3D, rhs: CATransform3D) -> Bool {
return !(lhs == rhs)
}
/**
A handler for the (-) prefix.
- Parameter point: A CGPoint.
- Returns: A CGPoint.
*/
prefix func -(point: CGPoint) -> CGPoint {
return CGPoint.zero - point
}
/**
A handler for the (abs) function.
- Parameter _ p: A CGPoint.
- Returns: A CGPoint.
*/
func abs(_ p: CGPoint) -> CGPoint {
return CGPoint(x: abs(p.x), y: abs(p.y))
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
internal struct AssociatedObject {
/**
Gets the Obj-C reference for the instance object within the UIView extension.
- Parameter base: Base object.
- Parameter key: Memory key pointer.
- Parameter initializer: Object initializer.
- Returns: The associated reference for the initializer object.
*/
static func get<T: Any>(base: Any, key: UnsafePointer<UInt8>, initializer: () -> T) -> T {
if let v = objc_getAssociatedObject(base, key) as? T {
return v
}
let v = initializer()
objc_setAssociatedObject(base, key, v, .OBJC_ASSOCIATION_RETAIN)
return v
}
/**
Sets the Obj-C reference for the instance object within the UIView extension.
- Parameter base: Base object.
- Parameter key: Memory key pointer.
- Parameter value: The object instance to set for the associated object.
- Returns: The associated reference for the initializer object.
*/
static func set<T: Any>(base: Any, key: UnsafePointer<UInt8>, value: T) {
objc_setAssociatedObject(base, key, value, .OBJC_ASSOCIATION_RETAIN)
}
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
fileprivate let parameterRegex = "(?:\\-?\\d+(\\.?\\d+)?)|\\w+"
fileprivate let transitionsRegex = "(\\w+)(?:\\(([^\\)]*)\\))?"
internal extension NSObject {
/// Copies an object using NSKeyedArchiver.
func copyWithArchiver() -> Any? {
return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: self))!
}
}
internal extension UIColor {
/// A tuple of the rgba components.
var components: (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) {
var r: CGFloat = 0
var g: CGFloat = 0
var b: CGFloat = 0
var a: CGFloat = 0
getRed(&r, green: &g, blue: &b, alpha: &a)
return (r, g, b, a)
}
/// The alpha component value.
var alphaComponent: CGFloat {
return components.a
}
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
fileprivate var AssociatedInstanceKey: UInt8 = 0
fileprivate struct AssociatedInstance {
/// A boolean indicating whether Motion is enabled.
fileprivate var isEnabled: Bool
/// An optional reference to the motion identifier.
fileprivate var identifier: String?
/// An optional reference to the motion animations.
fileprivate var animations: [MotionAnimation]?
/// An optional reference to the motion transition animations.
fileprivate var transitions: [MotionTransition]?
/// An alpha value.
fileprivate var alpha: CGFloat?
}
fileprivate extension UIView {
/// AssociatedInstance reference.
fileprivate var associatedInstance: AssociatedInstance {
get {
return AssociatedObject.get(base: self, key: &AssociatedInstanceKey) {
return AssociatedInstance(isEnabled: true, identifier: nil, animations: nil, transitions: nil, alpha: 1)
}
}
set(value) {
AssociatedObject.set(base: self, key: &AssociatedInstanceKey, value: value)
}
}
}
public extension UIView {
/// A boolean that indicates whether motion is enabled.
@IBInspectable
var isMotionEnabled: Bool {
get {
return associatedInstance.isEnabled
}
set(value) {
associatedInstance.isEnabled = value
}
}
/// An identifier value used to connect views across UIViewControllers.
@IBInspectable
var motionIdentifier: String? {
get {
return associatedInstance.identifier
}
set(value) {
associatedInstance.identifier = value
}
}
/**
A function that accepts CAAnimation objects and executes them on the
view's backing layer.
- Parameter animations: A list of CAAnimations.
*/
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.
*/
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.
*/
func animate(_ animations: MotionAnimation...) {
layer.animate(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.
*/
func animate(_ animations: [MotionAnimation], completion: (() -> Void)? = nil) {
layer.animate(animations, completion: completion)
}
/**
A function that accepts a list of MotionTransition values.
- Parameter transitions: A list of MotionTransition values.
*/
func transition(_ transitions: MotionTransition...) {
transition(transitions)
}
/**
A function that accepts an Array of MotionTransition values.
- Parameter transitions: An Array of MotionTransition values.
*/
func transition(_ transitions: [MotionTransition]) {
motionTransitions = transitions
}
}
internal extension UIView {
/// The animations to run while in transition.
var motionTransitions: [MotionTransition]? {
get {
return associatedInstance.transitions
}
set(value) {
associatedInstance.transitions = value
}
}
/// The animations to run while in transition.
var motionAlpha: CGFloat? {
get {
return associatedInstance.alpha
}
set(value) {
associatedInstance.alpha = value
}
}
/// Computes the rotate of the view.
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.
var motionPosition: CGPoint {
return superview?.convert(layer.position, to: nil) ?? layer.position
}
/// The layer.transform of a view.
var motionTransform: CATransform3D {
get {
return layer.transform
}
set(value) {
layer.transform = value
}
}
/// Computes the scale X axis value of the view.
var motionScaleX: CGFloat {
return transform.a
}
/// Computes the scale Y axis value of the view.
var motionScaleY: CGFloat {
return transform.b
}
}
internal extension UIView {
/// Retrieves a single Array of UIViews that are in the view hierarchy.
var flattenedViewHierarchy: [UIView] {
guard isMotionEnabled else {
return []
}
if #available(iOS 9.0, *) {
return isHidden && (superview is UICollectionView || superview is UIStackView || self is UITableViewCell) ? [] : ([self] + subviews.flatMap { $0.flattenedViewHierarchy })
}
return isHidden && (superview is UICollectionView || self is UITableViewCell) ? [] : ([self] + subviews.flatMap { $0.flattenedViewHierarchy })
}
/**
Retrieves the optimized duration based on the given parameters.
- Parameter fromPosition: A CGPoint.
- Parameter toPosition: An optional CGPoint.
- Parameter size: An optional CGSize.
- Parameter transform: An optional CATransform3D.
- Returns: A TimeInterval.
*/
func optimizedDuration(fromPosition: CGPoint, toPosition: CGPoint?, size: CGSize?, transform: CATransform3D?) -> TimeInterval {
let toPos = toPosition ?? fromPosition
let fromSize = (layer.presentation() ?? layer).bounds.size
let toSize = size ?? fromSize
let fromTransform = (layer.presentation() ?? layer).transform
let toTransform = transform ?? fromTransform
let realFromPos = CGPoint.zero.transform(fromTransform) + fromPosition
let realToPos = CGPoint.zero.transform(toTransform) + toPos
let realFromSize = fromSize.transform(fromTransform)
let realToSize = toSize.transform(toTransform)
let movePoints = realFromPos.distance(realToPos) + realFromSize.bottomRight.distance(realToSize.bottomRight)
// duration is 0.2 @ 0 to 0.375 @ 500
return 0.208 + Double(movePoints.clamp(0, 500)) / 3000
}
/**
Takes a snapshot of a view usinag the UI graphics context.
- Returns: A UIView with an embedded UIImageView.
*/
func slowSnapshotView() -> UIView {
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let imageView = UIImageView(image: image)
imageView.frame = bounds
let snapshotView = UIView(frame: bounds)
snapshotView.addSubview(imageView)
return snapshotView
}
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
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
}
}
The MIT License (MIT)
Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
public struct MotionAnimationState {
/// A reference to the position.
public var position: CGPoint?
/// A reference to the size.
public var size: CGSize?
/// A reference to the transform.
public var transform: CATransform3D?
/// A reference to the spin tuple.
public var spin: (x: CGFloat, y: CGFloat, z: CGFloat)?
/// A reference to the opacity.
public var opacity: Double?
/// A reference to the cornerRadius.
public var cornerRadius: CGFloat?
/// A reference to the backgroundColor.
public var backgroundColor: CGColor?
/// A reference to the zPosition.
public var zPosition: CGFloat?
/// A reference to the borderWidth.
public var borderWidth: CGFloat?
/// A reference to the borderColor.
public var borderColor: CGColor?
/// A reference to the shadowColor.
public var shadowColor: CGColor?
/// A reference to the shadowOpacity.
public var shadowOpacity: Float?
/// A reference to the shadowOffset.
public var shadowOffset: CGSize?
/// A reference to the shadowRadius.
public var shadowRadius: CGFloat?
/// A reference to the shadowPath.
public var shadowPath: CGPath?
/// A reference to the spring animation settings.
public var spring: (CGFloat, CGFloat)?
/// A time delay on starting the animation.
public var delay: TimeInterval = 0
/// The duration of the animation.
public var duration: TimeInterval = 0.35
/// The timing function value of the animation.
public var timingFunction = CAMediaTimingFunction.easeInOut
/// Custom target states.
public var custom: [String: Any]?
/// Completion block.
public var completion: (() -> Void)?
/**
An initializer that accepts an Array of MotionAnimations.
- Parameter animations: An Array of MotionAnimations.
*/
init(animations: [MotionAnimation]) {
append(contentsOf: animations)
}
}
extension MotionAnimationState {
/**
Adds a MotionAnimation to the current state.
- Parameter _ element: A MotionAnimation.
*/
public mutating func append(_ element: MotionAnimation) {
element.apply(&self)
}
/**
Adds an Array of MotionAnimations to the current state.
- Parameter contentsOf elements: An Array of MotionAnimations.
*/
public mutating func append(contentsOf elements: [MotionAnimation]) {
for v in elements {
v.apply(&self)
}
}
/**
A subscript that returns a custom value for a specified key.
- Parameter key: A String.
- Returns: An optional Any value.
*/
public subscript(key: String) -> Any? {
get {
return custom?[key]
}
set(value) {
if nil == custom {
custom = [:]
}
custom![key] = value
}
}
}
extension MotionAnimationState: ExpressibleByArrayLiteral {
/**
An initializer implementing the ExpressibleByArrayLiteral protocol.
- Parameter arrayLiteral elements: A list of MotionAnimations.
*/
public init(arrayLiteral elements: MotionAnimation...) {
append(contentsOf: elements)
}
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
public enum MotionCoordinateSpace {
case global
case local
case sameParent
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
public class MotionIndependentController: MotionController {
/// An initializer.
public override init() {
super.init()
}
/**
Transitions source views to their corresponding destination view
within a given root view.
- Parameter rootView: A UIView.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
- Parameter completion: An optional callback.
*/
public func transition(rootView: UIView, fromViews: [UIView], toViews: [UIView], completion: ((Bool) -> Void)? = nil) {
transitionContainer = rootView
completionCallback = completion
prepareTransition()
prepareContext(fromViews: fromViews, toViews: toViews)
prepareTransitionPairs()
animate()
}
}
fileprivate extension MotionIndependentController {
/**
Prepares the context.
- Parameter fromViews: An Array of UIViews.
- PArameter toViews: An Array of UIViews.
*/
func prepareContext(fromViews: [UIView], toViews: [UIView]) {
context.defaultCoordinateSpace = .sameParent
context.set(fromViews: fromViews, toViews: toViews)
processContext()
}
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
open class MotionPlugin: NSObject, MotionPreprocessor, MotionAnimator {
weak public var context: MotionContext!
/**
Determines whether or not to receive `seekTo` callback on every frame.
Default is false.
When **requirePerFrameCallback** is **false**, the plugin needs to start its own animations inside `animate` & `resume`
The `seekTo` method is only being called during an interactive transition.
When **requirePerFrameCallback** is **true**, the plugin will receive `seekTo` callback on every animation frame. Hence it is possible for the plugin to do per-frame animations without implementing `animate` & `resume`
*/
open var requirePerFrameCallback = false
public override required init() {}
/**
Called before any animation.
Override this method when you want to preprocess transitions for views
- Parameters:
- context: object holding all parsed and changed transitions,
- fromViews: A flattened list of all views from source ViewController
- toViews: A flattened list of all views from destination ViewController
To check a view's transitions:
context[view]
context[view, "transitionName"]
To set a view's transitions:
context[view] = [("transition1", ["parameter1"]), ("transition2", [])]
context[view, "transition1"] = ["parameter1", "parameter2"]
*/
open func process(fromViews: [UIView], toViews: [UIView]) {}
/**
- Returns: return true if the plugin can handle animating the view.
- Parameters:
- context: object holding all parsed and changed transitions,
- view: the view to check whether or not the plugin can handle the animation
- isAppearing: true if the view is isAppearing(i.e. a view in destination ViewController)
If return true, Motion won't animate and won't let any other plugins animate this view.
The view will also be hidden automatically during the animation.
*/
open func canAnimate(view: UIView, isAppearing: Bool) -> Bool { return false }
/**
Perform the animation.
Note: views in `fromViews` & `toViews` are hidden already. Unhide then if you need to take snapshots.
- Parameters:
- context: object holding all parsed and changed transitions,
- fromViews: A flattened list of all views from source ViewController (filtered by `canAnimate`)
- toViews: A flattened list of all views from destination ViewController (filtered by `canAnimate`)
- Returns: The duration needed to complete the animation
*/
open func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval { return 0 }
/**
Called when all animations are completed.
Should perform cleanup and release any reference
*/
open func clean() {}
/**
For supporting interactive animation only.
This method is called when an interactive animation is in place
The plugin should pause the animation, and seek to the given progress
- Parameters:
- elapsedTime: time of the animation to seek to.
*/
open func seek(to elapsedTime: TimeInterval) {}
/**
For supporting interactive animation only.
This method is called when an interactive animation is ended
The plugin should resume the animation.
- Parameters:
- elapsedTime: will be the same value since last `seekTo`
- reverse: a boolean value indicating whether or not the animation should reverse
*/
open func resume(at elapsedTime: TimeInterval, isReversed: Bool) -> TimeInterval { return 0 }
/**
For supporting interactive animation only.
This method is called when user wants to override animation transitions during an interactive animation
- Parameters:
- state: the target state to override
- view: the view to override
*/
open func apply(state: MotionTransitionState, to view: UIView) {}
}
// methods for enable/disable the current plugin
extension MotionPlugin {
public static var isEnabled: Bool {
get {
return Motion.isEnabled(plugin: self)
}
set {
if newValue {
enable()
} else {
disable()
}
}
}
public static func enable() {
Motion.enable(plugin: self)
}
public static func disable() {
Motion.disable(plugin: self)
}
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
public enum MotionSnapshotType {
/**
This setting will optimize for different types of views.
For custom views or views with masking, .optimizedDefault might
create snapshots that appear differently than the actual view.
In that case, use .normal or .slowRender to disable the optimization.
*/
case optimized
/// snapshotView(afterScreenUpdates:)
case normal
/// layer.render(in: currentContext)
case layerRender
/**
This setting will not create a snapshot. It will animate the view directly.
This will mess up the view hierarchy, therefore, view controllers have to rebuild
their view structure after the transition finishes.
*/
case noSnapshot
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import Foundation
public protocol MotionTransitionObserver {
/**
Executed when the elapsed time changes during a transition.
- Parameter transitionObserver: A MotionTransitionObserver.
- Parameter didUpdateWith elapsedTime: A TimeInterval.
*/
func motion(transitionObserver: MotionTransitionObserver, didUpdateWith elapsedTime: TimeInterval)
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
internal class MotionTransitionStateWrapper {
/// A reference to a MotionTransitionState.
internal var state: MotionTransitionState
/**
An initializer that accepts a given MotionTransitionState.
- Parameter state: A MotionTransitionState.
*/
internal init(state: MotionTransitionState) {
self.state = state
}
}
public struct MotionTransitionState {
/// The initial state that the transition will start at.
internal var beginState: MotionTransitionStateWrapper?
/// The start state if there is a match in the desition view controller.
public var beginStateIfMatched: [MotionTransition]?
/// A reference to the position.
public var position: CGPoint?
/// A reference to the size.
public var size: CGSize?
/// A reference to the transform.
public var transform: CATransform3D?
/// A reference to the opacity.
public var opacity: Double?
/// A reference to the cornerRadius.
public var cornerRadius: CGFloat?
/// A reference to the backgroundColor.
public var backgroundColor: CGColor?
/// A reference to the zPosition.
public var zPosition: CGFloat?
/// A reference to the contentsRect.
public var contentsRect: CGRect?
/// A reference to the contentsScale.
public var contentsScale: CGFloat?
/// A reference to the borderWidth.
public var borderWidth: CGFloat?
/// A reference to the borderColor.
public var borderColor: CGColor?
/// A reference to the shadowColor.
public var shadowColor: CGColor?
/// A reference to the shadowOpacity.
public var shadowOpacity: Float?
/// A reference to the shadowOffset.
public var shadowOffset: CGSize?
/// A reference to the shadowRadius.
public var shadowRadius: CGFloat?
/// A reference to the shadowPath.
public var shadowPath: CGPath?
/// A boolean for the masksToBounds state.
public var masksToBounds: Bool?
/// A boolean indicating whether to display a shadow or not.
public var displayShadow = true
/// A reference to the overlay settings.
public var overlay: (color: CGColor, opacity: CGFloat)?
/// A reference to the spring animation settings.
public var spring: (CGFloat, CGFloat)?
/// A time delay on starting the animation.
public var delay: TimeInterval = 0
/// The duration of the animation.
public var duration: TimeInterval?
/// The timing function value of the animation.
public var timingFunction: CAMediaTimingFunction?
/// The arc curve value.
public var arc: CGFloat?
/// The identifier value to match source and destination views.
public var motionIdentifier: String?
/// The cascading animation settings.
public var cascade: (TimeInterval, CascadeDirection, Bool)?
/**
A boolean indicating whether to ignore the subview transition
animations or not.
*/
public var ignoreSubviewTransitions: Bool?
/// The coordinate space to transition views within.
public var coordinateSpace: MotionCoordinateSpace?
/// Change the size of a view based on a scale factor.
public var useScaleBasedSizeChange: Bool?
/// The type of snapshot to use.
public var snapshotType: MotionSnapshotType?
/// Do not fade the view when transitioning.
public var nonFade = false
/// Force an animation.
public var forceAnimate = false
/// Custom target states.
public var custom: [String: Any]?
/**
An initializer that accepts an Array of MotionTransitions.
- Parameter transitions: An Array of MotionTransitions.
*/
init(transitions: [MotionTransition]) {
append(contentsOf: transitions)
}
}
extension MotionTransitionState {
/**
Adds a MotionTransition to the current state.
- Parameter _ element: A MotionTransition.
*/
public mutating func append(_ element: MotionTransition) {
element.apply(&self)
}
/**
Adds an Array of MotionTransitions to the current state.
- Parameter contentsOf elements: An Array of MotionTransitions.
*/
public mutating func append(contentsOf elements: [MotionTransition]) {
for v in elements {
v.apply(&self)
}
}
/**
A subscript that returns a custom value for a specified key.
- Parameter key: A String.
- Returns: An optional Any value.
*/
public subscript(key: String) -> Any? {
get {
return custom?[key]
}
set(value) {
if nil == custom {
custom = [:]
}
custom![key] = value
}
}
}
extension MotionTransitionState: ExpressibleByArrayLiteral {
/**
An initializer implementing the ExpressibleByArrayLiteral protocol.
- Parameter arrayLiteral elements: A list of MotionTransitions.
*/
public init(arrayLiteral elements: MotionTransition...) {
append(contentsOf: elements)
}
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
public enum CascadeDirection {
case topToBottom
case bottomToTop
case leftToRight
case rightToLeft
case radial(center:CGPoint)
case inverseRadial(center:CGPoint)
/// Based on the cascade direction a comparator is set.
var comparator: (UIView, UIView) -> Bool {
switch self {
case .topToBottom:
return { return $0.frame.minY < $1.frame.minY }
case .bottomToTop:
return { return $0.frame.maxY == $1.frame.maxY ? $0.frame.maxX > $1.frame.maxX : $0.frame.maxY > $1.frame.maxY }
case .leftToRight:
return { return $0.frame.minX < $1.frame.minX }
case .rightToLeft:
return { return $0.frame.maxX > $1.frame.maxX }
case .radial(let center):
return { return $0.center.distance(center) < $1.center.distance(center) }
case .inverseRadial(let center):
return { return $0.center.distance(center) > $1.center.distance(center) }
}
}
}
class CascadePreprocessor: MotionPreprocessor {
/// A reference to a MotionContext.
weak var context: MotionContext!
/**
Processes the transitionary views.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
func process(fromViews: [UIView], toViews: [UIView]) {
process(views: fromViews)
process(views: toViews)
}
/**
Process an Array of views for the cascade animation.
- Parameter views: An Array of UIViews.
*/
func process(views: [UIView]) {
for v in views {
guard let (deltaTime, direction, delayMatchedViews) = context[v]?.cascade else {
continue
}
let parentView = v is UITableView ? v.subviews.get(0) ?? v : v
let sortedSubviews = parentView.subviews.sorted(by: direction.comparator)
let initialDelay = context[v]!.delay
let finalDelay = TimeInterval(sortedSubviews.count) * deltaTime + initialDelay
for (i, subview) in sortedSubviews.enumerated() {
let delay = TimeInterval(i) * deltaTime + initialDelay
func applyDelay(view: UIView) {
if context.transitionPairedView(for: view) == nil {
context[view]?.delay = delay
} else if delayMatchedViews, let paired = context.transitionPairedView(for: view) {
context[view]?.delay = finalDelay
context[paired]?.delay = finalDelay
}
for subview in view.subviews {
applyDelay(view: subview)
}
}
applyDelay(view: subview)
}
}
}
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
class DurationPreprocessor: MotionPreprocessor {
/// A reference to a MotionContext.
weak var context: MotionContext!
/**
Processes the transitionary views.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
func process(fromViews: [UIView], toViews: [UIView]) {
var maxDuration: TimeInterval = 0
maxDuration = applyOptimizedDurationIfNoDuration(views:fromViews)
maxDuration = max(maxDuration, applyOptimizedDurationIfNoDuration(views:toViews))
setDurationForInfiniteDuration(views: fromViews, duration: maxDuration)
setDurationForInfiniteDuration(views: toViews, duration: maxDuration)
}
/**
Retrieves the optimized duration for a given UIView.
- Parameter for view: A UIView.
- Returns: A TimeInterval.
*/
func optimizedDuration(for view: UIView) -> TimeInterval {
let v = context[view]!
return view.optimizedDuration(fromPosition: context.container.convert(view.layer.position, from: view.superview),
toPosition: v.position,
size: v.size,
transform: v.transform)
}
/**
Applies the optimized duration for an Array of UIViews.
- Parameter views: An Array of UIViews.
- Returns: A TimeInterval.
*/
func applyOptimizedDurationIfNoDuration(views: [UIView]) -> TimeInterval {
var d: TimeInterval = 0
for v in views where nil != context[v] {
if nil == context[v]?.duration {
context[v]!.duration = optimizedDuration(for: v)
}
d = .infinity == context[v]!.duration! ?
max(d, optimizedDuration(for: v)) :
max(d, context[v]!.duration!)
}
return d
}
/**
Sets the duration if the duration of a transition is set to `.infinity`.
- Parameter views: An Array of UIViews.
- Parameter duration: A TimeInterval.
*/
func setDurationForInfiniteDuration(views: [UIView], duration: TimeInterval) {
for v in views where .infinity == context[v]?.duration {
context[v]!.duration = duration
}
}
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
class IgnoreSubviewTransitionsPreprocessor: MotionPreprocessor {
/// A reference to a MotionContext.
weak var context: MotionContext!
/**
Processes the transitionary views.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
func process(fromViews: [UIView], toViews: [UIView]) {
process(views:fromViews)
process(views:toViews)
}
/**
Process an Array of views for the cascade animation.
- Parameter views: An Array of UIViews.
*/
func process(views: [UIView]) {
for v in views {
guard let recursive = context[v]?.ignoreSubviewTransitions else {
continue
}
let parentView = v is UITableView ? v.subviews.get(0) ?? v : v
guard recursive else {
for subview in parentView.subviews {
context[subview] = nil
}
continue
}
cleanSubviewTransitions(for: parentView)
}
}
}
extension IgnoreSubviewTransitionsPreprocessor {
/**
Clears the transition for a given view's subviews.
- Parameter for view: A UIView.
*/
fileprivate func cleanSubviewTransitions(for view: UIView) {
for v in view.subviews {
context[v] = nil
cleanSubviewTransitions(for: v)
}
}
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
class MatchPreprocessor: MotionPreprocessor {
/// A reference to a MotionContext.
weak var context: MotionContext!
/**
Processes the transitionary views.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
func process(fromViews: [UIView], toViews: [UIView]) {
for tv in toViews {
guard let i = tv.motionIdentifier, let fv = context.sourceView(for: i) else { continue }
var tvState = context[tv] ?? MotionTransitionState()
var fvState = context[fv] ?? MotionTransitionState()
if let v = tvState.beginStateIfMatched {
tvState.append(.beginWith(transitions: v))
}
if let v = fvState.beginStateIfMatched {
fvState.append(.beginWith(transitions: v))
}
tvState.motionIdentifier = i
tvState.opacity = 0
fvState.motionIdentifier = i
fvState.arc = tvState.arc
fvState.duration = tvState.duration
fvState.timingFunction = tvState.timingFunction
fvState.delay = tvState.delay
fvState.spring = tvState.spring
let forceNonFade = tvState.nonFade || fvState.nonFade
let isNonOpaque = !fv.isOpaque || fv.alpha < 1 || !tv.isOpaque || tv.alpha < 1
if !forceNonFade && isNonOpaque {
// Cross fade if from/toViews are not opaque.
fvState.opacity = 0
} else {
// No cross fade in this case, fromView is always displayed during the transition.
fvState.opacity = nil
/**
We dont want two shadows showing up. Therefore we disable toView's
shadow when fromView is able to display its shadow.
*/
if !fv.layer.masksToBounds && fvState.displayShadow {
tvState.displayShadow = false
}
}
context[tv] = tvState
context[fv] = fvState
}
}
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
public protocol MotionPreprocessor: class {
/// A reference to a MotionContext.
weak var context: MotionContext! { get set }
/**
Processes the transitionary views.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
func process(fromViews: [UIView], toViews: [UIView])
}
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
class SourcePreprocessor: MotionPreprocessor {
/// A reference to a MotionContext.
weak var context: MotionContext!
/**
Processes the transitionary views.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
func process(fromViews: [UIView], toViews: [UIView]) {
for fv in fromViews {
guard let i = context[fv]?.motionIdentifier, let tv = context.destinationView(for: i) else {
continue
}
prepare(view: fv, for: tv)
}
for tv in toViews {
guard let i = context[tv]?.motionIdentifier, let fv = context.sourceView(for: i) else {
continue
}
prepare(view: tv, for: fv)
}
}
/**
Prepares a given view for a target view.
- Parameter view: A UIView.
- Parameter for targetView: A UIView.
*/
func prepare(view: UIView, for targetView: UIView) {
let targetPos = context.container.convert(targetView.layer.position, from: targetView.superview!)
var state = context[view]!
/**
Use global coordinate space since over target position is
converted from the global container
*/
state.coordinateSpace = .global
// Remove incompatible options.
state.position = targetPos
state.transform = nil
state.size = nil
state.cornerRadius = nil
if view.bounds.size != targetView.bounds.size {
state.size = targetView.bounds.size
}
if view.layer.cornerRadius != targetView.layer.cornerRadius {
state.cornerRadius = targetView.layer.cornerRadius
}
if view.layer.transform != targetView.layer.transform {
state.transform = targetView.layer.transform
}
if view.layer.shadowColor != targetView.layer.shadowColor {
state.shadowColor = targetView.layer.shadowColor
}
if view.layer.shadowOpacity != targetView.layer.shadowOpacity {
state.shadowOpacity = targetView.layer.shadowOpacity
}
if view.layer.shadowOffset != targetView.layer.shadowOffset {
state.shadowOffset = targetView.layer.shadowOffset
}
if view.layer.shadowRadius != targetView.layer.shadowRadius {
state.shadowRadius = targetView.layer.shadowRadius
}
if view.layer.shadowPath != targetView.layer.shadowPath {
state.shadowPath = targetView.layer.shadowPath
}
if view.layer.contentsRect != targetView.layer.contentsRect {
state.contentsRect = targetView.layer.contentsRect
}
if view.layer.contentsScale != targetView.layer.contentsScale {
state.contentsScale = targetView.layer.contentsScale
}
context[view] = state
}
}
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>FMWK</string> <string>FMWK</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2.6.3</string> <string>2.9.4</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
......
...@@ -31,17 +31,22 @@ ...@@ -31,17 +31,22 @@
import UIKit import UIKit
public struct Application { public struct Application {
/// A reference to the main UIWindow. /// An optional reference to the main UIWindow.
public static var keyWindow: UIWindow? { public static var keyWindow: UIWindow? {
return UIApplication.shared.keyWindow return UIApplication.shared.keyWindow
} }
/// A Boolean indicating if the device is in Landscape mode. /// An optional reference to the top most view controller.
public static var rootViewController: UIViewController? {
return keyWindow?.rootViewController
}
/// A boolean indicating if the device is in Landscape mode.
public static var isLandscape: Bool { public static var isLandscape: Bool {
return UIApplication.shared.statusBarOrientation.isLandscape return UIApplication.shared.statusBarOrientation.isLandscape
} }
/// A Boolean indicating if the device is in Portrait mode. /// A boolean indicating if the device is in Portrait mode.
public static var isPortrait: Bool { public static var isPortrait: Bool {
return !isLandscape return !isLandscape
} }
......
...@@ -105,7 +105,7 @@ open class Bar: View { ...@@ -105,7 +105,7 @@ open class Bar: View {
} }
/// ContentView that holds the any desired subviews. /// ContentView that holds the any desired subviews.
open let contentView = UIScrollView() open let contentView = UIView()
/// Left side UIViews. /// Left side UIViews.
open var leftViews: [UIView] { open var leftViews: [UIView] {
...@@ -256,22 +256,16 @@ open class Bar: View { ...@@ -256,22 +256,16 @@ open class Bar: View {
grid.commit() grid.commit()
contentView.grid.commit() contentView.grid.commit()
divider.reload() layoutDivider()
} }
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/
open override func prepare() { open override func prepare() {
super.prepare() super.prepare()
heightPreset = .normal heightPreset = .normal
autoresizingMask = .flexibleWidth autoresizingMask = .flexibleWidth
interimSpacePreset = .interimSpace3 interimSpacePreset = .interimSpace3
contentEdgeInsetsPreset = .square1 contentEdgeInsetsPreset = .square1
prepareContentView() prepareContentView()
} }
} }
...@@ -279,10 +273,6 @@ open class Bar: View { ...@@ -279,10 +273,6 @@ open class Bar: View {
extension Bar { extension Bar {
/// Prepares the contentView. /// Prepares the contentView.
fileprivate func prepareContentView() { fileprivate func prepareContentView() {
contentView.bounces = false
contentView.isPagingEnabled = true
contentView.showsVerticalScrollIndicator = false
contentView.showsHorizontalScrollIndicator = false
contentView.contentScaleFactor = Screen.scale contentView.contentScaleFactor = Screen.scale
} }
} }
...@@ -42,11 +42,10 @@ public enum BorderWidthPreset: Int { ...@@ -42,11 +42,10 @@ public enum BorderWidthPreset: Int {
case border7 case border7
case border8 case border8
case border9 case border9
}
/// Converts the BorderWidthPreset enum to a CGFloat value. /// A CGFloat representation of the border width preset.
public func BorderWidthPresetToValue(preset: BorderWidthPreset) -> CGFloat { public var cgFloatValue: CGFloat {
switch preset { switch self {
case .none: case .none:
return 0 return 0
case .border1: case .border1:
...@@ -68,4 +67,5 @@ public func BorderWidthPresetToValue(preset: BorderWidthPreset) -> CGFloat { ...@@ -68,4 +67,5 @@ public func BorderWidthPresetToValue(preset: BorderWidthPreset) -> CGFloat {
case .border9: case .border9:
return 8 return 8
} }
}
} }
...@@ -30,6 +30,24 @@ ...@@ -30,6 +30,24 @@
import UIKit import UIKit
extension UIViewController {
/**
A convenience property that provides access to the BottomNavigationController.
This is the recommended method of accessing the BottomNavigationController
through child UIViewControllers.
*/
public var bottomNavigationController: BottomNavigationController? {
var viewController: UIViewController? = self
while nil != viewController {
if viewController is BottomNavigationController {
return viewController as? BottomNavigationController
}
viewController = viewController?.parent
}
return nil
}
}
open class BottomNavigationController: UITabBarController { open class BottomNavigationController: UITabBarController {
/** /**
An initializer that initializes the object with a NSCoder object. An initializer that initializes the object with a NSCoder object.
...@@ -101,7 +119,7 @@ open class BottomNavigationController: UITabBarController { ...@@ -101,7 +119,7 @@ open class BottomNavigationController: UITabBarController {
} }
} }
tabBar.divider.reload() tabBar.layoutDivider()
} }
/** /**
...@@ -113,16 +131,21 @@ open class BottomNavigationController: UITabBarController { ...@@ -113,16 +131,21 @@ open class BottomNavigationController: UITabBarController {
*/ */
open func prepare() { open func prepare() {
view.clipsToBounds = true view.clipsToBounds = true
view.contentScaleFactor = Screen.scale
view.backgroundColor = .white view.backgroundColor = .white
view.contentScaleFactor = Screen.scale
prepareTabBar() prepareTabBar()
} }
}
fileprivate extension BottomNavigationController {
/// Prepares the tabBar. /// Prepares the tabBar.
private func prepareTabBar() { func prepareTabBar() {
tabBar.isTranslucent = false
tabBar.heightPreset = .normal tabBar.heightPreset = .normal
tabBar.depthPreset = .depth1 tabBar.dividerColor = Color.grey.lighten3
tabBar.dividerAlignment = .top tabBar.dividerAlignment = .top
let image = UIImage() let image = UIImage()
tabBar.shadowImage = image tabBar.shadowImage = image
tabBar.backgroundImage = image tabBar.backgroundImage = image
......
/*
* 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
extension UITabBarItem {
/// Sets the color of the title color for a state.
public func setTitleColor(color: UIColor, forState state: UIControlState) {
setTitleTextAttributes([NSForegroundColorAttributeName: color], for: state)
}
}
open class BottomTabBar: UITabBar {
open override var intrinsicContentSize: CGSize {
return CGSize(width: width, height: height)
}
/// Automatically aligns the BottomNavigationBar to the superview.
@IBInspectable
open var isAlignedToParentAutomatically = true
/// A property that accesses the backing layer's background
@IBInspectable
open override var backgroundColor: UIColor? {
didSet {
barTintColor = backgroundColor
}
}
/**
An initializer that initializes the object with a CGRect object.
If AutoLayout is used, it is better to initilize the instance
using the init() initializer.
- Parameter frame: A CGRect instance.
*/
public override init(frame: CGRect) {
super.init(frame: frame)
prepare()
}
/// A convenience initializer.
public convenience init() {
self.init(frame: .zero)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
prepare()
}
open override func layoutSubviews() {
super.layoutSubviews()
layoutShape()
layoutShadowPath()
if let v = items {
for item in v {
if .phone == Device.userInterfaceIdiom {
if nil == item.title {
let inset: CGFloat = 7
item.imageInsets = UIEdgeInsetsMake(inset, 0, -inset, 0)
} else {
let inset: CGFloat = 6
item.titlePositionAdjustment.vertical = -inset
}
} else if nil == item.title {
let inset: CGFloat = 9
item.imageInsets = UIEdgeInsetsMake(inset, 0, -inset, 0)
} else {
let inset: CGFloat = 3
item.imageInsets = UIEdgeInsetsMake(inset, 0, -inset, 0)
item.titlePositionAdjustment.vertical = -inset
}
}
}
divider.reload()
}
open override func didMoveToSuperview() {
super.didMoveToSuperview()
if isAlignedToParentAutomatically {
if let v = superview {
v.layout(self).bottom().horizontally()
}
}
}
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/
public func prepare() {
heightPreset = .normal
depthPreset = .depth1
dividerAlignment = .top
contentScaleFactor = Screen.scale
backgroundColor = .white
let image = UIImage.image(with: .clear, size: CGSize(width: 1, height: 1))
shadowImage = image
backgroundImage = image
}
}
/*
* 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
import AVFoundation
extension UIViewController {
/**
A convenience property that provides access to the CaptureController.
This is the recommended method of accessing the CaptureController
through child UIViewControllers.
*/
public var captureController: CaptureController? {
var viewController: UIViewController? = self
while nil != viewController {
if viewController is CaptureController {
return viewController as? CaptureController
}
viewController = viewController?.parent
}
return nil
}
}
open class CaptureController: ToolbarController {
/// A reference to the Capture instance.
@IBInspectable
open let capture = Capture()
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.portrait
}
open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return UIInterfaceOrientation.portrait
}
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/
open override func prepare() {
super.prepare()
displayStyle = .full
view.backgroundColor = .black
prepareStatusBar()
prepareToolbar()
prepareCapture()
}
}
extension CaptureController {
/// Prepares the statusBar.
fileprivate func prepareStatusBar() {
statusBar.backgroundColor = .clear
}
/// Prepares the toolbar.
fileprivate func prepareToolbar() {
toolbar.backgroundColor = .clear
toolbar.depthPreset = .none
}
/// Prepares capture.
fileprivate func prepareCapture() {
capture.delegate = self
capture.flashMode = .auto
}
}
extension CaptureController: CaptureDelegate {}
/*
* 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
import AVFoundation
open class CapturePreview: View {
open override class var layerClass: AnyClass {
return AVCaptureVideoPreviewLayer.self
}
/**
Converts a point in layer coordinates to a point of interest
in the coordinate space of the capture device providing input
to the layer.
- Parameter point: A CGPoint.
- Returns: A CGPoint that is converted.
*/
open func captureDevicePointOfInterestForPoint(point: CGPoint) -> CGPoint {
return (layer as! AVCaptureVideoPreviewLayer).captureDevicePointOfInterest(for: point)
}
/**
Converts a point of interest in the coordinate space of the
capture device providing input to the layer to a point in
layer coordinates.
- Parameter point: A CGPoint.
- Returns: A CGPoint that is converted.
*/
open func pointForCaptureDevicePointOfInterest(point: CGPoint) -> CGPoint {
return (layer as! AVCaptureVideoPreviewLayer).pointForCaptureDevicePoint(ofInterest: point)
}
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/
open override func prepare() {
super.prepare()
preparePreviewLayer()
}
/// Prepares the previewLayer.
private func preparePreviewLayer() {
layer.backgroundColor = Color.black.cgColor
layer.masksToBounds = true
(layer as! AVCaptureVideoPreviewLayer).videoGravity = AVLayerVideoGravityResizeAspectFill
}
}
...@@ -201,18 +201,11 @@ open class Card: PulseView { ...@@ -201,18 +201,11 @@ open class Card: PulseView {
bounds.size.height = h bounds.size.height = h
} }
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/
open override func prepare() { open override func prepare() {
super.prepare() super.prepare()
depthPreset = .depth1
pulseAnimation = .none pulseAnimation = .none
cornerRadiusPreset = .cornerRadius1 cornerRadiusPreset = .cornerRadius1
prepareContainer() prepareContainer()
} }
...@@ -235,7 +228,7 @@ open class Card: PulseView { ...@@ -235,7 +228,7 @@ open class Card: PulseView {
if 0 == h || nil != view as? UILabel { if 0 == h || nil != view as? UILabel {
(view as? UILabel)?.sizeToFit() (view as? UILabel)?.sizeToFit()
h = view.sizeThatFits(CGSize(width: w, height: CGFloat.greatestFiniteMagnitude)).height h = view.sizeThatFits(CGSize(width: w, height: .greatestFiniteMagnitude)).height
} }
view.width = w view.width = w
......
...@@ -28,30 +28,24 @@ ...@@ -28,30 +28,24 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
/** import UIKit
Gets the Obj-C reference for the instance object within the UIView extension.
- Parameter base: Base object. open class CardCollectionViewCell: CollectionViewCell {
- Parameter key: Memory key pointer. /// An optional reference to the card being displayed in the cell.
- Parameter initializer: Object initializer. open var card: Card? {
- Returns: The associated reference for the initializer object. didSet {
*/ oldValue?.removeFromSuperview()
internal func AssociatedObject<T: Any>(base: Any, key: UnsafePointer<UInt8>, initializer: () -> T) -> T {
if let v = objc_getAssociatedObject(base, key) as? T { guard let v = card else {
return v return
} }
let v = initializer() contentView.addSubview(v)
objc_setAssociatedObject(base, key, v, .OBJC_ASSOCIATION_RETAIN) }
return v }
}
/** open override func prepare() {
Sets the Obj-C reference for the instance object within the UIView extension. super.prepare()
- Parameter base: Base object. pulseAnimation = .none
- Parameter key: Memory key pointer. }
- Parameter value: The object instance to set for the associated object.
- Returns: The associated reference for the initializer object.
*/
internal func AssociateObject<T: Any>(base: Any, key: UnsafePointer<UInt8>, value: T) {
objc_setAssociatedObject(base, key, value, .OBJC_ASSOCIATION_RETAIN)
} }
...@@ -30,80 +30,84 @@ ...@@ -30,80 +30,84 @@
import UIKit import UIKit
class CardCollectionViewCell: CollectionViewCell { extension UIViewController {
open var card: Card? { /**
didSet { A convenience property that provides access to the CardCollectionViewController.
oldValue?.removeFromSuperview() This is the recommended method of accessing the CardCollectionViewController
if let v = card { through child UIViewControllers.
contentView.addSubview(v) */
public var cardCollectionViewController: CardCollectionViewController? {
var viewController: UIViewController? = self
while nil != viewController {
if viewController is CardCollectionViewController {
return viewController as? CardCollectionViewController
} }
viewController = viewController?.parent
} }
return nil
} }
} }
@objc(CollectionViewCard) open class CardCollectionViewController: UIViewController {
open class CollectionViewCard: Card { /// A reference to a Reminder.
/// A reference to the dataSourceItems. open let collectionView = CollectionView()
open var dataSourceItems = [DataSourceItem]() open var dataSourceItems = [DataSourceItem]()
/// An index of IndexPath to DataSourceItem. /// An index of IndexPath to DataSourceItem.
open var dataSourceItemsIndexPaths = [IndexPath: Any]() open var dataSourceItemsIndexPaths = [IndexPath: Any]()
/// A reference to the collectionView. open override func viewDidLoad() {
@IBInspectable super.viewDidLoad()
open let collectionView = CollectionView() prepare()
/// Reloads the view.
open override func reload() {
if 0 == collectionView.height {
var h: CGFloat = 0
var i: Int = 0
for dataSourceItem in dataSourceItems {
h += dataSourceItem.height ?? (dataSourceItems[i].data as? Card)?.height ?? 0
i += 1
}
collectionView.height = h
} }
collectionView.reloadData() open override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
super.layoutSubviews() layoutSubviews()
} }
/** /**
Prepares the view instance when intialized. When subclassing, Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method it is recommended to override the prepareView method
to initialize property values and other setup operations. to initialize property values and other setup operations.
The super.prepare method should always be called immediately The super.prepareView method should always be called immediately
when subclassing. when subclassing.
*/ */
open override func prepare() { open func prepare() {
super.prepare() view.clipsToBounds = true
pulseAnimation = .none view.backgroundColor = .white
view.contentScaleFactor = Screen.scale
prepareCollectionView() prepareCollectionView()
prepareContentView() }
/// Calls the layout functions for the view heirarchy.
open func layoutSubviews() {
layoutCollectionView()
} }
} }
extension CollectionViewCard { extension CardCollectionViewController {
/// Prepares the collectionView. /// Prepares the collectionView.
fileprivate func prepareCollectionView() { fileprivate func prepareCollectionView() {
collectionView.delegate = self collectionView.delegate = self
collectionView.dataSource = self collectionView.dataSource = self
collectionView.interimSpacePreset = .none
collectionView.register(CardCollectionViewCell.self, forCellWithReuseIdentifier: "CardCollectionViewCell") collectionView.register(CardCollectionViewCell.self, forCellWithReuseIdentifier: "CardCollectionViewCell")
view.addSubview(collectionView)
layoutCollectionView()
} }
}
/// Prepares the contentView. extension CardCollectionViewController {
fileprivate func prepareContentView() { /// Sets the frame for the collectionView.
contentView = collectionView fileprivate func layoutCollectionView() {
collectionView.frame = view.bounds
} }
} }
extension CollectionViewCard: CollectionViewDelegate {} extension CardCollectionViewController: CollectionViewDelegate {}
extension CollectionViewCard: CollectionViewDataSource { extension CardCollectionViewController: CollectionViewDataSource {
@objc @objc
open func numberOfSections(in collectionView: UICollectionView) -> Int { open func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1 return 1
...@@ -117,7 +121,6 @@ extension CollectionViewCard: CollectionViewDataSource { ...@@ -117,7 +121,6 @@ extension CollectionViewCard: CollectionViewDataSource {
@objc @objc
open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CardCollectionViewCell", for: indexPath) as! CardCollectionViewCell let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CardCollectionViewCell", for: indexPath) as! CardCollectionViewCell
cell.pulseAnimation = .none
guard let card = dataSourceItems[indexPath.item].data as? Card else { guard let card = dataSourceItems[indexPath.item].data as? Card else {
return cell return cell
...@@ -125,14 +128,11 @@ extension CollectionViewCard: CollectionViewDataSource { ...@@ -125,14 +128,11 @@ extension CollectionViewCard: CollectionViewDataSource {
dataSourceItemsIndexPaths[indexPath] = card dataSourceItemsIndexPaths[indexPath] = card
if .vertical == self.collectionView.scrollDirection { card.frame = cell.bounds
card.width = cell.width
} else {
card.height = cell.height
}
cell.card = card cell.card = card
return cell return cell
} }
} }
...@@ -108,7 +108,7 @@ extension NSMutableAttributedString { ...@@ -108,7 +108,7 @@ extension NSMutableAttributedString {
- Parameter value: Any type. - Parameter value: Any type.
- Parameter range: A NSRange. - Parameter range: A NSRange.
*/ */
open func addAttribute(characterAttribute: CharacterAttribute, value: Any, range: NSRange) { open func add(characterAttribute: CharacterAttribute, value: Any, range: NSRange) {
addAttribute(CharacterAttributeToValue(attribute: characterAttribute), value: value, range: range) addAttribute(CharacterAttributeToValue(attribute: characterAttribute), value: value, range: range)
} }
...@@ -117,9 +117,9 @@ extension NSMutableAttributedString { ...@@ -117,9 +117,9 @@ extension NSMutableAttributedString {
- Parameter characterAttributes: A Dictionary of CharacterAttribute type keys and Any type values. - Parameter characterAttributes: A Dictionary of CharacterAttribute type keys and Any type values.
- Parameter range: A NSRange. - Parameter range: A NSRange.
*/ */
open func addAttributes(characterAttributes: [CharacterAttribute: Any], range: NSRange) { open func add(characterAttributes: [CharacterAttribute: Any], range: NSRange) {
for (k, v) in characterAttributes { for (k, v) in characterAttributes {
addAttribute(characterAttribute: k, value: v, range: range) add(characterAttribute: k, value: v, range: range)
} }
} }
...@@ -129,9 +129,9 @@ extension NSMutableAttributedString { ...@@ -129,9 +129,9 @@ extension NSMutableAttributedString {
- Parameter value: Any type. - Parameter value: Any type.
- Parameter range: A NSRange. - Parameter range: A NSRange.
*/ */
open func updateAttribute(characterAttribute: CharacterAttribute, value: Any, range: NSRange) { open func update(characterAttribute: CharacterAttribute, value: Any, range: NSRange) {
removeAttribute(characterAttribute: characterAttribute, range: range) remove(characterAttribute: characterAttribute, range: range)
addAttribute(characterAttribute: characterAttribute, value: value, range: range) add(characterAttribute: characterAttribute, value: value, range: range)
} }
/** /**
...@@ -139,9 +139,9 @@ extension NSMutableAttributedString { ...@@ -139,9 +139,9 @@ extension NSMutableAttributedString {
- Parameter characterAttributes: A Dictionary of CharacterAttribute type keys and Any type values. - Parameter characterAttributes: A Dictionary of CharacterAttribute type keys and Any type values.
- Parameter range: A NSRange. - Parameter range: A NSRange.
*/ */
open func updateAttributes(characterAttributes: [CharacterAttribute: Any], range: NSRange) { open func update(characterAttributes: [CharacterAttribute: Any], range: NSRange) {
for (k, v) in characterAttributes { for (k, v) in characterAttributes {
updateAttribute(characterAttribute: k, value: v, range: range) update(characterAttribute: k, value: v, range: range)
} }
} }
...@@ -150,7 +150,7 @@ extension NSMutableAttributedString { ...@@ -150,7 +150,7 @@ extension NSMutableAttributedString {
- Parameter characterAttribute: A CharacterAttribute. - Parameter characterAttribute: A CharacterAttribute.
- Parameter range: A NSRange. - Parameter range: A NSRange.
*/ */
open func removeAttribute(characterAttribute: CharacterAttribute, range: NSRange) { open func remove(characterAttribute: CharacterAttribute, range: NSRange) {
removeAttribute(CharacterAttributeToValue(attribute: characterAttribute), range: range) removeAttribute(CharacterAttributeToValue(attribute: characterAttribute), range: range)
} }
...@@ -159,9 +159,9 @@ extension NSMutableAttributedString { ...@@ -159,9 +159,9 @@ extension NSMutableAttributedString {
- Parameter characterAttributes: An Array of CharacterAttributes. - Parameter characterAttributes: An Array of CharacterAttributes.
- Parameter range: A NSRange. - Parameter range: A NSRange.
*/ */
open func removeAttributes(characterAttributes: [CharacterAttribute], range: NSRange) { open func remove(characterAttributes: [CharacterAttribute], range: NSRange) {
for k in characterAttributes { for k in characterAttributes {
removeAttribute(characterAttribute: k, range: range) remove(characterAttribute: k, range: range)
} }
} }
} }
/*
* 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 ChipItemKey: UInt8 = 0
@objc(ChipBarAlignment)
public enum ChipBarAlignment: Int {
case top
case bottom
case hidden
}
extension UIViewController {
/**
A convenience property that provides access to the ChipBarController.
This is the recommended method of accessing the ChipBarController
through child UIViewControllers.
*/
public var chipsController: ChipBarController? {
return traverseViewControllerHierarchyForClassType()
}
}
open class ChipBarController: TransitionController {
/**
A Display value to indicate whether or not to
display the rootViewController to the full view
bounds, or up to the toolbar height.
*/
open var displayStyle = DisplayStyle.partial {
didSet {
layoutSubviews()
}
}
/// The ChipBar used to switch between view controllers.
@IBInspectable
open let chipBar = ChipBar()
/// The chipBar alignment.
open var chipBarAlignment = ChipBarAlignment.bottom {
didSet {
layoutSubviews()
}
}
open override func layoutSubviews() {
super.layoutSubviews()
layoutChipBar()
layoutContainer()
layoutRootViewController()
}
open override func prepare() {
super.prepare()
prepareChipBar()
}
}
fileprivate extension ChipBarController {
/// Prepares the ChipBar.
func prepareChipBar() {
chipBar.depthPreset = .depth1
view.addSubview(chipBar)
}
}
fileprivate extension ChipBarController {
/// Layout the container.
func layoutContainer() {
chipBar.width = view.width
switch displayStyle {
case .partial:
let p = chipBar.height
let y = view.height - p
switch chipBarAlignment {
case .top:
container.y = p
container.height = y
case .bottom:
container.y = 0
container.height = y
case .hidden:
container.y = 0
container.height = view.height
}
container.width = view.width
case .full:
container.frame = view.bounds
}
}
/// Layout the chipBar.
func layoutChipBar() {
chipBar.width = view.width
switch chipBarAlignment {
case .top:
chipBar.isHidden = false
chipBar.y = 0
case .bottom:
chipBar.isHidden = false
chipBar.y = view.height - chipBar.height
case .hidden:
chipBar.isHidden = true
}
}
/// Layout the rootViewController.
func layoutRootViewController() {
rootViewController.view.frame = container.bounds
}
}
...@@ -102,6 +102,15 @@ open class CollectionView: UICollectionView { ...@@ -102,6 +102,15 @@ open class CollectionView: UICollectionView {
/** /**
An initializer that initializes the object. An initializer that initializes the object.
- Parameter collectionViewLayout: A UICollectionViewLayout reference.
*/
public init(collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: .zero, collectionViewLayout: layout)
prepare()
}
/**
An initializer that initializes the object.
- Parameter frame: A CGRect defining the view's frame. - Parameter frame: A CGRect defining the view's frame.
*/ */
public init(frame: CGRect) { public init(frame: CGRect) {
...@@ -110,8 +119,9 @@ open class CollectionView: UICollectionView { ...@@ -110,8 +119,9 @@ open class CollectionView: UICollectionView {
} }
/// A convenience initializer that initializes the object. /// A convenience initializer that initializes the object.
public convenience init() { public init() {
self.init(frame: .zero) super.init(frame: .zero, collectionViewLayout: CollectionViewLayout())
prepare()
} }
/** /**
...@@ -122,7 +132,8 @@ open class CollectionView: UICollectionView { ...@@ -122,7 +132,8 @@ open class CollectionView: UICollectionView {
when subclassing. when subclassing.
*/ */
open func prepare() { open func prepare() {
contentScaleFactor = Screen.scale
backgroundColor = .white backgroundColor = .white
contentScaleFactor = Screen.scale
register(CollectionViewCell.self, forCellWithReuseIdentifier: "CollectionViewCell")
} }
} }
...@@ -250,6 +250,7 @@ open class CollectionViewCell: UICollectionViewCell, Pulseable, PulseableLayer { ...@@ -250,6 +250,7 @@ open class CollectionViewCell: UICollectionViewCell, Pulseable, PulseableLayer {
when subclassing. when subclassing.
*/ */
open func prepare() { open func prepare() {
backgroundColor = .white
contentScaleFactor = Screen.scale contentScaleFactor = Screen.scale
prepareVisualLayer() prepareVisualLayer()
preparePulse() preparePulse()
......
...@@ -71,7 +71,7 @@ open class CollectionViewController: UIViewController { ...@@ -71,7 +71,7 @@ open class CollectionViewController: UIViewController {
open override func viewWillLayoutSubviews() { open override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews() super.viewWillLayoutSubviews()
collectionView.frame = view.bounds layoutSubviews()
} }
/** /**
...@@ -87,6 +87,11 @@ open class CollectionViewController: UIViewController { ...@@ -87,6 +87,11 @@ open class CollectionViewController: UIViewController {
view.contentScaleFactor = Screen.scale view.contentScaleFactor = Screen.scale
prepareCollectionView() prepareCollectionView()
} }
/// Calls the layout functions for the view heirarchy.
open func layoutSubviews() {
layoutCollectionView()
}
} }
extension CollectionViewController { extension CollectionViewController {
...@@ -98,7 +103,15 @@ extension CollectionViewController { ...@@ -98,7 +103,15 @@ extension CollectionViewController {
} }
} }
extension CollectionViewController {
/// Sets the frame for the collectionView.
fileprivate func layoutCollectionView() {
collectionView.frame = view.bounds
}
}
extension CollectionViewController: CollectionViewDelegate {} extension CollectionViewController: CollectionViewDelegate {}
extension CollectionViewController: CollectionViewDataSource { extension CollectionViewController: CollectionViewDataSource {
@objc @objc
open func numberOfSections(in collectionView: UICollectionView) -> Int { open func numberOfSections(in collectionView: UICollectionView) -> Int {
...@@ -112,6 +125,6 @@ extension CollectionViewController: CollectionViewDataSource { ...@@ -112,6 +125,6 @@ extension CollectionViewController: CollectionViewDataSource {
@objc @objc
open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return UICollectionViewCell() return collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath)
} }
} }
...@@ -32,72 +32,154 @@ import UIKit ...@@ -32,72 +32,154 @@ import UIKit
open class CollectionViewLayout: UICollectionViewLayout { open class CollectionViewLayout: UICollectionViewLayout {
/// Used to calculate the dimensions of the cells. /// Used to calculate the dimensions of the cells.
open var offset = CGPoint.zero public var offset = CGPoint.zero
/// The size of items. /// The size of items.
open var itemSize = CGSize.zero public var itemSize = CGSize.zero
/// A preset wrapper around contentEdgeInsets. /// A preset wrapper around contentEdgeInsets.
open var contentEdgeInsetsPreset = EdgeInsetsPreset.none { public var contentEdgeInsetsPreset = EdgeInsetsPreset.none {
didSet { didSet {
contentEdgeInsets = EdgeInsetsPresetToValue(preset: contentEdgeInsetsPreset) contentEdgeInsets = EdgeInsetsPresetToValue(preset: contentEdgeInsetsPreset)
} }
} }
/// A wrapper around grid.contentEdgeInsets. /// A wrapper around grid.contentEdgeInsets.
open var contentEdgeInsets = EdgeInsets.zero public var contentEdgeInsets = EdgeInsets.zero
/// Size of the content. /// Size of the content.
open fileprivate(set) var contentSize = CGSize.zero public fileprivate(set) var contentSize = CGSize.zero
/// Layout attribute items. /// Layout attribute items.
open fileprivate(set) lazy var layoutItems = [(UICollectionViewLayoutAttributes, NSIndexPath)]() public fileprivate(set) lazy var layoutItems = [(UICollectionViewLayoutAttributes, NSIndexPath)]()
/// Cell data source items. /// Cell data source items.
open fileprivate(set) var dataSourceItems: [DataSourceItem]? public fileprivate(set) var dataSourceItems: [DataSourceItem]?
/// Scroll direction. /// Scroll direction.
open var scrollDirection = UICollectionViewScrollDirection.vertical public var scrollDirection = UICollectionViewScrollDirection.vertical
/// A preset wrapper around interimSpace. /// A preset wrapper around interimSpace.
open var interimSpacePreset = InterimSpacePreset.none { public var interimSpacePreset = InterimSpacePreset.none {
didSet { didSet {
interimSpace = InterimSpacePresetToValue(preset: interimSpacePreset) interimSpace = InterimSpacePresetToValue(preset: interimSpacePreset)
} }
} }
/// Spacing between items. /// Spacing between items.
open var interimSpace: InterimSpace = 0 public var interimSpace: InterimSpace = 0
open override var collectionViewContentSize: CGSize { open override var collectionViewContentSize: CGSize {
return contentSize return contentSize
} }
}
extension CollectionViewLayout {
/** /**
Retrieves the index paths for the items within the passed in CGRect. Retrieves the index paths for the items within the passed in CGRect.
- Parameter rect: A CGRect that acts as the bounds to find the items within. - Parameter rect: A CGRect that acts as the bounds to find the items within.
- Returns: An Array of NSIndexPath objects. - Returns: An Array of NSIndexPath objects.
*/ */
open func indexPathsOfItemsInRect(rect: CGRect) -> [NSIndexPath] { public func indexPathsOfItems(in rect: CGRect) -> [NSIndexPath] {
var paths = [NSIndexPath]() var paths = [NSIndexPath]()
for (attribute, indexPath) in layoutItems { for (attribute, indexPath) in layoutItems {
if rect.intersects(attribute.frame) { guard rect.intersects(attribute.frame) else {
paths.append(indexPath) continue
} }
paths.append(indexPath)
} }
return paths return paths
} }
}
extension CollectionViewLayout {
/**
Prepares the layout for the given data source items.
- Parameter for dataSourceItems: An Array of DataSourceItems.
*/
fileprivate func prepareLayout(for dataSourceItems: [DataSourceItem]) {
self.dataSourceItems = dataSourceItems
layoutItems.removeAll()
offset.x = contentEdgeInsets.left
offset.y = contentEdgeInsets.top
for i in 0..<dataSourceItems.count {
let item = dataSourceItems[i]
let indexPath = IndexPath(item: i, section: 0)
layoutItems.append((layoutAttributesForItem(at: indexPath)!, indexPath as NSIndexPath))
offset.x += interimSpace
offset.y += interimSpace
if nil != item.width {
offset.x += item.width!
} else if let v = item.data as? UIView, 0 < v.bounds.width {
offset.x += v.bounds.width
} else {
offset.x += itemSize.width
}
if nil != item.height {
offset.y += item.height!
} else if let v = item.data as? UIView, 0 < v.bounds.height {
offset.y += v.bounds.height
} else {
offset.y += itemSize.height
}
}
offset.x += contentEdgeInsets.right - interimSpace
offset.y += contentEdgeInsets.bottom - interimSpace
if 0 < itemSize.width && 0 < itemSize.height {
contentSize = CGSize(width: offset.x, height: offset.y)
} else if .vertical == scrollDirection {
contentSize = CGSize(width: collectionView!.bounds.width, height: offset.y)
} else {
contentSize = CGSize(width: offset.x, height: collectionView!.bounds.height)
}
}
}
extension CollectionViewLayout {
open override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { open override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
let item = dataSourceItems![indexPath.item] let dataSourceItem = dataSourceItems![indexPath.item]
if 0 < itemSize.width && 0 < itemSize.height { if 0 < itemSize.width && 0 < itemSize.height {
attributes.frame = CGRect(x: offset.x, y: offset.y, width: itemSize.width - contentEdgeInsets.left - contentEdgeInsets.right, height: itemSize.height - contentEdgeInsets.top - contentEdgeInsets.bottom) attributes.frame = CGRect(x: offset.x, y: offset.y, width: itemSize.width - contentEdgeInsets.left - contentEdgeInsets.right, height: itemSize.height - contentEdgeInsets.top - contentEdgeInsets.bottom)
} else if .vertical == scrollDirection { } else if .vertical == scrollDirection {
attributes.frame = CGRect(x: contentEdgeInsets.left, y: offset.y, width: collectionView!.bounds.width - contentEdgeInsets.left - contentEdgeInsets.right, height: item.height ?? collectionView!.bounds.height) if let h = dataSourceItem.height {
attributes.frame = CGRect(x: contentEdgeInsets.left, y: offset.y, width: collectionView!.bounds.width - contentEdgeInsets.left - contentEdgeInsets.right, height: h)
} else if let v = dataSourceItem.data as? UIView, 0 < v.bounds.height {
v.updateConstraints()
v.setNeedsLayout()
v.layoutIfNeeded()
attributes.frame = CGRect(x: contentEdgeInsets.left, y: offset.y, width: collectionView!.bounds.width - contentEdgeInsets.left - contentEdgeInsets.right, height: v.bounds.height)
} else { } else {
attributes.frame = CGRect(x: offset.x, y: contentEdgeInsets.top, width: item.width ?? collectionView!.bounds.width, height: collectionView!.bounds.height - contentEdgeInsets.top - contentEdgeInsets.bottom) attributes.frame = CGRect(x: contentEdgeInsets.left, y: offset.y, width: collectionView!.bounds.width - contentEdgeInsets.left - contentEdgeInsets.right, height: collectionView!.bounds.height)
}
} else {
if let w = dataSourceItem.width {
attributes.frame = CGRect(x: offset.x, y: contentEdgeInsets.top, width: w, height: collectionView!.bounds.height - contentEdgeInsets.top - contentEdgeInsets.bottom)
} else if let v = dataSourceItem.data as? UIView, 0 < v.bounds.width {
v.updateConstraints()
v.setNeedsLayout()
v.layoutIfNeeded()
attributes.frame = CGRect(x: offset.x, y: contentEdgeInsets.top, width: v.bounds.width, height: collectionView!.bounds.height - contentEdgeInsets.top - contentEdgeInsets.bottom)
} else {
attributes.frame = CGRect(x: offset.x, y: contentEdgeInsets.top, width: collectionView!.bounds.width, height: collectionView!.bounds.height - contentEdgeInsets.top - contentEdgeInsets.bottom)
}
} }
return attributes return attributes
...@@ -122,41 +204,10 @@ open class CollectionViewLayout: UICollectionViewLayout { ...@@ -122,41 +204,10 @@ open class CollectionViewLayout: UICollectionViewLayout {
return return
} }
prepareLayoutForItems(dataSourceItems: dataSource.dataSourceItems) prepareLayout(for: dataSource.dataSourceItems)
} }
open override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint { open override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
return proposedContentOffset return proposedContentOffset
} }
fileprivate func prepareLayoutForItems(dataSourceItems: [DataSourceItem]) {
self.dataSourceItems = dataSourceItems
layoutItems.removeAll()
offset.x = contentEdgeInsets.left
offset.y = contentEdgeInsets.top
for i in 0..<dataSourceItems.count {
let item = dataSourceItems[i]
let indexPath = IndexPath(item: i, section: 0)
layoutItems.append((layoutAttributesForItem(at: indexPath)!, indexPath as NSIndexPath))
offset.x += interimSpace
offset.x += item.width ?? itemSize.width
offset.y += interimSpace
offset.y += item.height ?? itemSize.height
}
offset.x += contentEdgeInsets.right - interimSpace
offset.y += contentEdgeInsets.bottom - interimSpace
if 0 < itemSize.width && 0 < itemSize.height {
contentSize = CGSize(width: offset.x, height: offset.y)
} else if .vertical == scrollDirection {
contentSize = CGSize(width: collectionView!.bounds.width, height: offset.y)
} else {
contentSize = CGSize(width: offset.x, height: collectionView!.bounds.height)
}
}
} }
...@@ -50,6 +50,11 @@ public struct Depth { ...@@ -50,6 +50,11 @@ public struct Depth {
/// Radius. /// Radius.
public var radius: CGFloat public var radius: CGFloat
/// A tuple of raw values.
public var rawValue: (CGSize, Float, CGFloat) {
return (offset.asSize, opacity, radius)
}
/// Preset. /// Preset.
public var preset = DepthPreset.none { public var preset = DepthPreset.none {
didSet { didSet {
...@@ -62,9 +67,9 @@ public struct Depth { ...@@ -62,9 +67,9 @@ public struct Depth {
/** /**
Initializer that takes in an offset, opacity, and radius. Initializer that takes in an offset, opacity, and radius.
- Parameter offset: UIOffset. - Parameter offset: A UIOffset.
- Parameter opacity: Float. - Parameter opacity: A Float.
- Parameter radius: CGFloat. - Parameter radius: A CGFloat.
*/ */
public init(offset: Offset = .zero, opacity: Float = 0, radius: CGFloat = 0) { public init(offset: Offset = .zero, opacity: Float = 0, radius: CGFloat = 0) {
self.offset = offset self.offset = offset
......
...@@ -30,7 +30,8 @@ ...@@ -30,7 +30,8 @@
import UIKit import UIKit
public enum DeviceModel { @objc(DeviceModel)
public enum DeviceModel: Int {
case iPodTouch5 case iPodTouch5
case iPodTouch6 case iPodTouch6
case iPhone4 case iPhone4
...@@ -116,3 +117,13 @@ public struct Device { ...@@ -116,3 +117,13 @@ public struct Device {
return UIDevice.current.userInterfaceIdiom return UIDevice.current.userInterfaceIdiom
} }
} }
public func ==(lhs: DeviceModel, rhs: DeviceModel) -> Bool {
return lhs.rawValue == rhs.rawValue
}
public func !=(lhs: DeviceModel, rhs: DeviceModel) -> Bool {
return lhs.rawValue != rhs.rawValue
}
...@@ -141,12 +141,12 @@ extension UIView { ...@@ -141,12 +141,12 @@ extension UIView {
/// TabBarItem reference. /// TabBarItem reference.
public private(set) var divider: Divider { public private(set) var divider: Divider {
get { get {
return AssociatedObject(base: self, key: &DividerKey) { return AssociatedObject.get(base: self, key: &DividerKey) {
return Divider(view: self) return Divider(view: self)
} }
} }
set(value) { set(value) {
AssociateObject(base: self, key: &DividerKey, value: value) AssociatedObject.set(base: self, key: &DividerKey, value: value)
} }
} }
...@@ -212,4 +212,9 @@ extension UIView { ...@@ -212,4 +212,9 @@ extension UIView {
divider.thickness = value divider.thickness = value
} }
} }
/// Sets the divider frame.
open func layoutDivider() {
divider.reload()
}
} }
...@@ -40,13 +40,6 @@ open class ErrorTextField: TextField { ...@@ -40,13 +40,6 @@ open class ErrorTextField: TextField {
} }
} }
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/
open override func prepare() { open override func prepare() {
super.prepare() super.prepare()
isErrorRevealed = false isErrorRevealed = false
......
...@@ -31,13 +31,6 @@ ...@@ -31,13 +31,6 @@
import UIKit import UIKit
open class FABButton: Button { open class FABButton: Button {
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/
open override func prepare() { open override func prepare() {
super.prepare() super.prepare()
depthPreset = .depth1 depthPreset = .depth1
......
...@@ -45,13 +45,6 @@ open class FABMenuItem: View { ...@@ -45,13 +45,6 @@ open class FABMenuItem: View {
/// A reference to the fabButton. /// A reference to the fabButton.
open let fabButton = FABButton() open let fabButton = FABButton()
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/
open override func prepare() { open override func prepare() {
super.prepare() super.prepare()
backgroundColor = nil backgroundColor = nil
...@@ -82,6 +75,12 @@ open class FABMenuItem: View { ...@@ -82,6 +75,12 @@ open class FABMenuItem: View {
addSubview(titleLabel) addSubview(titleLabel)
} }
} }
open var titleLabelSide : labelSide = .right
public enum labelSide {
case left
case right
}
} }
extension FABMenuItem { extension FABMenuItem {
...@@ -93,7 +92,14 @@ extension FABMenuItem { ...@@ -93,7 +92,14 @@ extension FABMenuItem {
titleLabel.width += 1.5 * interimSpace titleLabel.width += 1.5 * interimSpace
titleLabel.height += interimSpace / 2 titleLabel.height += interimSpace / 2
titleLabel.y = (height - titleLabel.height) / 2 titleLabel.y = (height - titleLabel.height) / 2
switch (titleLabelSide) {
case .left:
titleLabel.x = -titleLabel.width - interimSpace titleLabel.x = -titleLabel.width - interimSpace
case .right:
titleLabel.x = frame.width + interimSpace
}
titleLabel.alpha = 0 titleLabel.alpha = 0
titleLabel.isHidden = false titleLabel.isHidden = false
...@@ -218,8 +224,8 @@ open class FABMenu: View { ...@@ -218,8 +224,8 @@ open class FABMenu: View {
} }
} }
/// An internal handler for the FABButton. /// An open handler for the FABButton.
internal var handleFABButtonCallback: ((UIButton) -> Void)? open var handleFABButtonCallback: ((UIButton) -> Void)?
/// An internal handler for the open function. /// An internal handler for the open function.
internal var handleOpenCallback: (() -> Void)? internal var handleOpenCallback: (() -> Void)?
...@@ -341,7 +347,7 @@ extension FABMenu { ...@@ -341,7 +347,7 @@ extension FABMenu {
- Parameter animations: An animation block to execute on each view's animation. - Parameter animations: An animation block to execute on each view's animation.
- Parameter completion: A completion block to execute on each view's animation. - Parameter completion: A completion block to execute on each view's animation.
*/ */
internal func open(isTriggeredByUserInteraction: Bool, duration: TimeInterval = 0.15, delay: TimeInterval = 0, usingSpringWithDamping: CGFloat = 0.5, initialSpringVelocity: CGFloat = 0, options: UIViewAnimationOptions = [], animations: ((UIView) -> Void)? = nil, completion: ((UIView) -> Void)? = nil) { open func open(isTriggeredByUserInteraction: Bool, duration: TimeInterval = 0.15, delay: TimeInterval = 0, usingSpringWithDamping: CGFloat = 0.5, initialSpringVelocity: CGFloat = 0, options: UIViewAnimationOptions = [], animations: ((UIView) -> Void)? = nil, completion: ((UIView) -> Void)? = nil) {
handleOpenCallback?() handleOpenCallback?()
if isTriggeredByUserInteraction { if isTriggeredByUserInteraction {
...@@ -390,7 +396,7 @@ extension FABMenu { ...@@ -390,7 +396,7 @@ extension FABMenu {
- Parameter animations: An animation block to execute on each view's animation. - Parameter animations: An animation block to execute on each view's animation.
- Parameter completion: A completion block to execute on each view's animation. - Parameter completion: A completion block to execute on each view's animation.
*/ */
internal func close(isTriggeredByUserInteraction: Bool, duration: TimeInterval = 0.15, delay: TimeInterval = 0, usingSpringWithDamping: CGFloat = 0.5, initialSpringVelocity: CGFloat = 0, options: UIViewAnimationOptions = [], animations: ((UIView) -> Void)? = nil, completion: ((UIView) -> Void)? = nil) { open func close(isTriggeredByUserInteraction: Bool, duration: TimeInterval = 0.15, delay: TimeInterval = 0, usingSpringWithDamping: CGFloat = 0.5, initialSpringVelocity: CGFloat = 0, options: UIViewAnimationOptions = [], animations: ((UIView) -> Void)? = nil, completion: ((UIView) -> Void)? = nil) {
handleCloseCallback?() handleCloseCallback?()
if isTriggeredByUserInteraction { if isTriggeredByUserInteraction {
......
...@@ -43,18 +43,11 @@ extension UIViewController { ...@@ -43,18 +43,11 @@ extension UIViewController {
through child UIViewControllers. through child UIViewControllers.
*/ */
public var fabMenuController: FABMenuController? { public var fabMenuController: FABMenuController? {
var viewController: UIViewController? = self return traverseViewControllerHierarchyForClassType()
while nil != viewController {
if viewController is FABMenuController {
return viewController as? FABMenuController
}
viewController = viewController?.parent
}
return nil
} }
} }
open class FABMenuController: RootController { open class FABMenuController: TransitionController {
/// Reference to the MenuView. /// Reference to the MenuView.
@IBInspectable @IBInspectable
open let fabMenu = FABMenu() open let fabMenu = FABMenu()
...@@ -73,13 +66,6 @@ open class FABMenuController: RootController { ...@@ -73,13 +66,6 @@ open class FABMenuController: RootController {
rootViewController.view.frame = view.bounds rootViewController.view.frame = view.bounds
} }
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/
open override func prepare() { open override func prepare() {
super.prepare() super.prepare()
prepareFABMenu() prepareFABMenu()
...@@ -93,29 +79,42 @@ extension FABMenuController { ...@@ -93,29 +79,42 @@ extension FABMenuController {
fileprivate func prepareFABMenu() { fileprivate func prepareFABMenu() {
fabMenu.delegate = self fabMenu.delegate = self
fabMenu.zPosition = 1000 fabMenu.zPosition = 1000
fabMenu.handleFABButtonCallback = handleFABButtonCallback
fabMenu.handleOpenCallback = handleOpenCallback fabMenu.handleFABButtonCallback = { [weak self] in
fabMenu.handleCloseCallback = handleCloseCallback self?.handleFABButtonCallback(button: $0)
fabMenu.handleCompletionCallback = handleCompletionCallback }
fabMenu.handleOpenCallback = { [weak self] in
self?.handleOpenCallback()
}
fabMenu.handleCloseCallback = { [weak self] in
self?.handleCloseCallback()
}
fabMenu.handleCompletionCallback = { [weak self] in
self?.handleCompletionCallback(view: $0)
}
view.addSubview(fabMenu) view.addSubview(fabMenu)
} }
} }
extension FABMenuController { fileprivate extension FABMenuController {
/// Shows the fabMenuBacking. /// Shows the fabMenuBacking.
fileprivate func showFabMenuBacking() { func showFabMenuBacking() {
showFade() showFade()
showBlurView() showBlurView()
} }
/// Hides the fabMenuBacking. /// Hides the fabMenuBacking.
fileprivate func hideFabMenuBacking() { func hideFabMenuBacking() {
hideFade() hideFade()
hideBlurView() hideBlurView()
} }
/// Shows the blurView. /// Shows the blurView.
fileprivate func showBlurView() { func showBlurView() {
guard .blur == fabMenuBacking else { guard .blur == fabMenuBacking else {
return return
} }
...@@ -136,7 +135,7 @@ extension FABMenuController { ...@@ -136,7 +135,7 @@ extension FABMenuController {
} }
/// Hides the blurView. /// Hides the blurView.
fileprivate func hideBlurView() { func hideBlurView() {
guard .blur == fabMenuBacking else { guard .blur == fabMenuBacking else {
return return
} }
...@@ -150,7 +149,7 @@ extension FABMenuController { ...@@ -150,7 +149,7 @@ extension FABMenuController {
} }
/// Shows the fade. /// Shows the fade.
fileprivate func showFade() { func showFade() {
guard .fade == fabMenuBacking else { guard .fade == fabMenuBacking else {
return return
} }
...@@ -165,7 +164,7 @@ extension FABMenuController { ...@@ -165,7 +164,7 @@ extension FABMenuController {
} }
/// Hides the fade. /// Hides the fade.
fileprivate func hideFade() { func hideFade() {
guard .fade == fabMenuBacking else { guard .fade == fabMenuBacking else {
return return
} }
...@@ -180,13 +179,12 @@ extension FABMenuController { ...@@ -180,13 +179,12 @@ extension FABMenuController {
} }
} }
extension FABMenuController { fileprivate extension FABMenuController {
/** /**
Handler to toggle the FABMenu opened or closed. Handler to toggle the FABMenu opened or closed.
- Parameter button: A UIButton. - Parameter button: A UIButton.
*/ */
@objc func handleFABButtonCallback(button: UIButton) {
fileprivate func handleFABButtonCallback(button: UIButton) {
guard fabMenu.isOpened else { guard fabMenu.isOpened else {
fabMenu.open(isTriggeredByUserInteraction: true) fabMenu.open(isTriggeredByUserInteraction: true)
return return
...@@ -196,15 +194,13 @@ extension FABMenuController { ...@@ -196,15 +194,13 @@ extension FABMenuController {
} }
/// Handler for when the FABMenu.open function is called. /// Handler for when the FABMenu.open function is called.
@objc func handleOpenCallback() {
fileprivate func handleOpenCallback() {
isUserInteractionEnabled = false isUserInteractionEnabled = false
showFabMenuBacking() showFabMenuBacking()
} }
/// Handler for when the FABMenu.close function is called. /// Handler for when the FABMenu.close function is called.
@objc func handleCloseCallback() {
fileprivate func handleCloseCallback() {
isUserInteractionEnabled = false isUserInteractionEnabled = false
hideFabMenuBacking() hideFabMenuBacking()
} }
...@@ -213,7 +209,7 @@ extension FABMenuController { ...@@ -213,7 +209,7 @@ extension FABMenuController {
Completion handler for FABMenu open and close calls. Completion handler for FABMenu open and close calls.
- Parameter view: A UIView. - Parameter view: A UIView.
*/ */
fileprivate func handleCompletionCallback(view: UIView) { func handleCompletionCallback(view: UIView) {
if view == fabMenu.fabMenuItems.last { if view == fabMenu.fabMenuItems.last {
isUserInteractionEnabled = true isUserInteractionEnabled = true
} }
......
...@@ -31,13 +31,6 @@ ...@@ -31,13 +31,6 @@
import UIKit import UIKit
open class FlatButton: Button { open class FlatButton: Button {
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/
open override func prepare() { open override func prepare() {
super.prepare() super.prepare()
cornerRadiusPreset = .cornerRadius1 cornerRadiusPreset = .cornerRadius1
......
...@@ -272,12 +272,12 @@ extension UIView { ...@@ -272,12 +272,12 @@ extension UIView {
/// Grid reference. /// Grid reference.
public var grid: Grid { public var grid: Grid {
get { get {
return AssociatedObject(base: self, key: &GridKey) { return AssociatedObject.get(base: self, key: &GridKey) {
return Grid(context: self) return Grid(context: self)
} }
} }
set(value) { set(value) {
AssociateObject(base: self, key: &GridKey, value: value) AssociatedObject.set(base: self, key: &GridKey, value: value)
} }
} }
......
...@@ -31,13 +31,6 @@ ...@@ -31,13 +31,6 @@
import UIKit import UIKit
open class IconButton: Button { open class IconButton: Button {
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/
open override func prepare() { open override func prepare() {
super.prepare() super.prepare()
pulseAnimation = .center pulseAnimation = .center
......
...@@ -30,12 +30,6 @@ ...@@ -30,12 +30,6 @@
import UIKit import UIKit
@objc(ToolbarAlignment)
public enum ToolbarAlignment: Int {
case top
case bottom
}
open class ImageCard: Card { open class ImageCard: Card {
/** /**
A Display value to indicate whether or not to A Display value to indicate whether or not to
......
...@@ -639,6 +639,7 @@ extension Layout { ...@@ -639,6 +639,7 @@ extension Layout {
public class func width(parent: UIView, child: UIView, width: CGFloat = 0) { public class func width(parent: UIView, child: UIView, width: CGFloat = 0) {
prepareForConstraint(parent, child: child) prepareForConstraint(parent, child: child)
parent.addConstraint(NSLayoutConstraint(item: child, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: width)) parent.addConstraint(NSLayoutConstraint(item: child, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: width))
child.updateConstraints()
child.setNeedsLayout() child.setNeedsLayout()
child.layoutIfNeeded() child.layoutIfNeeded()
} }
...@@ -652,6 +653,7 @@ extension Layout { ...@@ -652,6 +653,7 @@ extension Layout {
public class func height(parent: UIView, child: UIView, height: CGFloat = 0) { public class func height(parent: UIView, child: UIView, height: CGFloat = 0) {
prepareForConstraint(parent, child: child) prepareForConstraint(parent, child: child)
parent.addConstraint(NSLayoutConstraint(item: child, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: height)) parent.addConstraint(NSLayoutConstraint(item: child, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: height))
child.updateConstraints()
child.setNeedsLayout() child.setNeedsLayout()
child.layoutIfNeeded() child.layoutIfNeeded()
} }
...@@ -687,6 +689,7 @@ extension Layout { ...@@ -687,6 +689,7 @@ extension Layout {
parent.addConstraint(NSLayoutConstraint(item: children[children.count - 1], attribute: .right, relatedBy: .equal, toItem: parent, attribute: .right, multiplier: 1, constant: -right)) parent.addConstraint(NSLayoutConstraint(item: children[children.count - 1], attribute: .right, relatedBy: .equal, toItem: parent, attribute: .right, multiplier: 1, constant: -right))
} }
for child in children { for child in children {
child.updateConstraints()
child.setNeedsLayout() child.setNeedsLayout()
child.layoutIfNeeded() child.layoutIfNeeded()
} }
...@@ -703,6 +706,7 @@ extension Layout { ...@@ -703,6 +706,7 @@ extension Layout {
*/ */
public class func vertically(parent: UIView, children: [UIView], top: CGFloat = 0, bottom: CGFloat = 0, interimSpace: InterimSpace = 0) { public class func vertically(parent: UIView, children: [UIView], top: CGFloat = 0, bottom: CGFloat = 0, interimSpace: InterimSpace = 0) {
prepareForConstraint(parent, children: children) prepareForConstraint(parent, children: children)
if 0 < children.count { if 0 < children.count {
parent.addConstraint(NSLayoutConstraint(item: children[0], attribute: .top, relatedBy: .equal, toItem: parent, attribute: .top, multiplier: 1, constant: top)) parent.addConstraint(NSLayoutConstraint(item: children[0], attribute: .top, relatedBy: .equal, toItem: parent, attribute: .top, multiplier: 1, constant: top))
for i in 1..<children.count { for i in 1..<children.count {
...@@ -711,7 +715,9 @@ extension Layout { ...@@ -711,7 +715,9 @@ extension Layout {
} }
parent.addConstraint(NSLayoutConstraint(item: children[children.count - 1], attribute: .bottom, relatedBy: .equal, toItem: parent, attribute: .bottom, multiplier: 1, constant: -bottom)) parent.addConstraint(NSLayoutConstraint(item: children[children.count - 1], attribute: .bottom, relatedBy: .equal, toItem: parent, attribute: .bottom, multiplier: 1, constant: -bottom))
} }
for child in children { for child in children {
child.updateConstraints()
child.setNeedsLayout() child.setNeedsLayout()
child.layoutIfNeeded() child.layoutIfNeeded()
} }
...@@ -726,8 +732,11 @@ extension Layout { ...@@ -726,8 +732,11 @@ extension Layout {
*/ */
public class func horizontally(parent: UIView, child: UIView, left: CGFloat = 0, right: CGFloat = 0) { public class func horizontally(parent: UIView, child: UIView, left: CGFloat = 0, right: CGFloat = 0) {
prepareForConstraint(parent, child: child) prepareForConstraint(parent, child: child)
parent.addConstraint(NSLayoutConstraint(item: child, attribute: .left, relatedBy: .equal, toItem: parent, attribute: .left, multiplier: 1, constant: left)) parent.addConstraint(NSLayoutConstraint(item: child, attribute: .left, relatedBy: .equal, toItem: parent, attribute: .left, multiplier: 1, constant: left))
parent.addConstraint(NSLayoutConstraint(item: child, attribute: .right, relatedBy: .equal, toItem: parent, attribute: .right, multiplier: 1, constant: -right)) parent.addConstraint(NSLayoutConstraint(item: child, attribute: .right, relatedBy: .equal, toItem: parent, attribute: .right, multiplier: 1, constant: -right))
child.updateConstraints()
child.setNeedsLayout() child.setNeedsLayout()
child.layoutIfNeeded() child.layoutIfNeeded()
} }
...@@ -741,8 +750,11 @@ extension Layout { ...@@ -741,8 +750,11 @@ extension Layout {
*/ */
public class func vertically(parent: UIView, child: UIView, top: CGFloat = 0, bottom: CGFloat = 0) { public class func vertically(parent: UIView, child: UIView, top: CGFloat = 0, bottom: CGFloat = 0) {
prepareForConstraint(parent, child: child) prepareForConstraint(parent, child: child)
parent.addConstraint(NSLayoutConstraint(item: child, attribute: .top, relatedBy: .equal, toItem: parent, attribute: .top, multiplier: 1, constant: top)) parent.addConstraint(NSLayoutConstraint(item: child, attribute: .top, relatedBy: .equal, toItem: parent, attribute: .top, multiplier: 1, constant: top))
parent.addConstraint(NSLayoutConstraint(item: child, attribute: .bottom, relatedBy: .equal, toItem: parent, attribute: .bottom, multiplier: 1, constant: -bottom)) parent.addConstraint(NSLayoutConstraint(item: child, attribute: .bottom, relatedBy: .equal, toItem: parent, attribute: .bottom, multiplier: 1, constant: -bottom))
child.updateConstraints()
child.setNeedsLayout() child.setNeedsLayout()
child.layoutIfNeeded() child.layoutIfNeeded()
} }
...@@ -770,7 +782,10 @@ extension Layout { ...@@ -770,7 +782,10 @@ extension Layout {
*/ */
public class func top(parent: UIView, child: UIView, top: CGFloat = 0) { public class func top(parent: UIView, child: UIView, top: CGFloat = 0) {
prepareForConstraint(parent, child: child) prepareForConstraint(parent, child: child)
parent.addConstraint(NSLayoutConstraint(item: child, attribute: .top, relatedBy: .equal, toItem: parent, attribute: .top, multiplier: 1, constant: top)) parent.addConstraint(NSLayoutConstraint(item: child, attribute: .top, relatedBy: .equal, toItem: parent, attribute: .top, multiplier: 1, constant: top))
child.updateConstraints()
child.setNeedsLayout() child.setNeedsLayout()
child.layoutIfNeeded() child.layoutIfNeeded()
} }
...@@ -784,7 +799,10 @@ extension Layout { ...@@ -784,7 +799,10 @@ extension Layout {
*/ */
public class func left(parent: UIView, child: UIView, left: CGFloat = 0) { public class func left(parent: UIView, child: UIView, left: CGFloat = 0) {
prepareForConstraint(parent, child: child) prepareForConstraint(parent, child: child)
parent.addConstraint(NSLayoutConstraint(item: child, attribute: .left, relatedBy: .equal, toItem: parent, attribute: .left, multiplier: 1, constant: left)) parent.addConstraint(NSLayoutConstraint(item: child, attribute: .left, relatedBy: .equal, toItem: parent, attribute: .left, multiplier: 1, constant: left))
child.updateConstraints()
child.setNeedsLayout() child.setNeedsLayout()
child.layoutIfNeeded() child.layoutIfNeeded()
} }
...@@ -798,7 +816,10 @@ extension Layout { ...@@ -798,7 +816,10 @@ extension Layout {
*/ */
public class func bottom(parent: UIView, child: UIView, bottom: CGFloat = 0) { public class func bottom(parent: UIView, child: UIView, bottom: CGFloat = 0) {
prepareForConstraint(parent, child: child) prepareForConstraint(parent, child: child)
parent.addConstraint(NSLayoutConstraint(item: child, attribute: .bottom, relatedBy: .equal, toItem: parent, attribute: .bottom, multiplier: 1, constant: -bottom)) parent.addConstraint(NSLayoutConstraint(item: child, attribute: .bottom, relatedBy: .equal, toItem: parent, attribute: .bottom, multiplier: 1, constant: -bottom))
child.updateConstraints()
child.setNeedsLayout() child.setNeedsLayout()
child.layoutIfNeeded() child.layoutIfNeeded()
} }
...@@ -812,7 +833,10 @@ extension Layout { ...@@ -812,7 +833,10 @@ extension Layout {
*/ */
public class func right(parent: UIView, child: UIView, right: CGFloat = 0) { public class func right(parent: UIView, child: UIView, right: CGFloat = 0) {
prepareForConstraint(parent, child: child) prepareForConstraint(parent, child: child)
parent.addConstraint(NSLayoutConstraint(item: child, attribute: .right, relatedBy: .equal, toItem: parent, attribute: .right, multiplier: 1, constant: -right)) parent.addConstraint(NSLayoutConstraint(item: child, attribute: .right, relatedBy: .equal, toItem: parent, attribute: .right, multiplier: 1, constant: -right))
child.updateConstraints()
child.setNeedsLayout() child.setNeedsLayout()
child.layoutIfNeeded() child.layoutIfNeeded()
} }
...@@ -891,7 +915,10 @@ extension Layout { ...@@ -891,7 +915,10 @@ extension Layout {
*/ */
public class func centerHorizontally(parent: UIView, child: UIView, offset: CGFloat = 0) { public class func centerHorizontally(parent: UIView, child: UIView, offset: CGFloat = 0) {
prepareForConstraint(parent, child: child) prepareForConstraint(parent, child: child)
parent.addConstraint(NSLayoutConstraint(item: child, attribute: .centerX, relatedBy: .equal, toItem: parent, attribute: .centerX, multiplier: 1, constant: offset)) parent.addConstraint(NSLayoutConstraint(item: child, attribute: .centerX, relatedBy: .equal, toItem: parent, attribute: .centerX, multiplier: 1, constant: offset))
child.updateConstraints()
child.setNeedsLayout() child.setNeedsLayout()
child.layoutIfNeeded() child.layoutIfNeeded()
} }
...@@ -905,7 +932,10 @@ extension Layout { ...@@ -905,7 +932,10 @@ extension Layout {
*/ */
public class func centerVertically(parent: UIView, child: UIView, offset: CGFloat = 0) { public class func centerVertically(parent: UIView, child: UIView, offset: CGFloat = 0) {
prepareForConstraint(parent, child: child) prepareForConstraint(parent, child: child)
parent.addConstraint(NSLayoutConstraint(item: child, attribute: .centerY, relatedBy: .equal, toItem: parent, attribute: .centerY, multiplier: 1, constant: offset)) parent.addConstraint(NSLayoutConstraint(item: child, attribute: .centerY, relatedBy: .equal, toItem: parent, attribute: .centerY, multiplier: 1, constant: offset))
child.updateConstraints()
child.setNeedsLayout() child.setNeedsLayout()
child.layoutIfNeeded() child.layoutIfNeeded()
} }
...@@ -968,12 +998,12 @@ extension UIView { ...@@ -968,12 +998,12 @@ extension UIView {
/// Layout reference. /// Layout reference.
public private(set) var layout: Layout { public private(set) var layout: Layout {
get { get {
return AssociatedObject(base: self, key: &LayoutKey) { return AssociatedObject.get(base: self, key: &LayoutKey) {
return Layout(parent: self) return Layout(parent: self)
} }
} }
set(value) { set(value) {
AssociateObject(base: self, key: &LayoutKey, value: value) AssociatedObject.set(base: self, key: &LayoutKey, value: value)
} }
} }
......
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