Commit 4d694db7 by Daniel Dahan

Hero + Motion rework: adding new files and the Hero base framework

parent 9465da01
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
public class AnimationPreprocessor: TransitionPreprocessor {
/// A reference to the Motion instance.
fileprivate var motion: Motion
/// A reference to a MotionContext.
public weak var context: MotionContext!
/**
Initializer that accepts a Motion instance.
- Parameter motion: A Motion instance.
*/
init(motion: Motion) {
self.motion = motion
}
/**
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
public func process(fromViews: [UIView], toViews: [UIView]) {
/*
let inNavigationController = motion.isNavigationController
let inTabBarController = motion.isTabBarController
let toViewController = motion.toViewController
let fromViewController = motion.fromViewController
let presenting = motion.isPresenting
let fromOverFullScreen = motion.isFromViewFullScreen
let toOverFullScreen = motion.isToViewFullScreen
let fromView = motion.fromView
let toView = motion.toView
let animators = motion.animators
*/
}
}
/*
* 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 {
var animator: MotionAnimator?
var snapshot: UIView
var duration: TimeInterval = 0
var targetState: MotionTargetState
// computed
var currentTime: TimeInterval {
return snapshot.layer.convertTime(CACurrentMediaTime(), from: nil)
}
var container: UIView? {
return animator?.context.container
}
class func canAnimate(view: UIView, state: MotionTargetState, appearing: Bool) -> Bool {
return false
}
func apply(state: MotionTargetState) {
}
func resume(timePassed: TimeInterval, reverse: Bool) {
}
func seek(timePassed: TimeInterval) {
}
func clean() {
animator = nil
}
func startAnimations(appearing: Bool) {
}
required init(animator: MotionAnimator, snapshot: UIView, targetState: MotionTargetState) {
self.animator = animator
self.snapshot = snapshot
self.targetState = targetState
}
}
/*
* 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 UIView {
func optimizedDuration(fromPosition: CGPoint, toPosition: CGPoint?, size: CGSize?, transform: CATransform3D?) -> TimeInterval {
let fromPos = fromPosition
let toPos = toPosition ?? fromPos
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) + fromPos
let realToPos = CGPoint.zero.transform(toTransform) + toPos
let realFromSize = fromSize.transform(fromTransform)
let realToSize = toSize.transform(toTransform)
let movePoints = (realFromPos.distance(realToPos) + realFromSize.point.distance(realToSize.point))
// duration is 0.2 @ 0 to 0.375 @ 500
let duration = 0.208 + Double(movePoints.clamp(0, 500)) / 3000
return duration
}
}
protocol HasInsertOrder: class {
var insertToViewFirst: Bool { get set }
}
internal class MotionDefaultAnimator<ViewContext: MotionAnimatorViewContext>: MotionAnimator, HasInsertOrder {
weak public var context: MotionContext!
var viewContexts: [UIView: ViewContext] = [:]
internal var insertToViewFirst = false
public func seekTo(timePassed: TimeInterval) {
for viewContext in viewContexts.values {
viewContext.seek(timePassed: timePassed)
}
}
public func resume(timePassed: TimeInterval, reverse: Bool) -> TimeInterval {
var duration: TimeInterval = 0
for (_, context) in viewContexts {
context.resume(timePassed: timePassed, reverse: reverse)
duration = max(duration, context.duration)
}
return duration
}
public func apply(state: MotionTargetState, to view: UIView) {
if let context = viewContexts[view] {
context.apply(state:state)
}
}
public func canAnimate(view: UIView, appearing: Bool) -> Bool {
guard let state = context[view] else { return false }
return ViewContext.canAnimate(view: view, state: state, appearing: appearing)
}
public func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval {
var duration: TimeInterval = 0
if insertToViewFirst {
for v in toViews { animate(view: v, appearing: true) }
for v in fromViews { animate(view: v, appearing: false) }
} else {
for v in fromViews { animate(view: v, appearing: false) }
for v in toViews { animate(view: v, appearing: true) }
}
for viewContext in viewContexts.values {
duration = max(duration, viewContext.duration)
}
return duration
}
func animate(view: UIView, appearing: Bool) {
let snapshot = context.snapshotView(for: view)
let viewContext = ViewContext(animator:self, snapshot: snapshot, targetState: context[view]!)
viewContexts[view] = viewContext
viewContext.startAnimations(appearing: appearing)
}
public func clean() {
for vc in viewContexts.values {
vc.clean()
}
viewContexts.removeAll()
insertToViewFirst = false
}
}
/*
* 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 {
var viewPropertyAnimator: UIViewPropertyAnimator?
override class func canAnimate(view: UIView, state: MotionTargetState, appearing: Bool) -> Bool {
return view is UIVisualEffectView && state.opacity != nil
}
override func resume(timePassed: TimeInterval, reverse: Bool) {
viewPropertyAnimator?.finishAnimation(at: reverse ? .start : .end)
}
override func seek(timePassed: TimeInterval) {
viewPropertyAnimator?.pauseAnimation()
viewPropertyAnimator?.fractionComplete = CGFloat(timePassed / duration)
}
override func clean() {
super.clean()
viewPropertyAnimator?.stopAnimation(true)
viewPropertyAnimator = nil
}
override func startAnimations(appearing: Bool) {
guard let visualEffectView = snapshot as? UIVisualEffectView else { return }
let appearedEffect = visualEffectView.effect
let disappearedEffect = targetState.opacity == 0 ? nil : visualEffectView.effect
visualEffectView.effect = appearing ? disappearedEffect : appearedEffect
duration = targetState.duration!
viewPropertyAnimator = UIViewPropertyAnimator(duration: duration, curve: .easeInOut) {
visualEffectView.effect = appearing ? 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
#if os(iOS)
public class MotionDebugPlugin: MotionPlugin {
public static var showOnTop: Bool = false
var debugView: MotionDebugView?
var zPositionMap = [UIView: CGFloat]()
var addedLayers: [CALayer] = []
var updating = false
override public func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval {
if Motion.shared.forceNotInteractive { return 0 }
var hasArc = false
for v in context.fromViews + context.toViews where context[v]?.arc != nil && context[v]?.position != nil {
hasArc = true
break
}
let debugView = MotionDebugView(initialProcess: Motion.shared.presenting ? 0.0 : 1.0, showCurveButton:hasArc, showOnTop:MotionDebugPlugin.showOnTop)
debugView.frame = Motion.shared.container.bounds
debugView.delegate = self
UIApplication.shared.keyWindow!.addSubview(debugView)
debugView.layoutSubviews()
self.debugView = debugView
UIView.animate(withDuration: 0.4) {
debugView.showControls = true
}
return .infinity
}
public override func resume(timePassed: TimeInterval, reverse: Bool) -> TimeInterval {
guard let debugView = debugView else { return 0.4 }
debugView.delegate = nil
UIView.animate(withDuration: 0.4) {
debugView.showControls = false
debugView.debugSlider.setValue(roundf(debugView.progress), animated: true)
}
on3D(wants3D: false)
return 0.4
}
public override func clean() {
debugView?.removeFromSuperview()
debugView = nil
}
}
extension MotionDebugPlugin:MotionDebugViewDelegate {
public func onDone() {
guard let debugView = debugView else { return }
let seekValue = Motion.shared.presenting ? debugView.progress : 1.0 - debugView.progress
if seekValue > 0.5 {
Motion.shared.end()
} else {
Motion.shared.cancel()
}
}
public func onProcessSliderChanged(progress: Float) {
let seekValue = Motion.shared.presenting ? progress : 1.0 - progress
Motion.shared.update(progress: Double(seekValue))
}
func onPerspectiveChanged(translation: CGPoint, rotation: CGFloat, scale: CGFloat) {
var t = CATransform3DIdentity
t.m34 = -1 / 4000
t = CATransform3DTranslate(t, translation.x, translation.y, 0)
t = CATransform3DScale(t, scale, scale, 1)
t = CATransform3DRotate(t, rotation, 0, 1, 0)
Motion.shared.container.layer.sublayerTransform = t
}
func animateZPosition(view: UIView, to: CGFloat) {
let a = CABasicAnimation(keyPath: "zPosition")
a.fromValue = view.layer.value(forKeyPath: "zPosition")
a.toValue = NSNumber(value: Double(to))
a.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
a.duration = 0.4
view.layer.add(a, forKey: "zPosition")
view.layer.zPosition = to
}
func onDisplayArcCurve(wantsCurve: Bool) {
for layer in addedLayers {
layer.removeFromSuperlayer()
addedLayers.removeAll()
}
if wantsCurve {
for layer in Motion.shared.container.layer.sublayers! {
for (_, anim) in layer.animations {
if let keyframeAnim = anim as? CAKeyframeAnimation, let path = keyframeAnim.path {
let s = CAShapeLayer()
s.zPosition = layer.zPosition + 10
s.path = path
s.strokeColor = UIColor.blue.cgColor
s.fillColor = UIColor.clear.cgColor
Motion.shared.container.layer.addSublayer(s)
addedLayers.append(s)
}
}
}
}
}
func on3D(wants3D: Bool) {
var t = CATransform3DIdentity
if wants3D {
var viewsWithZPosition = Set<UIView>()
for view in Motion.shared.container.subviews where view.layer.zPosition != 0 {
viewsWithZPosition.insert(view)
zPositionMap[view] = view.layer.zPosition
}
let viewsWithoutZPosition = Motion.shared.container.subviews.filter { return !viewsWithZPosition.contains($0) }
let viewsWithPositiveZPosition = viewsWithZPosition.filter { return $0.layer.zPosition > 0 }
for (i, v) in viewsWithoutZPosition.enumerated() {
animateZPosition(view:v, to:CGFloat(i * 10))
}
var maxZPosition: CGFloat = 0
for v in viewsWithPositiveZPosition {
maxZPosition = max(maxZPosition, v.layer.zPosition)
animateZPosition(view:v, to:v.layer.zPosition + CGFloat(viewsWithoutZPosition.count * 10))
}
t.m34 = -1 / 4000
t = CATransform3DTranslate(t, debugView!.translation.x, debugView!.translation.y, 0)
t = CATransform3DScale(t, debugView!.scale, debugView!.scale, 1)
t = CATransform3DRotate(t, debugView!.rotation, 0, 1, 0)
} else {
for v in Motion.shared.container.subviews {
animateZPosition(view:v, to:self.zPositionMap[v] ?? 0)
}
self.zPositionMap.removeAll()
}
let a = CABasicAnimation(keyPath: "sublayerTransform")
a.fromValue = Motion.shared.container.layer.value(forKeyPath: "sublayerTransform")
a.toValue = NSValue(caTransform3D: t)
a.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
a.duration = 0.4
UIView.animate(withDuration:0.4) {
self.context.container.backgroundColor = UIColor(white: 0.85, alpha: 1.0)
}
Motion.shared.container.layer.add(a, forKey: "debug")
Motion.shared.container.layer.sublayerTransform = t
}
}
#endif
/*
* 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
#if os(iOS)
protocol MotionDebugViewDelegate: class {
func onProcessSliderChanged(progress: Float)
func onPerspectiveChanged(translation: CGPoint, rotation: CGFloat, scale: CGFloat)
func on3D(wants3D: Bool)
func onDisplayArcCurve(wantsCurve: Bool)
func onDone()
}
class MotionDebugView: UIView {
var backgroundView: UIView!
var debugSlider: UISlider!
var perspectiveButton: UIButton!
var doneButton: UIButton!
var arcCurveButton: UIButton?
weak var delegate: MotionDebugViewDelegate?
var panGR: UIPanGestureRecognizer!
var pinchGR: UIPinchGestureRecognizer!
var showControls: Bool = false {
didSet {
layoutSubviews()
}
}
var showOnTop: Bool = false
var rotation: CGFloat = π / 6
var scale: CGFloat = 0.6
var translation: CGPoint = .zero
var progress: Float {
return debugSlider.value
}
init(initialProcess: Float, showCurveButton: Bool, showOnTop: Bool) {
super.init(frame:.zero)
self.showOnTop = showOnTop
backgroundView = UIView(frame:.zero)
backgroundView.backgroundColor = UIColor(white: 1.0, alpha: 0.95)
backgroundView.layer.shadowColor = UIColor.darkGray.cgColor
backgroundView.layer.shadowOpacity = 0.3
backgroundView.layer.shadowRadius = 5
backgroundView.layer.shadowOffset = CGSize.zero
addSubview(backgroundView)
doneButton = UIButton(type: .system)
doneButton.setTitle("Done", for: .normal)
doneButton.addTarget(self, action: #selector(onDone), for: .touchUpInside)
backgroundView.addSubview(doneButton)
perspectiveButton = UIButton(type: .system)
perspectiveButton.setTitle("3D View", for: .normal)
perspectiveButton.addTarget(self, action: #selector(onPerspective), for: .touchUpInside)
backgroundView.addSubview(perspectiveButton)
if showCurveButton {
arcCurveButton = UIButton(type: .system)
arcCurveButton!.setTitle("Show Arcs", for: .normal)
arcCurveButton!.addTarget(self, action: #selector(onDisplayArcCurve), for: .touchUpInside)
backgroundView.addSubview(arcCurveButton!)
}
debugSlider = UISlider(frame: .zero)
debugSlider.layer.zPosition = 1000
debugSlider.minimumValue = 0
debugSlider.maximumValue = 1
debugSlider.addTarget(self, action: #selector(onSlide), for: .valueChanged)
debugSlider.isUserInteractionEnabled = true
debugSlider.value = initialProcess
backgroundView.addSubview(debugSlider)
panGR = UIPanGestureRecognizer(target: self, action: #selector(pan))
panGR.delegate = self
panGR.maximumNumberOfTouches = 1
addGestureRecognizer(panGR)
pinchGR = UIPinchGestureRecognizer(target: self, action: #selector(pinch))
pinchGR.delegate = self
addGestureRecognizer(pinchGR)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func layoutSubviews() {
super.layoutSubviews()
var backgroundFrame = bounds
backgroundFrame.size.height = 72
if showOnTop {
backgroundFrame.origin.y = showControls ? 0 : -80
} else {
backgroundFrame.origin.y = bounds.maxY - CGFloat(showControls ? 72.0 : -8.0)
}
backgroundView.frame = backgroundFrame
var sliderFrame = bounds.insetBy(dx: 10, dy: 0)
sliderFrame.size.height = 44
sliderFrame.origin.y = 28
debugSlider.frame = sliderFrame
perspectiveButton.sizeToFit()
perspectiveButton.frame.origin = CGPoint(x:bounds.maxX - perspectiveButton.bounds.width - 10, y: 4)
doneButton.sizeToFit()
doneButton.frame.origin = CGPoint(x:10, y: 4)
arcCurveButton?.sizeToFit()
arcCurveButton?.center = CGPoint(x: center.x, y: doneButton.center.y)
}
var startRotation: CGFloat = 0
@objc public func pan() {
if panGR.state == .began {
startRotation = rotation
}
rotation = startRotation + panGR.translation(in: nil).x / 150
if rotation > π {
rotation -= 2 * π
} else if rotation < -π {
rotation += 2 * π
}
delegate?.onPerspectiveChanged(translation:translation, rotation: rotation, scale:scale)
}
var startLocation: CGPoint = .zero
var startTranslation: CGPoint = .zero
var startScale: CGFloat = 1
@objc public func pinch() {
switch pinchGR.state {
case .began:
startLocation = pinchGR.location(in: nil)
startTranslation = translation
startScale = scale
fallthrough
case .changed:
if pinchGR.numberOfTouches >= 2 {
scale = min(1, max(0.2, startScale * pinchGR.scale))
translation = startTranslation + pinchGR.location(in: nil) - startLocation
delegate?.onPerspectiveChanged(translation:translation, rotation: rotation, scale:scale)
}
default:
break
}
}
@objc public func onDone() {
delegate?.onDone()
}
@objc public func onPerspective() {
perspectiveButton.isSelected = !perspectiveButton.isSelected
delegate?.on3D(wants3D: perspectiveButton.isSelected)
}
@objc public func onDisplayArcCurve() {
arcCurveButton!.isSelected = !arcCurveButton!.isSelected
delegate?.onDisplayArcCurve(wantsCurve: arcCurveButton!.isSelected)
}
@objc public func onSlide() {
delegate?.onProcessSliderChanged(progress: debugSlider.value)
}
}
extension MotionDebugView:UIGestureRecognizerDelegate {
public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return perspectiveButton.isSelected
}
}
#endif
// The MIT License (MIT)
//
// 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 DurationPreprocessor: TransitionPreprocessor {
/// A reference to a MotionContext.
public weak var context: MotionContext!
/**
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
public func process(fromViews: [UIView], toViews: [UIView]) {
var duration: TimeInterval = 0
duration = applyOptimizedDuration(for: fromViews)
duration = max(duration, applyOptimizedDuration(for: toViews))
set(duration: duration, for: fromViews)
set(duration: duration, for: toViews)
}
}
extension DurationPreprocessor {
fileprivate func set(duration: TimeInterval, for views: [UIView]) {
for view in views where .infinity == context.viewToMotionTransitionState[view]?.duration {
context.viewToMotionTransitionState[view]?.duration = duration
}
}
fileprivate func applyOptimizedDuration(for views: [UIView]) -> TimeInterval {
var duration: TimeInterval = 0
for v in views {
guard var state = context.viewToMotionTransitionState[v] else {
continue
}
if state.duration == nil {
state.duration = optimizedDuration(for: v)
}
if state.duration! == .infinity {
duration = max(duration, optimizedDuration(for: v))
} else {
duration = max(duration, state.duration!)
}
}
return duration
}
fileprivate func optimizedDuration(for view: UIView) -> TimeInterval {
guard let state = context.viewToMotionTransitionState[view] else {
return 0
}
return view.optimizedDuration(fromPosition: context.container.convert(view.layer.position, from: view.superview), toPosition: state.position, size: state.size, transform: state.transform)
}
}
/*
* 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 CALayer {
// return all animations running by this layer.
// the returned value is mutable
var animations: [(String, CAAnimation)] {
if let keys = animationKeys() {
return keys.map { return ($0, self.animation(forKey: $0)!.copy() as! CAAnimation) }
}
return []
}
}
/*
* 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 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.275)
static func from(name: String) -> CAMediaTimingFunction? {
switch name {
case "linear":
return .linear
case "easeIn":
return .easeIn
case "easeOut":
return .easeOut
case "easeInOut":
return .easeInOut
case "standard":
return .standard
case "deceleration":
return .deceleration
case "acceleration":
return .acceleration
case "sharp":
return .sharp
default:
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 MetalKit
let π = CGFloat.pi
internal struct KeySet<Key: Hashable, Value: Hashable> {
var dict: [Key:Set<Value>] = [:]
internal subscript(key: Key) -> Set<Value> {
mutating get {
if dict[key] == nil {
dict[key] = Set<Value>()
}
return dict[key]!
}
set {
dict[key] = newValue
}
}
}
internal extension CGSize {
internal var center: CGPoint {
return CGPoint(x: width / 2, y: height / 2)
}
internal var point: CGPoint {
return CGPoint(x: width, y: height)
}
internal func transform(_ t: CGAffineTransform) -> CGSize {
return self.applying(t)
}
internal func transform(_ t: CATransform3D) -> CGSize {
return self.applying(CATransform3DGetAffineTransform(t))
}
}
internal extension CGRect {
internal var center: CGPoint {
return CGPoint(x: origin.x + size.width/2, y: origin.y + size.height/2)
}
internal var bounds: CGRect {
return CGRect(origin: CGPoint.zero, size: size)
}
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 {
internal func clamp(_ a: CGFloat, _ b: CGFloat) -> CGFloat {
return self < a ? a : (self > b ? b : self)
}
}
extension CGPoint {
internal func translate(_ dx: CGFloat, dy: CGFloat) -> CGPoint {
return CGPoint(x: self.x+dx, y: self.y+dy)
}
internal func transform(_ t: CGAffineTransform) -> CGPoint {
return self.applying(t)
}
internal func transform(_ t: CATransform3D) -> CGPoint {
return self.applying(CATransform3DGetAffineTransform(t))
}
internal func distance(_ b: CGPoint) -> CGFloat {
return sqrt(pow(self.x - b.x, 2) + pow(self.y - b.y, 2))
}
}
internal func + (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
internal func - (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x - right.x, y: left.y - right.y)
}
internal func / (left: CGPoint, right: CGFloat) -> CGPoint {
return CGPoint(x: left.x/right, y: left.y/right)
}
internal func / (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x/right.x, y: left.y/right.y)
}
internal func * (left: CGPoint, right: CGFloat) -> CGPoint {
return CGPoint(x: left.x*right, y: left.y*right)
}
internal func * (left: CGPoint, right: CGSize) -> CGPoint {
return CGPoint(x: left.x*right.width, y: left.y*right.width)
}
internal func * (left: CGFloat, right: CGPoint) -> CGPoint {
return right * left
}
internal func * (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x*right.x, y: left.y*right.y)
}
internal prefix func - (point: CGPoint) -> CGPoint {
return CGPoint.zero - point
}
internal func abs(_ p: CGPoint) -> CGPoint {
return CGPoint(x: abs(p.x), y: abs(p.y))
}
internal func * (left: CGSize, right: CGFloat) -> CGSize {
return CGSize(width: left.width*right, height: left.height*right)
}
internal func * (left: CGSize, right: CGSize) -> CGSize {
return CGSize(width: left.width*right.width, height: left.height*right.width)
}
internal func / (left: CGSize, right: CGSize) -> CGSize {
return CGSize(width: left.width/right.width, height: left.height/right.height)
}
internal func == (lhs: CATransform3D, rhs: CATransform3D) -> Bool {
var lhs = lhs
var rhs = rhs
return memcmp(&lhs, &rhs, MemoryLayout<CATransform3D>.size) == 0
}
internal func != (lhs: CATransform3D, rhs: CATransform3D) -> Bool {
return !(lhs == rhs)
}
/*
* 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
func delay(_ time: Double, execute: @escaping () -> Void) {
if time > 0 {
DispatchQueue.main.asyncAfter(deadline: .now() + time, execute: execute)
} else {
DispatchQueue.main.async(execute: execute)
}
}
/*
* 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 modifiersRegex = "(\\w+)(?:\\(([^\\)]*)\\))?"
internal extension NSObject {
func copyWithArchiver() -> Any? {
return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: self))!
}
}
internal extension UIImage {
class func imageWithView(view: UIView) -> UIImage {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, 0.0)
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img!
}
}
internal extension UIColor {
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)
}
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
public extension UIView {
private struct AssociatedKeys {
static var motionID = "motionID"
static var motionModifiers = "motionModifers"
static var motionStoredAlpha = "motionStoredAlpha"
static var motionEnabled = "motionEnabled"
}
/**
**motionID** is the identifier for the view. When doing a transition between two view controllers,
Motion will search through all the subviews for both view controllers and matches views with the same **motionID**.
Whenever a pair is discovered,
Motion will automatically transit the views from source state to the destination state.
*/
@IBInspectable public var motionID: String? {
get { return objc_getAssociatedObject(self, &AssociatedKeys.motionID) as? String }
set { objc_setAssociatedObject(self, &AssociatedKeys.motionID, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
/**
**isMotionEnabled** allows to specify whether a view and its subviews should be consider for animations.
If true, Motion will search through all the subviews for motionIds and modifiers. Defaults to true
*/
@IBInspectable public var isMotionEnabled: Bool {
get { return objc_getAssociatedObject(self, &AssociatedKeys.motionEnabled) as? Bool ?? true }
set { objc_setAssociatedObject(self, &AssociatedKeys.motionEnabled, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
/**
Use **motionModifiers** to specify animations alongside the main transition. Checkout `MotionTransition.swift` for available modifiers.
*/
public var motionModifiers: [MotionTransition]? {
get { return objc_getAssociatedObject(self, &AssociatedKeys.motionModifiers) as? [MotionTransition] }
set { objc_setAssociatedObject(self, &AssociatedKeys.motionModifiers, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
/**
**motionModifierString** provides another way to set **motionModifiers**. It can be assigned through storyboard.
*/
@IBInspectable public var motionModifierString: String? {
get { fatalError("Reverse lookup is not supported") }
set { motionModifiers = newValue?.parse() }
}
internal 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
}
internal 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 })
}
/// Used for .overFullScreen presentation
internal var motionStoredAlpha: CGFloat? {
get {
if let doubleValue = (objc_getAssociatedObject(self, &AssociatedKeys.motionStoredAlpha) as? NSNumber)?.doubleValue {
return CGFloat(doubleValue)
}
return nil
}
set {
if let newValue = newValue {
objc_setAssociatedObject(self, &AssociatedKeys.motionStoredAlpha, NSNumber(value:newValue.native), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
} else {
objc_setAssociatedObject(self, &AssociatedKeys.motionStoredAlpha, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
}
/*
* 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 {
func get(_ index: Int) -> Element? {
if index < count {
return self[index]
}
return nil
}
}
internal extension Array where Element: ExprNode {
func getCGFloat(_ index: Int) -> CGFloat? {
if let s = get(index) as? NumberNode {
return CGFloat(s.value)
}
return nil
}
func getDouble(_ index: Int) -> Double? {
if let s = get(index) as? NumberNode {
return Double(s.value)
}
return nil
}
func getFloat(_ index: Int) -> Float? {
if let s = get(index) as? NumberNode {
return s.value
}
return nil
}
func getBool(_ index: Int) -> Bool? {
if let s = get(index) as? VariableNode, let f = Bool(s.name) {
return f
}
return nil
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
public class MatchPreprocessor: TransitionPreprocessor {
/// A reference to a MotionContext.
public weak var context: MotionContext!
/**
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
public func process(fromViews: [UIView], toViews: [UIView]) {
for tv in toViews {
guard let identifier = tv.motionIdentifier else {
return
}
guard let fv = context.sourceIdentifierToView[identifier] else {
continue
}
var tvState = context.viewToMotionTransitionState[tv] ?? MotionTransitionState()
var fvState = context.viewToMotionTransitionState[fv] ?? MotionTransitionState()
if let v = tvState.startStateIfMatched {
tvState.append(.startWith(animations: v))
}
if let v = fvState.startStateIfMatched {
fvState.append(.startWith(animations: v))
}
fvState.motionIdentifier = identifier
fvState.arc = tvState.arc
fvState.duration = tvState.duration
fvState.timingFunction = tvState.timingFunction
fvState.delay = tvState.delay
fvState.spring = tvState.spring
tvState.motionIdentifier = identifier
tvState.opacity = 0
if !fv.isOpaque || fv.alpha < 1 || !tv.isOpaque || tv.alpha < 1 {
fvState.opacity = 0
} else {
fvState.opacity = nil
if !fv.layer.masksToBounds && fvState.displayShadow {
tvState.displayShadow = false
}
}
context.viewToMotionTransitionState[tv] = tvState
context.viewToMotionTransitionState[fv] = fvState
}
}
}
/*
* 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 MetalKit
internal struct KeySet<Key: Hashable, Value: Hashable> {
/// A reference to the dictionary storing the key / Set pairs.
fileprivate var dictionary = [Key: Set<Value>]()
/**
Subscript for matching keys and returning the corresponding set.
- Parameter key: A Key type.
- Returns: A Set<Value> type.
*/
subscript(key: Key) -> Set<Value> {
mutating get {
if nil == dictionary[key] {
dictionary[key] = Set<Value>()
}
return dictionary[key]!
}
set(value) {
dictionary[key] = value
}
}
}
internal extension CGSize {
internal var center: CGPoint {
return CGPoint(x: width / 2, y: height / 2)
}
internal var point: CGPoint {
return CGPoint(x: width, y: height)
}
internal func transform(_ t: CGAffineTransform) -> CGSize {
return applying(t)
}
internal func transform(_ t: CATransform3D) -> CGSize {
return applying(CATransform3DGetAffineTransform(t))
}
}
internal extension CGRect {
var center: CGPoint {
return CGPoint(x: origin.x + size.width / 2, y: origin.y + size.height / 2)
}
var bounds: CGRect {
return CGRect(origin: CGPoint.zero, size: size)
}
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)
}
}
internal extension CGFloat {
func clamp(_ a: CGFloat, _ b: CGFloat) -> CGFloat {
return self < a ? a : self > b ? b : self
}
}
internal extension CGPoint {
func translate(_ dx: CGFloat, dy: CGFloat) -> CGPoint {
return CGPoint(x: x + dx, y: y + dy)
}
func transform(_ t: CGAffineTransform) -> CGPoint {
return applying(t)
}
func transform(_ t: CATransform3D) -> CGPoint {
return applying(CATransform3DGetAffineTransform(t))
}
func distance(_ b: CGPoint) -> CGFloat {
return sqrt(pow(x - b.x, 2) + pow(y - b.y, 2))
}
}
internal func +(left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
internal func -(left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x - right.x, y: left.y - right.y)
}
internal func /(left: CGPoint, right: CGFloat) -> CGPoint {
return CGPoint(x: left.x / right, y: left.y / right)
}
internal func /(left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x / right.x, y: left.y / right.y)
}
internal func *(left: CGPoint, right: CGFloat) -> CGPoint {
return CGPoint(x: left.x * right, y: left.y * right)
}
internal func *(left: CGPoint, right: CGSize) -> CGPoint {
return CGPoint(x: left.x * right.width, y: left.y * right.width)
}
internal func *(left: CGFloat, right: CGPoint) -> CGPoint {
return right * left
}
internal func *(left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x * right.x, y: left.y * right.y)
}
internal prefix func -(point: CGPoint) -> CGPoint {
return CGPoint.zero - point
}
internal func abs(_ p: CGPoint) -> CGPoint {
return CGPoint(x: abs(p.x), y: abs(p.y))
}
internal func *(left: CGSize, right: CGFloat) -> CGSize {
return CGSize(width: left.width * right, height: left.height * right)
}
internal func *(left: CGSize, right: CGSize) -> CGSize {
return CGSize(width: left.width * right.width, height: left.height * right.width)
}
internal func /(left: CGSize, right: CGSize) -> CGSize {
return CGSize(width: left.width / right.width, height: left.height / right.height)
}
internal func == (lhs: CATransform3D, rhs: CATransform3D) -> Bool {
var lhs = lhs
var rhs = rhs
return memcmp(&lhs, &rhs, MemoryLayout<CATransform3D>.size) == 0
}
internal func != (lhs: CATransform3D, rhs: CATransform3D) -> Bool {
return !(lhs == rhs)
}
/*
* 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.
*/
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.
*/
public 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.
*/
public static func set<T: Any>(base: Any, key: UnsafePointer<UInt8>, value: T) {
objc_setAssociatedObject(base, key, value, .OBJC_ASSOCIATION_RETAIN)
}
}
/*
* 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 MotionInstanceControllerKey: UInt8 = 0
fileprivate struct MotionInstanceController {
/// A boolean indicating whether Motion is enabled.
fileprivate var isEnabled: Bool
/// An optional reference to the current snapshot.
fileprivate var snapshot: UIView?
/// An optional reference to the previous UINavigationControllerDelegate.
fileprivate var previousNavigationDelegate: UINavigationControllerDelegate?
/// An optional reference to the previous UITabBarControllerDelegate.
fileprivate var previousTabBarDelegate: UITabBarControllerDelegate?
}
extension UIViewController {
/// MotionInstanceController reference.
fileprivate var motionControllerInstance: MotionInstanceController {
get {
return AssociatedObject.get(base: self, key: &MotionInstanceControllerKey) {
return MotionInstanceController(isEnabled: false, snapshot: nil, previousNavigationDelegate: nil, previousTabBarDelegate: nil)
}
}
set(value) {
AssociatedObject.set(base: self, key: &MotionInstanceControllerKey, value: value)
}
}
/// A boolean that indicates whether motion is enabled.
@IBInspectable
public var isMotionEnabled: Bool {
get {
return transitioningDelegate is Motion
}
set {
guard newValue != isMotionEnabled else {
return
}
if newValue {
transitioningDelegate = Motion.shared
if let v = self as? UINavigationController {
motionPreviousNavigationDelegate = v.delegate
v.delegate = Motion.shared
}
if let v = self as? UITabBarController {
motionPreviousTabBarDelegate = v.delegate
v.delegate = Motion.shared
}
} else {
transitioningDelegate = nil
if let v = self as? UINavigationController, v.delegate is Motion {
v.delegate = motionPreviousNavigationDelegate
}
if let v = self as? UITabBarController, v.delegate is Motion {
v.delegate = motionPreviousTabBarDelegate
}
}
}
}
/// An optional reference to the current snapshot.
internal var motionSnapshot: UIView? {
get {
return motionControllerInstance.snapshot
}
set(value) {
motionControllerInstance.snapshot = value
}
}
/// An optional reference to the previous UINavigationControllerDelegate.
internal var motionPreviousNavigationDelegate: UINavigationControllerDelegate? {
get {
return motionControllerInstance.previousNavigationDelegate
}
set(value) {
motionControllerInstance.previousNavigationDelegate = value
}
}
/// An optional reference to the previous UITabBarControllerDelegate.
internal var motionPreviousTabBarDelegate: UITabBarControllerDelegate? {
get {
return motionControllerInstance.previousTabBarDelegate
}
set(value) {
motionControllerInstance.previousTabBarDelegate = value
}
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
public enum MotionAnimation {
case delay(TimeInterval)
case timingFunction(MotionAnimationTimingFunction)
case duration(TimeInterval)
case custom(CABasicAnimation)
case backgroundColor(UIColor)
case barTintColor(UIColor)
case borderColor(UIColor)
case borderWidth(CGFloat)
case cornerRadius(CGFloat)
case transform(CATransform3D)
case rotationAngle(CGFloat)
case rotationAngleX(CGFloat)
case rotationAngleY(CGFloat)
case rotationAngleZ(CGFloat)
case spin(CGFloat)
case spinX(CGFloat)
case spinY(CGFloat)
case spinZ(CGFloat)
case scale(CGFloat)
case scaleX(CGFloat)
case scaleY(CGFloat)
case scaleZ(CGFloat)
case translate(x: CGFloat, y: CGFloat)
case translateX(CGFloat)
case translateY(CGFloat)
case translateZ(CGFloat)
case x(CGFloat)
case y(CGFloat)
case point(x: CGFloat, y: CGFloat)
case position(x: CGFloat, y: CGFloat)
case fade(Double)
case zPosition(CGFloat)
case width(CGFloat)
case height(CGFloat)
case size(width: CGFloat, height: CGFloat)
case shadowPath(CGPath)
case shadowColor(UIColor)
case shadowOffset(CGSize)
case shadowOpacity(Float)
case shadowRadius(CGFloat)
case depth(shadowOffset: CGSize, shadowOpacity: Float, shadowRadius: CGFloat)
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
@objc(MotionAnimationFillMode)
public enum MotionAnimationFillMode: Int {
case forwards
case backwards
case both
case removed
}
/**
Converts the MotionAnimationFillMode enum value to a corresponding String.
- Parameter mode: An MotionAnimationFillMode enum value.
*/
public func MotionAnimationFillModeToValue(mode: MotionAnimationFillMode) -> String {
switch mode {
case .forwards:
return kCAFillModeForwards
case .backwards:
return kCAFillModeBackwards
case .both:
return kCAFillModeBoth
case .removed:
return kCAFillModeRemoved
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
@objc(MotionAnimationTimingFunction)
public enum MotionAnimationTimingFunction: Int {
case `default`
case linear
case easeIn
case easeOut
case easeInEaseOut
}
/**
Converts the MotionAnimationTimingFunction enum value to a corresponding CAMediaTimingFunction.
- Parameter function: An MotionAnimationTimingFunction enum value.
- Returns: A CAMediaTimingFunction.
*/
public func MotionAnimationTimingFunctionToValue(timingFunction: MotionAnimationTimingFunction) -> CAMediaTimingFunction {
switch timingFunction {
case .default:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault)
case .linear:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
case .easeIn:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
case .easeOut:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
case .easeInEaseOut:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
}
}
/*
* 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.
*/
public enum MotionCascadeDirection {
case topToBottom
case bottomToTop
case leftToRight
case rightToLeft
}
/*
* 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.
*/
public enum MotionCoordinateSpace {
case global
case local
case parent
}
/*
* 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 {
public override init() {
super.init()
}
public func transition(rootView: UIView, fromViews: [UIView], toViews: [UIView], completion: ((Bool) -> Void)? = nil) {
transitionContainer = rootView
completionCallback = completion
prepareForTransition()
context.defaultCoordinateSpace = .sameParent
context.set(fromViews: fromViews, toViews: toViews)
processContext()
prepareForAnimation()
animate()
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
public protocol MotionObserver {
/**
An update method called when an animation elapsed time is updated
during a transition.
- Parameter elapsedTime: A TimeInterval.
*/
func update(elapsedTime: TimeInterval)
}
public protocol MotionSubscriber {
/// A reference to an Array of MotionObservers.
var observers: [MotionObserver] { get }
}
/*
* 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 modifiers for views
- Parameters:
- context: object holding all parsed and changed modifiers,
- 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 modifiers:
context[view]
context[view, "modifierName"]
To set a view's modifiers:
context[view] = [("modifier1", ["parameter1"]), ("modifier2", [])]
context[view, "modifier1"] = ["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 modifiers,
- view: the view to check whether or not the plugin can handle the animation
- appearing: true if the view is appearing(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, appearing: 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 modifiers,
- 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:
- timePassed: time of the animation to seek to.
*/
open func seekTo(timePassed: TimeInterval) {}
/**
For supporting interactive animation only.
This method is called when an interactive animation is ended
The plugin should resume the animation.
- Parameters:
- timePassed: will be the same value since last `seekTo`
- reverse: a boolean value indicating whether or not the animation should reverse
*/
open func resume(timePassed: TimeInterval, reverse: Bool) -> TimeInterval { return 0 }
/**
For supporting interactive animation only.
This method is called when user wants to override animation modifiers during an interactive animation
- Parameters:
- state: the target state to override
- view: the view to override
*/
open func apply(state: MotionTargetState, 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)
}
}
/*
* 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.
*/
public enum MotionSnapshot {
case optimized
case normal
case layerRender
case none
}
/*
* 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 MotionStringConvertible {
static func from(node: ExprNode) -> Self?
}
extension String {
func parse<T: MotionStringConvertible>() -> [T]? {
let lexer = Lexer(input: self)
let parser = Parser(tokens: lexer.tokenize())
do {
let nodes = try parser.parse()
var results = [T]()
for node in nodes {
if let modifier = T.from(node: node) {
results.append(modifier)
} else {
print("\(node.name) doesn't exist in \(T.self)")
}
}
return results
} catch let error {
print("failed to parse \"\(self)\", error: \(error)")
}
return nil
}
func parseOne<T: MotionStringConvertible>() -> T? {
return parse()?.last
}
}
/*
* 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 {
/// Will optimize for different type 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
/// will not create snapshot. animate the view directly.
/// This will mess up the view hierarchy, therefore, view controllers have to rebuild
/// its view structure after the transition finishes
case noSnapshot
}
public enum MotionCoordinateSpace {
case global
case local
case sameParent
}
public struct MotionTargetState {
class MotionTargetStateWrapper {
var state: MotionTargetState
init(state: MotionTargetState) {
self.state = state
}
}
internal var beginState: MotionTargetStateWrapper?
public var beginStateIfMatched: [MotionTransition]?
public var position: CGPoint?
public var size: CGSize?
public var transform: CATransform3D?
public var opacity: Float?
public var cornerRadius: CGFloat?
public var backgroundColor: CGColor?
public var zPosition: CGFloat?
public var contentsRect: CGRect?
public var contentsScale: CGFloat?
public var borderWidth: CGFloat?
public var borderColor: CGColor?
public var shadowColor: CGColor?
public var shadowOpacity: Float?
public var shadowOffset: CGSize?
public var shadowRadius: CGFloat?
public var shadowPath: CGPath?
public var masksToBounds: Bool?
public var displayShadow: Bool = true
public var overlay: (color: CGColor, opacity: CGFloat)?
public var spring: (CGFloat, CGFloat)?
public var delay: TimeInterval = 0
public var duration: TimeInterval?
public var timingFunction: CAMediaTimingFunction?
public var arc: CGFloat?
public var source: String?
public var cascade: (TimeInterval, CascadeDirection, Bool)?
public var ignoreSubviewModifiers: Bool?
public var coordinateSpace: MotionCoordinateSpace?
public var useScaleBasedSizeChange: Bool?
public var snapshotType: MotionSnapshotType?
public var nonFade: Bool = false
public var forceAnimate: Bool = false
public var custom: [String:Any]?
init(modifiers: [MotionTransition]) {
append(contentsOf: modifiers)
}
public mutating func append(_ modifier: MotionTransition) {
modifier.apply(&self)
}
public mutating func append(contentsOf modifiers: [MotionTransition]) {
for modifier in modifiers {
modifier.apply(&self)
}
}
/**
- Returns: custom item for a specific key
*/
public subscript(key: String) -> Any? {
get {
return custom?[key]
}
set {
if custom == nil {
custom = [:]
}
custom![key] = newValue
}
}
}
extension MotionTargetState: ExpressibleByArrayLiteral {
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
/// used to construct MotionTransition from motionModifierString
extension MotionTransition: MotionStringConvertible {
public static func from(node: ExprNode) -> MotionTransition? {
let name: String = node.name
let parameters: [ExprNode] = (node as? CallNode)?.arguments ?? []
switch name {
case "fade":
return .fade
case "opacity":
return MotionTransition.opacity(parameters.getFloat(0) ?? 1)
case "position":
return .position(CGPoint(x: parameters.getCGFloat(0) ?? 0, y: parameters.getCGFloat(1) ?? 0))
case "size":
return .size(CGSize(width: parameters.getCGFloat(0) ?? 0, height: parameters.getCGFloat(1) ?? 0))
case "scale":
if parameters.count == 1 {
return .scale(parameters.getCGFloat(0) ?? 1)
} else {
return .scale(x: parameters.getCGFloat(0) ?? 1,
y: parameters.getCGFloat(1) ?? 1,
z: parameters.getCGFloat(2) ?? 1)
}
case "rotate":
if parameters.count == 1 {
return .rotate(parameters.getCGFloat(0) ?? 0)
} else {
return .rotate(x: parameters.getCGFloat(0) ?? 0,
y: parameters.getCGFloat(1) ?? 0,
z: parameters.getCGFloat(2) ?? 0)
}
case "translate":
return .translate(x: parameters.getCGFloat(0) ?? 0,
y: parameters.getCGFloat(1) ?? 0,
z: parameters.getCGFloat(2) ?? 0)
case "overlay":
return .overlay(color: UIColor(red: parameters.getCGFloat(0) ?? 1,
green: parameters.getCGFloat(1) ?? 1,
blue: parameters.getCGFloat(2) ?? 1,
alpha: 1),
opacity: parameters.getCGFloat(3) ?? 1)
case "duration":
if let duration = parameters.getDouble(0) {
return .duration(duration)
}
case "durationMatchLongest":
return .durationMatchLongest
case "delay":
if let delay = parameters.getDouble(0) {
return .delay(delay)
}
case "spring":
if #available(iOS 9, *) {
return .spring(stiffness: parameters.getCGFloat(0) ?? 250, damping: parameters.getCGFloat(1) ?? 30)
}
case "timingFunction":
if let c1 = parameters.getFloat(0),
let c2 = parameters.getFloat(1),
let c3 = parameters.getFloat(2),
let c4 = parameters.getFloat(3) {
return .timingFunction(CAMediaTimingFunction(controlPoints: c1, c2, c3, c4))
} else if let name = parameters.get(0)?.name, let timingFunction = CAMediaTimingFunction.from(name:name) {
return .timingFunction(timingFunction)
}
case "arc":
return .arc(intensity: parameters.getCGFloat(0) ?? 1)
case "cascade":
var cascadeDirection = CascadeDirection.topToBottom
if let directionString = parameters.get(1)?.name,
let direction = CascadeDirection(directionString) {
cascadeDirection = direction
}
return .cascade(delta: parameters.getDouble(0) ?? 0.02, direction: cascadeDirection, delayMatchedViews:parameters.getBool(2) ?? false)
case "source":
if let motionID = parameters.get(0)?.name {
return .source(motionID: motionID)
}
case "useGlobalCoordinateSpace":
return .useGlobalCoordinateSpace
case "useSameParentCoordinateSpace":
return .useSameParentCoordinateSpace
case "ignoreSubviewModifiers":
return .ignoreSubviewModifiers(recursive:parameters.getBool(0) ?? false)
case "zPosition":
if let zPosition = parameters.getCGFloat(0) {
return .zPosition(zPosition)
}
case "useOptimizedSnapshot":
return .useOptimizedSnapshot
case "useNormalSnapshot":
return .useNormalSnapshot
case "useLayerRenderSnapshot":
return .useLayerRenderSnapshot
case "useNoSnapshot":
return .useNoSnapshot
case "forceAnimate":
return .forceAnimate
default: break
}
return nil
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
public protocol MotionTransitionAnimator: class {
/// A reference to a MotionContext.
weak var context: MotionContext! { get set }
/**
*/
func canAnimate(view: UIView, isAppearing: Bool) -> Bool
/**
*/
func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval
/**
*/
func seekTo(elapsedTime: TimeInterval)
/**
*/
func resume(elapsedTime: TimeInterval, isReversed: Bool) -> TimeInterval
///
func clean()
func apply(motionTransitions: [MotionTransition], to view: UIView)
}
/*
* 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
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 {
/// An optional reference to the start state of the view.
internal var startState: MotionTransitionStateWrapper?
/// A reference to the motion identifier.
public var motionIdentifier: String?
public var startStateIfMatched: [MotionTransition]?
public var position: CGPoint?
public var size: CGSize?
public var transform: CATransform3D?
public var opacity: Float?
public var cornerRadius: CGFloat?
public var backgroundColor: CGColor?
public var zPosition: CGFloat?
public var contentsRect: CGRect?
public var contentsScale: CGFloat?
public var borderWidth: CGFloat?
public var borderColor: CGColor?
public var shadowColor: CGColor?
public var shadowOpacity: Float?
public var shadowOffset: CGSize?
public var shadowRadius: CGFloat?
public var shadowPath: CGPath?
public var masksToBounds: Bool?
public var displayShadow: Bool = true
public var overlay: (color: CGColor, opacity: CGFloat)?
public var spring: (CGFloat, CGFloat)?
public var delay: TimeInterval = 0
public var duration: TimeInterval?
public var timingFunction: MotionAnimationTimingFunction?
public var arc: CGFloat?
public var cascade: (TimeInterval, MotionCascadeDirection, Bool)?
public var ignoreSubviewTransitionAnimations: Bool?
public var coordinateSpace: MotionCoordinateSpace?
public var useScaleBasedSizeChange: Bool?
public var motionSnapshot: MotionSnapshot?
public var forceAnimate: Bool = false
public var custom: [String:Any]?
init(transitionAnimations: [MotionTransition]) {
append(contentsOf: transitionAnimations)
}
public mutating func append(_ transitionAnimations: MotionTransition) {
transitionAnimations.apply(&self)
}
public mutating func append(contentsOf transitionAnimations: [MotionTransition]) {
for v in transitionAnimations {
v.apply(&self)
}
}
}
extension MotionTransitionState: ExpressibleByArrayLiteral {
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 protocol MotionPreprocessor: class {
weak var context: MotionContext! { get set }
func process(fromViews: [UIView], toViews: [UIView])
}
public protocol MotionAnimator: class {
weak var context: MotionContext! { get set }
func canAnimate(view: UIView, appearing: Bool) -> Bool
func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval
func clean()
func seekTo(timePassed: TimeInterval)
func resume(timePassed: TimeInterval, reverse: Bool) -> TimeInterval
func apply(state: MotionTargetState, to view: UIView)
}
public protocol MotionProgressUpdateObserver {
func motionDidUpdateProgress(progress: Double)
}
@objc public protocol MotionViewControllerDelegate {
@objc optional func motionWillStartAnimatingFrom(viewController: UIViewController)
@objc optional func motionDidEndAnimatingFrom(viewController: UIViewController)
@objc optional func motionDidCancelAnimatingFrom(viewController: UIViewController)
@objc optional func motionWillStartTransition()
@objc optional func motionDidEndTransition()
@objc optional func motionDidCancelTransition()
@objc optional func motionWillStartAnimatingTo(viewController: UIViewController)
@objc optional func motionDidEndAnimatingTo(viewController: UIViewController)
@objc optional func motionDidCancelAnimatingTo(viewController: UIViewController)
}
//
// Lexer.swift
// Kaleidoscope
//
// Created by Matthew Cheok on 15/11/15.
// Copyright © 2015 Matthew Cheok. All rights reserved.
//
import Foundation
public enum Token {
case identifier(String, CountableRange<Int>)
case number(Float, CountableRange<Int>)
case parensOpen(CountableRange<Int>)
case parensClose(CountableRange<Int>)
case comma(CountableRange<Int>)
case other(String, CountableRange<Int>)
}
typealias TokenGenerator = (String, CountableRange<Int>) -> Token?
let tokenList: [(String, TokenGenerator)] = [
("[ \t\n]", { _ in nil }),
("[a-zA-Z][a-zA-Z0-9]*", { .identifier($0, $1) }),
("\\-?[0-9.]+", { .number(Float($0)!, $1) }),
("\\(", { .parensOpen($1) }),
("\\)", { .parensClose($1) }),
(",", { .comma($1) })
]
public class Lexer {
let input: String
public init(input: String) {
self.input = input
}
public func tokenize() -> [Token] {
var tokens = [Token]()
var content = input
while !content.characters.isEmpty {
var matched = false
for (pattern, generator) in tokenList {
if let (m, r) = content.match(regex: pattern) {
if let t = generator(m, r) {
tokens.append(t)
}
content = content.substring(from: content.index(content.startIndex, offsetBy: m.characters.count))
matched = true
break
}
}
if !matched {
let index = content.index(content.startIndex, offsetBy: 1)
let intIndex = content.distance(from: content.startIndex, to: index)
tokens.append(.other(content.substring(to: index), intIndex..<intIndex+1))
content = content.substring(from: index)
}
}
return tokens
}
}
//
// Nodes.swift
// Kaleidoscope
//
// Created by Matthew Cheok on 15/11/15.
// Copyright © 2015 Matthew Cheok. All rights reserved.
//
import Foundation
public class ExprNode: CustomStringConvertible, Equatable {
public var range: CountableRange<Int> = 0..<0
public let name: String
public var description: String {
return "ExprNode(name: \"\(name)\")"
}
public init(name: String) {
self.name = name
}
}
public func == (lhs: ExprNode, rhs: ExprNode) -> Bool {
return lhs.description == rhs.description
}
public class NumberNode: ExprNode {
public let value: Float
public override var description: String {
return "NumberNode(value: \(value))"
}
public init(value: Float) {
self.value = value
super.init(name: "\(value)")
}
}
public class VariableNode: ExprNode {
public override var description: String {
return "VariableNode(name: \"\(name)\")"
}
}
public class BinaryOpNode: ExprNode {
public let lhs: ExprNode
public let rhs: ExprNode
public override var description: String {
return "BinaryOpNode(name: \"\(name)\", lhs: \(lhs), rhs: \(rhs))"
}
public init(name: String, lhs: ExprNode, rhs: ExprNode) {
self.lhs = lhs
self.rhs = rhs
super.init(name: "\(name)")
}
}
public class CallNode: ExprNode {
public let arguments: [ExprNode]
public override var description: String {
return "CallNode(name: \"\(name)\", arguments: \(arguments))"
}
public init(name: String, arguments: [ExprNode]) {
self.arguments = arguments
super.init(name: "\(name)")
}
}
public class PrototypeNode: ExprNode {
public let argumentNames: [String]
public override var description: String {
return "PrototypeNode(name: \"\(name)\", argumentNames: \(argumentNames))"
}
public init(name: String, argumentNames: [String]) {
self.argumentNames = argumentNames
super.init(name: "\(name)")
}
}
public class FunctionNode: ExprNode {
public let prototype: PrototypeNode
public let body: ExprNode
public override var description: String {
return "FunctionNode(prototype: \(prototype), body: \(body))"
}
public init(prototype: PrototypeNode, body: ExprNode) {
self.prototype = prototype
self.body = body
super.init(name: "\(prototype.name)")
}
}
//
// Parser.swift
// Kaleidoscope
//
// Created by Matthew Cheok on 15/11/15.
// Copyright © 2015 Matthew Cheok. All rights reserved.
//
import Foundation
public enum ParseError: Error {
case unexpectToken
case undefinedOperator(String)
case expectCharacter(Character)
case expectExpression
case expectArgumentList
case expectFunctionName
}
public class Parser {
let tokens: [Token]
var index = 0
public init(tokens: [Token]) {
self.tokens = tokens
}
func peekCurrentToken() -> Token {
if index >= tokens.count {
return .other("", 0..<0)
}
return tokens[index]
}
@discardableResult func popCurrentToken() -> Token {
defer { index += 1 }
return tokens[index]
}
func parseNumber() throws -> ExprNode {
guard case let .number(value, _) = popCurrentToken() else {
throw ParseError.unexpectToken
}
return NumberNode(value: value)
}
func parseExpression() throws -> ExprNode {
let node = try parsePrimary()
return try parseBinaryOp(node: node)
}
func parseParens() throws -> ExprNode {
guard case .parensOpen = popCurrentToken() else {
throw ParseError.expectCharacter("(")
}
let exp = try parseExpression()
guard case .parensClose = popCurrentToken() else {
throw ParseError.expectCharacter(")")
}
return exp
}
func parseIdentifier() throws -> ExprNode {
guard case let .identifier(name, _) = popCurrentToken() else {
throw ParseError.unexpectToken
}
guard case .parensOpen = peekCurrentToken() else {
return VariableNode(name: name)
}
popCurrentToken()
var arguments = [ExprNode]()
if case .parensClose = peekCurrentToken() {
} else {
while true {
let argument = try parseExpression()
arguments.append(argument)
if case .parensClose = peekCurrentToken() {
break
}
guard case .comma = popCurrentToken() else {
throw ParseError.expectArgumentList
}
}
}
popCurrentToken()
return CallNode(name: name, arguments: arguments)
}
func parsePrimary() throws -> ExprNode {
switch peekCurrentToken() {
case .identifier:
return try parseIdentifier()
case .number:
return try parseNumber()
case .parensOpen:
return try parseParens()
default:
throw ParseError.expectExpression
}
}
let operatorPrecedence: [String: Int] = [
"+": 20,
"-": 20,
"*": 40,
"/": 40
]
func getCurrentTokenPrecedence() throws -> Int {
guard index < tokens.count else {
return -1
}
guard case let .other(op, _) = peekCurrentToken() else {
return -1
}
guard let precedence = operatorPrecedence[op] else {
throw ParseError.undefinedOperator(op)
}
return precedence
}
func parseBinaryOp(node: ExprNode, exprPrecedence: Int = 0) throws -> ExprNode {
var lhs = node
while true {
let tokenPrecedence = try getCurrentTokenPrecedence()
if tokenPrecedence < exprPrecedence {
return lhs
}
guard case let .other(op, _) = popCurrentToken() else {
throw ParseError.unexpectToken
}
var rhs = try parsePrimary()
let nextPrecedence = try getCurrentTokenPrecedence()
if tokenPrecedence < nextPrecedence {
rhs = try parseBinaryOp(node: rhs, exprPrecedence: tokenPrecedence+1)
}
lhs = BinaryOpNode(name: op, lhs: lhs, rhs: rhs)
}
}
public func parse() throws -> [ExprNode] {
index = 0
var nodes = [ExprNode]()
while index < tokens.count {
let expr = try parsePrimary()
nodes.append(expr)
}
return nodes
}
}
//
// Regex.swift
// Kaleidoscope
//
// Created by Matthew Cheok on 15/11/15.
// Copyright © 2015 Matthew Cheok. All rights reserved.
//
import Foundation
var expressions = [String: NSRegularExpression]()
public extension String {
public func match(regex: String) -> (String, CountableRange<Int>)? {
let expression: NSRegularExpression
if let exists = expressions[regex] {
expression = exists
} else {
do {
expression = try NSRegularExpression(pattern: "^\(regex)", options: [])
expressions[regex] = expression
} catch {
return nil
}
}
let range = expression.rangeOfFirstMatch(in: self, options: [], range: NSRange(0 ..< self.utf16.count))
if range.location != NSNotFound {
return ((self as NSString).substring(with: range), range.location ..< range.location + range.length )
}
return nil
}
}
//
// CascadeEffect.swift
/*
* 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 BasePreprocessor: MotionPreprocessor {
weak public var context: MotionContext!
func process(fromViews: [UIView], toViews: [UIView]) {}
}
//
// CascadeEffect.swift
/*
* 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)
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) }
}
}
init?(_ string: String) {
switch string {
case "bottomToTop":
self = .bottomToTop
case "leftToRight":
self = .leftToRight
case "rightToLeft":
self = .rightToLeft
case "topToBottom":
self = .topToBottom
default:
return nil
}
}
}
class CascadePreprocessor: BasePreprocessor {
override func process(fromViews: [UIView], toViews: [UIView]) {
process(views:fromViews)
process(views:toViews)
}
func process(views: [UIView]) {
for view in views {
guard let (deltaTime, direction, delayMatchedViews) = context[view]?.cascade else { continue }
var parentView = view
if view is UITableView, let wrapperView = view.subviews.get(0) {
parentView = wrapperView
}
let sortedSubviews = parentView.subviews.sorted(by: direction.comparator)
let initialDelay = context[view]!.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.pairedView(for: view) == nil {
context[view]?.delay = delay
} else if delayMatchedViews, let paired = context.pairedView(for: view) {
context[view]?.delay = finalDelay
context[paired]?.delay = finalDelay
}
for subview in view.subviews {
applyDelay(view: subview)
}
}
applyDelay(view: subview)
}
}
}
}
//
// DurationPreprocessor.swift
// Motion
//
// Created by Luke on 3/16/17.
// Copyright © 2017 Luke Zhao. All rights reserved.
//
import UIKit
class DurationPreprocessor: BasePreprocessor {
override 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)
}
func optimizedDurationFor(view: UIView) -> TimeInterval {
let targetState = context[view]!
return view.optimizedDuration(fromPosition: context.container.convert(view.layer.position, from: view.superview),
toPosition: targetState.position,
size: targetState.size,
transform: targetState.transform)
}
func applyOptimizedDurationIfNoDuration(views: [UIView]) -> TimeInterval {
var maxDuration: TimeInterval = 0
for view in views where context[view] != nil {
if context[view]?.duration == nil {
context[view]!.duration = optimizedDurationFor(view: view)
}
if context[view]!.duration! == .infinity {
maxDuration = max(maxDuration, optimizedDurationFor(view: view))
} else {
maxDuration = max(maxDuration, context[view]!.duration!)
}
}
return maxDuration
}
func setDurationForInfiniteDuration(views: [UIView], duration: TimeInterval) {
for view in views where context[view]?.duration == .infinity {
context[view]!.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 IgnoreSubviewModifiersPreprocessor: BasePreprocessor {
override func process(fromViews: [UIView], toViews: [UIView]) {
process(views:fromViews)
process(views:toViews)
}
func process(views: [UIView]) {
for view in views {
guard let recursive = context[view]?.ignoreSubviewModifiers else { continue }
var parentView = view
if view is UITableView, let wrapperView = view.subviews.get(0) {
parentView = wrapperView
}
if recursive {
cleanSubviewModifiers(parentView)
} else {
for subview in parentView.subviews {
context[subview] = nil
}
}
}
}
private func cleanSubviewModifiers(_ parentView: UIView) {
for view in parentView.subviews {
context[view] = nil
cleanSubviewModifiers(view)
}
}
}
/*
* 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: BasePreprocessor {
override func process(fromViews: [UIView], toViews: [UIView]) {
for tv in toViews {
guard let id = tv.motionID, let fv = context.sourceView(for: id) else { continue }
var tvState = context[tv] ?? MotionTargetState()
var fvState = context[fv] ?? MotionTargetState()
if let beginStateIfMatched = tvState.beginStateIfMatched {
tvState.append(.beginWith(modifiers: beginStateIfMatched))
}
if let beginStateIfMatched = fvState.beginStateIfMatched {
fvState.append(.beginWith(modifiers: beginStateIfMatched))
}
// match is just a two-way source effect
tvState.source = id
fvState.source = id
fvState.arc = tvState.arc
fvState.duration = tvState.duration
fvState.timingFunction = tvState.timingFunction
fvState.delay = tvState.delay
fvState.spring = tvState.spring
tvState.opacity = 0
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
class SourcePreprocessor: BasePreprocessor {
override func process(fromViews: [UIView], toViews: [UIView]) {
for fv in fromViews {
guard let id = context[fv]?.source,
let tv = context.destinationView(for: id) else { continue }
prepareFor(view: fv, targetView: tv)
}
for tv in toViews {
guard let id = context[tv]?.source,
let fv = context.sourceView(for: id) else { continue }
prepareFor(view: tv, targetView: fv)
}
}
func prepareFor(view: UIView, 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
}
}
// The MIT License (MIT)
//
// 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 SourcePreprocessor: TransitionPreprocessor {
/// A reference to a MotionContext.
public weak var context: MotionContext!
/**
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
public func process(fromViews: [UIView], toViews: [UIView]) {
prepare(fromViews: fromViews)
prepare(toViews: toViews)
}
}
extension SourcePreprocessor {
/**
Prepares the from views.
- Parameter fromViews: An Array of UIViews.
*/
fileprivate func prepare(fromViews: [UIView]) {
for fv in fromViews {
guard let identifier = context.viewToMotionTransitionState[fv]?.motionIdentifier else {
continue
}
guard let tv = context.destinationIdentifierToView[identifier] else {
continue
}
prepare(view: fv, for: tv)
}
}
/**
Prepares the to views.
- Parameter fromViews: An Array of UIViews.
*/
fileprivate func prepare(toViews: [UIView]) {
for tv in context.toViews {
guard let identifier = context.viewToMotionTransitionState[tv]?.motionIdentifier else {
continue
}
guard let fv = context.sourceIdentifierToView[identifier] else {
continue
}
prepare(view: tv, for: fv)
}
}
fileprivate func prepare(view: UIView, for targetView: UIView) {
guard var state = context.viewToMotionTransitionState[view] else {
return
}
state.coordinateSpace = .global
state.position = context.container.convert(targetView.layer.position, from: targetView.superview!)
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.viewToMotionTransitionState[view] = state
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
public protocol TransitionPreprocessor: class {
/// A reference to a MotionContext.
weak var context: MotionContext! { get set }
/**
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
func process(fromViews: [UIView], toViews: [UIView])
}
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