Commit 4fa225cb by Daniel Dahan

development: updated Material for changes made to handle Motion

parents cb8adf42 3fb84108
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = 'Material' s.name = 'Material'
s.version = '2.4.12' s.version = '2.4.13'
s.license = 'BSD-3-Clause' s.license = 'BSD-3-Clause'
s.summary = 'An animation and graphics framework for Material Design in Swift.' s.summary = 'An animation and graphics framework for Material Design in Swift.'
s.homepage = 'http://materialswift.com' s.homepage = 'http://materialswift.com'
......
...@@ -8,13 +8,15 @@ Material is an animation and graphics framework for Material Design in Swift. ...@@ -8,13 +8,15 @@ Material is an animation and graphics framework for Material Design in Swift.
* [Download the latest sample](https://github.com/CosmicMind/Samples/tree/master/Graph/CardTableView). * [Download the latest sample](https://github.com/CosmicMind/Samples/tree/master/Graph/CardTableView).
## About Material 2 ## Use Motion With Material
The first version of Material was to bring Material Design to iOS. We considered that a great starting point, but not the entire story. Material 2 is the next chapter, which goes deeper into iOS with refined APIs that simplify Architecture, Photo Library, Reminders, Text Editing, Photo & Video, and much more. In addition to Material Design, we love Apple’s flat UI. Having this in mind, we made it possible to accomplish both UI styles with ease. Motion is a new tool used to create transition animations between view controllers.
## Why A Separate Samples Repo? ## Sample
We moved all sample projects to a separate repo named [Samples](https://github.com/CosmicMind/Samples) to allow their development to be independent of the Material framework. There has been instances where we needed to update the versions of the framework to accommodate changes that only occurred in the sample projects. Take a look at a sample [Photo Collection](https://github.com/CosmicMind/Samples/tree/master/Motion/PhotoCollection) project.
![Motion Photo Collection Sample](http://www.cosmicmind.com/motion/cosmicmind_motion_sample.gif)
## Features ## Features
...@@ -39,17 +41,6 @@ We moved all sample projects to a separate repo named [Samples](https://github.c ...@@ -39,17 +41,6 @@ We moved all sample projects to a separate repo named [Samples](https://github.c
- [x] Sample Projects - [x] Sample Projects
- [x] And More... - [x] And More...
## Releasing January 2017 - Material 2.5.0
- [x] Reminders
- [x] Text Editor
- [x] Toasts
- [x] Dialogs & Alerts
- [x] Bottom Sheets
- [x] Transitions
- [x] Updated Motion API
- [x] And More...
## Requirements ## Requirements
* iOS 8.0+ / Mac OS X 10.9+ * iOS 8.0+ / Mac OS X 10.9+
......
...@@ -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.4.12</string> <string>2.4.13</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
......
/*
* Copyright (C) 2015 - 2016, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
@objc(AnimationFillMode)
public enum AnimationFillMode: Int {
case forwards
case backwards
case both
case removed
}
/**
Converts the AnimationFillMode enum value to a corresponding String.
- Parameter mode: An AnimationFillMode enum value.
*/
public func AnimationFillModeToValue(mode: AnimationFillMode) -> String {
switch mode {
case .forwards:
return kCAFillModeForwards
case .backwards:
return kCAFillModeBackwards
case .both:
return kCAFillModeBoth
case .removed:
return kCAFillModeRemoved
}
}
@objc(AnimationTimingFunction)
public enum AnimationTimingFunction: Int {
case `default`
case linear
case easeIn
case easeOut
case easeInEaseOut
}
/**
Converts the AnimationTimingFunction enum value to a corresponding CAMediaTimingFunction.
- Parameter function: An AnimationTimingFunction enum value.
- Returns: A CAMediaTimingFunction.
*/
public func AnimationTimingFunctionToValue(function: AnimationTimingFunction) -> CAMediaTimingFunction {
switch function {
case .default:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault)
case .linear:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
case .easeIn:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
case .easeOut:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
case .easeInEaseOut:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
}
}
public typealias AnimationDelayCancelBlock = (Bool) -> Void
public struct Animation {
/**
Executes a block of code after a time delay.
- Parameter duration: An animation duration time.
- Parameter animations: An animation block.
- Parameter execute block: A completion block that is executed once
the animations have completed.
*/
@discardableResult
public static func delay(_ time: TimeInterval, execute block: @escaping () -> Void) -> AnimationDelayCancelBlock? {
func asyncAfter(completion: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + time, execute: completion)
}
var cancelable: AnimationDelayCancelBlock?
let delayed: AnimationDelayCancelBlock = {
if !$0 {
DispatchQueue.main.async(execute: block)
}
cancelable = nil
}
cancelable = delayed
asyncAfter {
cancelable?(false)
}
return cancelable;
}
/**
Cancels the delayed AnimationDelayCancelBlock.
- Parameter delayed completion: An AnimationDelayCancelBlock.
*/
public static func cancel(delayed completion: AnimationDelayCancelBlock) {
completion(true)
}
/**
Disables the default animations set on CALayers.
- Parameter animations: A callback that wraps the animations to disable.
*/
public static func disable(animations: (() -> Void)) {
animate(duration: 0, animations: animations)
}
/**
Runs an animation with a specified duration.
- Parameter duration: An animation duration time.
- Parameter animations: An animation block.
- Parameter timingFunction: An AnimationTimingFunction value.
- Parameter completion: A completion block that is executed once
the animations have completed.
*/
public static func animate(duration: CFTimeInterval, timingFunction: AnimationTimingFunction = .easeInEaseOut, animations: (() -> Void), completion: (() -> Void)? = nil) {
CATransaction.begin()
CATransaction.setAnimationDuration(duration)
CATransaction.setCompletionBlock(completion)
CATransaction.setAnimationTimingFunction(AnimationTimingFunctionToValue(function: .easeInEaseOut))
animations()
CATransaction.commit()
}
/**
Creates a CAAnimationGroup.
- Parameter animations: An Array of CAAnimation objects.
- Parameter timingFunction: An AnimationTimingFunction value.
- Parameter duration: An animation duration time for the group.
- Returns: A CAAnimationGroup.
*/
public static func animate(group animations: [CAAnimation], timingFunction: AnimationTimingFunction = .easeInEaseOut, duration: CFTimeInterval = 0.5) -> CAAnimationGroup {
let group = CAAnimationGroup()
group.fillMode = AnimationFillModeToValue(mode: .forwards)
group.isRemovedOnCompletion = false
group.animations = animations
group.duration = duration
group.timingFunction = AnimationTimingFunctionToValue(function: timingFunction)
return group
}
/**
Executes an animation block with a given delay and duration.
- Parameter delay time: A CFTimeInterval.
- Parameter duration: An animation duration time.
- Parameter animations: An animation block.
- Parameter completion: A completion block that is executed once
the animations have completed.
*/
public static func animate(delay time: CFTimeInterval, duration: CFTimeInterval, animations: @escaping (() -> Void), completion: (() -> Void)? = nil) {
delay(time) {
animate(duration: duration, animations: animations, completion: completion)
}
}
}
/*
* Copyright (C) 2015 - 2016, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
public enum AnimationKeyPath: String {
case backgroundColor
case scale = "transform.scale"
case position
case shadowPath
}
extension CABasicAnimation {
/**
A convenience initializer that takes a given AnimationKeyPath.
- Parameter keyPath: An AnimationKeyPath.
*/
public convenience init(keyPath: AnimationKeyPath) {
self.init(keyPath: keyPath.rawValue)
}
}
extension Animation {
/**
Creates a CABasicAnimation for the backgroundColor key path.
- Parameter color: A UIColor.
- Parameter duration: An animation time duration.
- Returns: A CABasicAnimation.
*/
public static func backgroundColor(color: UIColor, duration: CFTimeInterval? = nil) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .backgroundColor)
animation.toValue = color.cgColor
animation.fillMode = AnimationFillModeToValue(mode: .forwards)
animation.isRemovedOnCompletion = false
animation.timingFunction = AnimationTimingFunctionToValue(function: .easeInEaseOut)
if let v = duration {
animation.duration = v
}
return animation
}
/**
Creates a CABasicAnimation for the transform.scale key path.
- Parameter by scale: A CGFloat.
- Parameter duration: An animation time duration.
- Returns: A CABasicAnimation.
*/
public static func scale(by scale: CGFloat, duration: CFTimeInterval? = nil) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .scale)
animation.toValue = scale as NSNumber
animation.fillMode = AnimationFillModeToValue(mode: .forwards)
animation.isRemovedOnCompletion = false
animation.timingFunction = AnimationTimingFunctionToValue(function: .easeInEaseOut)
if let v = duration {
animation.duration = v
}
return animation
}
/**
Creates a CABasicAnimation for the position key path.
- Parameter to point: A CGPoint.
- Parameter duration: An animation time duration.
- Returns: A CABasicAnimation.
*/
public static func position(to point: CGPoint, duration: CFTimeInterval? = nil) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .position)
animation.toValue = NSValue(cgPoint: point)
animation.fillMode = AnimationFillModeToValue(mode: .forwards)
animation.isRemovedOnCompletion = false
animation.timingFunction = AnimationTimingFunctionToValue(function: .easeInEaseOut)
if let v = duration {
animation.duration = v
}
return animation
}
/**
Creates a CABasicAnimation for the shadowPath key path.
- Parameter to path: A CGPath.
- Parameter duration: An animation time duration.
- Returns: A CABasicAnimation.
*/
public static func shadowPath(to path: CGPath, duration: CFTimeInterval? = nil) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .shadowPath)
animation.toValue = path
animation.fillMode = AnimationFillModeToValue(mode: .forwards)
animation.isRemovedOnCompletion = false
animation.timingFunction = AnimationTimingFunctionToValue(function: .linear)
if let v = duration {
animation.duration = v
}
return animation
}
}
...@@ -40,7 +40,7 @@ open class Button: UIButton, Pulseable { ...@@ -40,7 +40,7 @@ open class Button: UIButton, Pulseable {
open let visualLayer = CAShapeLayer() open let visualLayer = CAShapeLayer()
/// A Pulse reference. /// A Pulse reference.
internal var pulse: PulseMotion! internal var pulse: Pulse!
/// PulseAnimation value. /// PulseAnimation value.
open var pulseAnimation: PulseAnimation { open var pulseAnimation: PulseAnimation {
...@@ -188,10 +188,8 @@ open class Button: UIButton, Pulseable { ...@@ -188,10 +188,8 @@ open class Button: UIButton, Pulseable {
from the center. from the center.
*/ */
open func pulse(point: CGPoint? = nil) { open func pulse(point: CGPoint? = nil) {
let p = point ?? center pulse.expand(point: point ?? center)
Animation.delay(0.35) { [weak self] in
pulse.expand(point: p)
Motion.delay(0.35) { [weak self] in
self?.pulse.contract() self?.pulse.contract()
} }
} }
...@@ -261,7 +259,7 @@ extension Button { ...@@ -261,7 +259,7 @@ extension Button {
/// Prepares the pulse motion. /// Prepares the pulse motion.
fileprivate func preparePulse() { fileprivate func preparePulse() {
pulse = PulseMotion(pulseView: self, pulseLayer: visualLayer) pulse = Pulse(pulseView: self, pulseLayer: visualLayer)
} }
/** /**
......
...@@ -41,7 +41,7 @@ open class CollectionReusableView: UICollectionReusableView, Pulseable { ...@@ -41,7 +41,7 @@ open class CollectionReusableView: UICollectionReusableView, Pulseable {
open let visualLayer = CAShapeLayer() open let visualLayer = CAShapeLayer()
/// A Pulse reference. /// A Pulse reference.
internal var pulse: PulseMotion! internal var pulse: Pulse!
/// PulseAnimation value. /// PulseAnimation value.
open var pulseAnimation: PulseAnimation { open var pulseAnimation: PulseAnimation {
...@@ -240,10 +240,8 @@ open class CollectionReusableView: UICollectionReusableView, Pulseable { ...@@ -240,10 +240,8 @@ open class CollectionReusableView: UICollectionReusableView, Pulseable {
from the center. from the center.
*/ */
open func pulse(point: CGPoint? = nil) { open func pulse(point: CGPoint? = nil) {
let p = point ?? center pulse.expand(point: point ?? center)
Animation.delay(0.35) { [weak self] in
pulse.expand(point: p)
Motion.delay(0.35) { [weak self] in
self?.pulse.contract() self?.pulse.contract()
} }
} }
...@@ -298,7 +296,7 @@ open class CollectionReusableView: UICollectionReusableView, Pulseable { ...@@ -298,7 +296,7 @@ open class CollectionReusableView: UICollectionReusableView, Pulseable {
extension CollectionReusableView { extension CollectionReusableView {
/// Prepares the pulse motion. /// Prepares the pulse motion.
fileprivate func preparePulse() { fileprivate func preparePulse() {
pulse = PulseMotion(pulseView: self, pulseLayer: visualLayer) pulse = Pulse(pulseView: self, pulseLayer: visualLayer)
pulseAnimation = .none pulseAnimation = .none
} }
......
...@@ -41,7 +41,7 @@ open class CollectionViewCell: UICollectionViewCell, Pulseable { ...@@ -41,7 +41,7 @@ open class CollectionViewCell: UICollectionViewCell, Pulseable {
open let visualLayer = CAShapeLayer() open let visualLayer = CAShapeLayer()
/// A Pulse reference. /// A Pulse reference.
internal var pulse: PulseMotion! internal var pulse: Pulse!
/// PulseAnimation value. /// PulseAnimation value.
open var pulseAnimation: PulseAnimation { open var pulseAnimation: PulseAnimation {
...@@ -198,10 +198,8 @@ open class CollectionViewCell: UICollectionViewCell, Pulseable { ...@@ -198,10 +198,8 @@ open class CollectionViewCell: UICollectionViewCell, Pulseable {
from the center. from the center.
*/ */
open func pulse(point: CGPoint? = nil) { open func pulse(point: CGPoint? = nil) {
let p = point ?? center pulse.expand(point: point ?? center)
Animation.delay(0.35) { [weak self] in
pulse.expand(point: p)
Motion.delay(0.35) { [weak self] in
self?.pulse.contract() self?.pulse.contract()
} }
} }
...@@ -256,7 +254,7 @@ open class CollectionViewCell: UICollectionViewCell, Pulseable { ...@@ -256,7 +254,7 @@ open class CollectionViewCell: UICollectionViewCell, Pulseable {
extension CollectionViewCell { extension CollectionViewCell {
/// Prepares the pulse motion. /// Prepares the pulse motion.
fileprivate func preparePulse() { fileprivate func preparePulse() {
pulse = PulseMotion(pulseView: self, pulseLayer: visualLayer) pulse = Pulse(pulseView: self, pulseLayer: visualLayer)
} }
/// Prepares the visualLayer property. /// Prepares the visualLayer property.
......
...@@ -172,8 +172,8 @@ public protocol FABMenuDelegate { ...@@ -172,8 +172,8 @@ public protocol FABMenuDelegate {
@objc(FABMenu) @objc(FABMenu)
open class FABMenu: View { open class FABMenu: View {
/// A reference to the SpringMotion object. /// A reference to the SpringAnimation object.
internal let spring = SpringMotion() internal let spring = SpringAnimation()
open var fabMenuDirection: FABMenuDirection { open var fabMenuDirection: FABMenuDirection {
get { get {
......
...@@ -247,7 +247,67 @@ extension CALayer { ...@@ -247,7 +247,67 @@ extension CALayer {
materialLayer.borderWidthPreset = value materialLayer.borderWidthPreset = value
} }
} }
}
/// Grid extension for UIView.
extension CALayer {
/**
A method that accepts CAAnimation objects and executes them on the
view's backing layer.
- Parameter animation: A CAAnimation instance.
*/
open func animate(_ animation: CAAnimation) {
animation.delegate = self
if let a = animation as? CABasicAnimation {
a.fromValue = (presentation() ?? self).value(forKeyPath: a.keyPath!)
}
if let a = animation as? CAPropertyAnimation {
add(a, forKey: a.keyPath!)
} else if let a = animation as? CAAnimationGroup {
add(a, forKey: nil)
} else if let a = animation as? CATransition {
add(a, forKey: kCATransition)
}
}
/**
A delegation method that is executed when the backing layer stops
running an animation.
- Parameter animation: The CAAnimation instance that stopped running.
- Parameter flag: A boolean that indicates if the animation stopped
because it was completed or interrupted. True if completed, false
if interrupted.
*/
open func animationDidStop(_ animation: CAAnimation, finished flag: Bool) {
guard let a = animation as? CAPropertyAnimation else {
if let a = (animation as? CAAnimationGroup)?.animations {
for x in a {
animationDidStop(x, finished: true)
}
}
return
}
guard let b = a as? CABasicAnimation else {
return
}
guard let v = b.toValue else {
return
}
guard let k = b.keyPath else {
return
}
setValue(v, forKeyPath: k)
removeAnimation(forKey: k)
}
}
extension CALayer {
/// Manages the layout for the shape of the view instance. /// Manages the layout for the shape of the view instance.
open func layoutShape() { open func layoutShape() {
guard .none != shapePreset else { guard .none != shapePreset else {
...@@ -257,7 +317,7 @@ extension CALayer { ...@@ -257,7 +317,7 @@ extension CALayer {
if 0 == frame.width { if 0 == frame.width {
frame.size.width = frame.height frame.size.width = frame.height
} }
if 0 == frame.height { if 0 == frame.height {
frame.size.height = frame.width frame.size.height = frame.width
} }
...@@ -274,15 +334,18 @@ extension CALayer { ...@@ -274,15 +334,18 @@ extension CALayer {
guard isShadowPathAutoSizing else { guard isShadowPathAutoSizing else {
return return
} }
if .none == depthPreset { if .none == depthPreset {
shadowPath = nil shadowPath = nil
} else if nil == shadowPath { } else if nil == shadowPath {
shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath
} else { } else {
let a = Motion.shadow(path: UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath) let a = Animation.shadowPath(to: UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath)
a.fromValue = shadowPath a.fromValue = shadowPath
animate(a) animate(a)
} }
} }
} }
@available(iOS 10, *)
extension CALayer: CAAnimationDelegate {}
...@@ -131,10 +131,20 @@ extension UIImage { ...@@ -131,10 +131,20 @@ extension UIImage {
- Returns: A UIImage that is the color passed in. - Returns: A UIImage that is the color passed in.
*/ */
open class func image(with color: UIColor, size: CGSize) -> UIImage? { open class func image(with color: UIColor, size: CGSize) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, Screen.scale)
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: 0.0, y: -size.height)
context.setBlendMode(.multiply)
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill() color.setFill()
UIRectFill(rect) context.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext() let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext() UIGraphicsEndImageContext()
return image?.withRenderingMode(.alwaysOriginal) return image?.withRenderingMode(.alwaysOriginal)
......
...@@ -290,13 +290,12 @@ open class NavigationBar: UINavigationBar { ...@@ -290,13 +290,12 @@ open class NavigationBar: UINavigationBar {
*/ */
open func prepare() { open func prepare() {
barStyle = .black barStyle = .black
isTranslucent = false
depthPreset = .depth1 depthPreset = .depth1
interimSpacePreset = .interimSpace3 interimSpacePreset = .interimSpace3
contentEdgeInsetsPreset = .square1 contentEdgeInsetsPreset = .square1
contentScaleFactor = Screen.scale contentScaleFactor = Screen.scale
backButtonImage = Icon.cm.arrowBack backButtonImage = Icon.cm.arrowBack
let image = UIImage.image(with: .clear, size: CGSize(width: 1, height: 1)) let image = UIImage.image(with: .clear, size: CGSize(width: bounds.width, height: bounds.height))
shadowImage = image shadowImage = image
setBackgroundImage(image, for: .default) setBackgroundImage(image, for: .default)
backgroundColor = .white backgroundColor = .white
......
...@@ -108,7 +108,8 @@ open class NavigationController: UINavigationController { ...@@ -108,7 +108,8 @@ open class NavigationController: UINavigationController {
open override func viewWillLayoutSubviews() { open override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews() super.viewWillLayoutSubviews()
navigationBar.width = view.width navigationBar.setNeedsLayout()
navigationBar.layoutIfNeeded()
} }
/** /**
......
/*
* Copyright (C) 2015 - 2016, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
@objc(PulseAnimation)
public enum PulseAnimation: Int {
case none
case center
case centerWithBacking
case centerRadialBeyondBounds
case radialBeyondBounds
case backing
case point
case pointWithBacking
}
public protocol Pulseable {
/// A reference to the PulseAnimation.
var pulseAnimation: PulseAnimation { get set }
/// A UIColor.
var pulseColor: UIColor { get set }
/// The opcaity value for the pulse animation.
var pulseOpacity: CGFloat { get set }
}
public struct Pulse {
/// A UIView that is Pulseable.
fileprivate weak var pulseView: UIView?
/// The layer the pulse layers are added to.
fileprivate weak var pulseLayer: CALayer?
/// Pulse layers.
fileprivate var layers = [CAShapeLayer]()
/// A reference to the PulseAnimation.
public var animation = PulseAnimation.pointWithBacking
/// A UIColor.
public var color = Color.grey.base
/// The opcaity value for the pulse animation.
public var opacity: CGFloat = 0.18
/**
An initializer that takes a given view and pulse layer.
- Parameter pulseView: An optional UIView.
- Parameter pulseLayer: An optional CALayer.
*/
internal init(pulseView: UIView?, pulseLayer: CALayer?) {
self.pulseView = pulseView
self.pulseLayer = pulseLayer
}
/**
Triggers the expanding animation.
- Parameter point: A point to pulse from.
*/
public mutating func expand(point: CGPoint) {
guard let view = pulseView else {
return
}
guard let layer = pulseLayer else {
return
}
guard .none != animation else {
return
}
let bLayer = CAShapeLayer()
let pLayer = CAShapeLayer()
bLayer.addSublayer(pLayer)
layer.addSublayer(bLayer)
bLayer.zPosition = 0
pLayer.zPosition = 0
layers.insert(bLayer, at: 0)
layer.masksToBounds = !(.centerRadialBeyondBounds == animation || .radialBeyondBounds == animation)
let w = view.bounds.width
let h = view.bounds.height
Animation.disable(animations: { [
n = .center == animation ? w < h ? w : h : w < h ? h : w,
bounds = layer.bounds,
animation = animation,
color = color,
opacity = opacity
] in
bLayer.frame = bounds
pLayer.bounds = CGRect(x: 0, y: 0, width: n, height: n)
switch animation {
case .center, .centerWithBacking, .centerRadialBeyondBounds:
pLayer.position = CGPoint(x: w / 2, y: h / 2)
default:
pLayer.position = point
}
pLayer.cornerRadius = n / 2
pLayer.backgroundColor = color.withAlphaComponent(opacity).cgColor
pLayer.transform = CATransform3DMakeAffineTransform(CGAffineTransform(scaleX: 0, y: 0))
})
bLayer.setValue(false, forKey: "animated")
let duration: CFTimeInterval = .center == animation ? 0.16125 : 0.325
switch animation {
case .centerWithBacking, .backing, .pointWithBacking:
bLayer.add(Animation.backgroundColor(color: color.withAlphaComponent(opacity / 2), duration: duration), forKey: nil)
default:break
}
switch animation {
case .center, .centerWithBacking, .centerRadialBeyondBounds, .radialBeyondBounds, .point, .pointWithBacking:
pLayer.add(Animation.scale(by: 1, duration: duration), forKey: nil)
default:break
}
Animation.delay(duration) {
bLayer.setValue(true, forKey: "animated")
}
}
/// Triggers the contracting animation.
public mutating func contract() {
guard let bLayer = layers.popLast() else {
return
}
guard let animated = bLayer.value(forKey: "animated") as? Bool else {
return
}
Animation.delay(animated ? 0 : 0.15) { [animation = animation, color = color] in
guard let pLayer = bLayer.sublayers?.first as? CAShapeLayer else {
return
}
let duration = 0.325
switch animation {
case .centerWithBacking, .backing, .pointWithBacking:
bLayer.add(Animation.backgroundColor(color: color.withAlphaComponent(0), duration: duration), forKey: nil)
default:break
}
switch animation {
case .center, .centerWithBacking, .centerRadialBeyondBounds, .radialBeyondBounds, .point, .pointWithBacking:
pLayer.add(Animation.animate(group: [
Animation.scale(by: .center == animation ? 1 : 1.325),
Animation.backgroundColor(color: color.withAlphaComponent(0))
], duration: duration), forKey: nil)
default:break
}
Animation.delay(duration) {
pLayer.removeFromSuperlayer()
bLayer.removeFromSuperlayer()
}
}
}
}
...@@ -32,7 +32,7 @@ import UIKit ...@@ -32,7 +32,7 @@ import UIKit
open class PulseView: View, Pulseable { open class PulseView: View, Pulseable {
/// A Pulse reference. /// A Pulse reference.
internal var pulse: PulseMotion! internal var pulse: Pulse!
/// PulseAnimation value. /// PulseAnimation value.
open var pulseAnimation: PulseAnimation { open var pulseAnimation: PulseAnimation {
...@@ -72,10 +72,8 @@ open class PulseView: View, Pulseable { ...@@ -72,10 +72,8 @@ open class PulseView: View, Pulseable {
from the center. from the center.
*/ */
open func pulse(point: CGPoint? = nil) { open func pulse(point: CGPoint? = nil) {
let p = point ?? center pulse.expand(point: point ?? center)
Animation.delay(0.35) { [weak self] in
pulse.expand(point: p)
Motion.delay(0.35) { [weak self] in
self?.pulse.contract() self?.pulse.contract()
} }
} }
...@@ -129,6 +127,6 @@ open class PulseView: View, Pulseable { ...@@ -129,6 +127,6 @@ open class PulseView: View, Pulseable {
extension PulseView { extension PulseView {
/// Prepares the pulse motion. /// Prepares the pulse motion.
fileprivate func preparePulse() { fileprivate func preparePulse() {
pulse = PulseMotion(pulseView: self, pulseLayer: visualLayer) pulse = Pulse(pulseView: self, pulseLayer: visualLayer)
} }
} }
...@@ -122,8 +122,8 @@ open class SnackbarController: RootController { ...@@ -122,8 +122,8 @@ open class SnackbarController: RootController {
- Parameter status: A SnackbarStatus enum value. - Parameter status: A SnackbarStatus enum value.
*/ */
@discardableResult @discardableResult
open func animate(snackbar status: SnackbarStatus, delay: TimeInterval = 0, animations: ((Snackbar) -> Void)? = nil, completion: ((Snackbar) -> Void)? = nil) -> MotionDelayCancelBlock? { open func animate(snackbar status: SnackbarStatus, delay: TimeInterval = 0, animations: ((Snackbar) -> Void)? = nil, completion: ((Snackbar) -> Void)? = nil) -> AnimationDelayCancelBlock? {
return Motion.delay(delay) { [weak self, status = status, animations = animations, completion = completion] in return Animation.delay(delay) { [weak self, status = status, animations = animations, completion = completion] in
guard let s = self else { guard let s = self else {
return return
} }
......
...@@ -38,7 +38,7 @@ public enum SpringDirection: Int { ...@@ -38,7 +38,7 @@ public enum SpringDirection: Int {
case right case right
} }
open class SpringMotion { open class SpringAnimation {
/// A SpringDirection value. /// A SpringDirection value.
open var springDirection = SpringDirection.up open var springDirection = SpringDirection.up
...@@ -99,7 +99,7 @@ open class SpringMotion { ...@@ -99,7 +99,7 @@ open class SpringMotion {
} }
} }
extension SpringMotion { extension SpringAnimation {
/// Disable the Menu if views exist. /// Disable the Menu if views exist.
fileprivate func disable() { fileprivate func disable() {
guard 0 < views.count else { guard 0 < views.count else {
...@@ -122,7 +122,7 @@ extension SpringMotion { ...@@ -122,7 +122,7 @@ extension SpringMotion {
} }
} }
extension SpringMotion { extension SpringAnimation {
/** /**
Expands the Spring component with animation options. Expands the Spring component with animation options.
- Parameter duration: The time for each view's animation. - Parameter duration: The time for each view's animation.
...@@ -182,7 +182,7 @@ extension SpringMotion { ...@@ -182,7 +182,7 @@ extension SpringMotion {
} }
} }
extension SpringMotion { extension SpringAnimation {
/** /**
Handles the animation open completion. Handles the animation open completion.
- Parameter view: A UIView. - Parameter view: A UIView.
...@@ -215,7 +215,7 @@ extension SpringMotion { ...@@ -215,7 +215,7 @@ extension SpringMotion {
} }
} }
extension SpringMotion { extension SpringAnimation {
/** /**
Open the Menu component with animation options in the Up direction. Open the Menu component with animation options in the Up direction.
- Parameter duration: The time for each view's animation. - Parameter duration: The time for each view's animation.
......
...@@ -40,7 +40,7 @@ open class TableViewCell: UITableViewCell, Pulseable { ...@@ -40,7 +40,7 @@ open class TableViewCell: UITableViewCell, Pulseable {
open let visualLayer = CAShapeLayer() open let visualLayer = CAShapeLayer()
/// A Pulse reference. /// A Pulse reference.
internal var pulse: PulseMotion! internal var pulse: Pulse!
/// PulseAnimation value. /// PulseAnimation value.
open var pulseAnimation: PulseAnimation { open var pulseAnimation: PulseAnimation {
...@@ -114,10 +114,8 @@ open class TableViewCell: UITableViewCell, Pulseable { ...@@ -114,10 +114,8 @@ open class TableViewCell: UITableViewCell, Pulseable {
from the center. from the center.
*/ */
open func pulse(point: CGPoint? = nil) { open func pulse(point: CGPoint? = nil) {
let p = point ?? center pulse.expand(point: point ?? center)
Animation.delay(0.35) { [weak self] in
pulse.expand(point: p)
Motion.delay(0.35) { [weak self] in
self?.pulse.contract() self?.pulse.contract()
} }
} }
...@@ -177,7 +175,7 @@ open class TableViewCell: UITableViewCell, Pulseable { ...@@ -177,7 +175,7 @@ open class TableViewCell: UITableViewCell, Pulseable {
extension TableViewCell { extension TableViewCell {
/// Prepares the pulse motion. /// Prepares the pulse motion.
fileprivate func preparePulse() { fileprivate func preparePulse() {
pulse = PulseMotion(pulseView: self, pulseLayer: visualLayer) pulse = Pulse(pulseView: self, pulseLayer: visualLayer)
} }
/// Prepares the visualLayer property. /// Prepares the visualLayer property.
......
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