Skip to content

Commit

Permalink
Merge pull request #53 from OrkhanAlikhanov/bug-fixes
Browse files Browse the repository at this point in the history
Bug fixes and MotionViewTransition
  • Loading branch information
DanielDahan authored Sep 24, 2018
2 parents 6d02346 + 64b831e commit 4a8fa6a
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 68 deletions.
10 changes: 10 additions & 0 deletions Motion.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@
96E409AB1F24F7570015A2B5 /* MotionPreprocessor.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96E409621F24F7370015A2B5 /* MotionPreprocessor.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96E409AC1F24F7570015A2B5 /* SourcePreprocessor.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96E409631F24F7370015A2B5 /* SourcePreprocessor.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96E409AD1F24F7570015A2B5 /* TransitionPreprocessor.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96E409641F24F7370015A2B5 /* TransitionPreprocessor.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9D72470D21557F7000C04B48 /* MotionViewTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D72470C21557F7000C04B48 /* MotionViewTransition.swift */; };
9D7247132158A15E00C04B48 /* MotionViewTransitionAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D7247122158A15E00C04B48 /* MotionViewTransitionAnimator.swift */; };
D4A2ECE521467454003162B4 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A2ECE421467454003162B4 /* Application.swift */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -137,6 +139,8 @@
96E409641F24F7370015A2B5 /* TransitionPreprocessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionPreprocessor.swift; sourceTree = "<group>"; };
96E409BB1F24FC210015A2B5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
96E409BC1F24FC300015A2B5 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
9D72470C21557F7000C04B48 /* MotionViewTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionViewTransition.swift; sourceTree = "<group>"; };
9D7247122158A15E00C04B48 /* MotionViewTransitionAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionViewTransitionAnimator.swift; sourceTree = "<group>"; };
D4A2ECE421467454003162B4 /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -164,7 +168,9 @@
96E4093B1F24F7370015A2B5 /* Sources */,
96C98DD21E424AB000B22906 /* Products */,
);
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
};
96C98DD21E424AB000B22906 /* Products */ = {
isa = PBXGroup;
Expand Down Expand Up @@ -192,6 +198,7 @@
96E4095A1F24F7370015A2B5 /* MotionModifier.swift */,
96E4095C1F24F7370015A2B5 /* MotionTargetState.swift */,
96E4095B1F24F7370015A2B5 /* MotionTransitionObserver.swift */,
9D72470C21557F7000C04B48 /* MotionViewTransition.swift */,
96E4093C1F24F7370015A2B5 /* Animator */,
96E409431F24F7370015A2B5 /* Extensions */,
96E4095D1F24F7370015A2B5 /* Preprocessors */,
Expand All @@ -208,6 +215,7 @@
96E4093E1F24F7370015A2B5 /* MotionAnimatorViewContext.swift */,
96E4093F1F24F7370015A2B5 /* MotionCoreAnimationViewContext.swift */,
96E409421F24F7370015A2B5 /* MotionViewPropertyViewContext.swift */,
9D7247122158A15E00C04B48 /* MotionViewTransitionAnimator.swift */,
);
path = Animator;
sourceTree = "<group>";
Expand Down Expand Up @@ -358,6 +366,7 @@
965FE9931FE43DE10098BDD0 /* MotionTransition+UINavigationControllerDelegate.swift in Sources */,
96E409861F24F7370015A2B5 /* MotionPreprocessor.swift in Sources */,
96E409821F24F7370015A2B5 /* CascadePreprocessor.swift in Sources */,
9D7247132158A15E00C04B48 /* MotionViewTransitionAnimator.swift in Sources */,
96E4096C1F24F7370015A2B5 /* Motion+CALayer.swift in Sources */,
96E409781F24F7370015A2B5 /* MotionCAAnimation.swift in Sources */,
96E409811F24F7370015A2B5 /* MotionTargetState.swift in Sources */,
Expand All @@ -376,6 +385,7 @@
965FE9691FDDA1F20098BDD0 /* MotionViewOrderStrategy.swift in Sources */,
965FE97A1FE1D83D0098BDD0 /* MotionTransition.swift in Sources */,
965FE98D1FE334E10098BDD0 /* MotionTransition+UIViewControllerTransitioningDelegate.swift in Sources */,
9D72470D21557F7000C04B48 /* MotionViewTransition.swift in Sources */,
96E409871F24F7370015A2B5 /* SourcePreprocessor.swift in Sources */,
965FE9A11FE43EF80098BDD0 /* MotionTransition+CustomTransition.swift in Sources */,
96E409701F24F7370015A2B5 /* Motion+UIKit.swift in Sources */,
Expand Down
5 changes: 0 additions & 5 deletions Sources/Animator/MotionAnimatorViewContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,6 @@ internal class MotionAnimatorViewContext {
/// Animation duration time.
var duration: TimeInterval = 0

/// The computed current time of the snapshot layer.
var currentTime: TimeInterval {
return snapshot.layer.convertTime(CACurrentMediaTime(), from: nil)
}

/// A container view for the transition.
var container: UIView? {
return animator?.motion.context.container
Expand Down
5 changes: 5 additions & 0 deletions Sources/Animator/MotionCoreAnimationViewContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
/// A reference to the animation timing function.
fileprivate var timingFunction = CAMediaTimingFunction.standard

/// The computed current time of the snapshot layer.
var currentTime: TimeInterval {
return snapshot.layer.convertTime((animator as! MotionCoreAnimator<MotionCoreAnimationViewContext>).currentTime, from: nil)
}

/// Current animations.
var animations = [(CALayer, String, CAAnimation)]()

Expand Down
57 changes: 51 additions & 6 deletions Sources/Animator/MotionCoreAnimator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,29 @@
import UIKit

internal class MotionCoreAnimator<T: MotionAnimatorViewContext>: MotionAnimator {
/**
Backing field for storing CACurrentMediaTime to ensure all
animations begin at the exact same time.

Should be invalidated using invalidateCurrentTime method
after firing all animations.
*/
private var storedCurrentTime: TimeInterval?

/// Current time for the animator.
var currentTime: TimeInterval {
if nil == storedCurrentTime {
storedCurrentTime = CACurrentMediaTime()
}

return storedCurrentTime!
}

/// Invalidates stored current time.
func invalidateCurrentTime() {
storedCurrentTime = nil
}

weak public var motion: MotionTransition!

/// A reference to the MotionContext.
Expand All @@ -46,6 +69,7 @@ internal class MotionCoreAnimator<T: MotionAnimatorViewContext>: MotionAnimator
}

viewToContexts.removeAll()
invalidateCurrentTime()
}

/**
Expand All @@ -55,7 +79,7 @@ internal class MotionCoreAnimator<T: MotionAnimatorViewContext>: MotionAnimator
view is appearing.
*/
func canAnimate(view: UIView, isAppearing: Bool) -> Bool {
guard let state = context[view] else {
guard let state = targetState(for: view) else {
return false
}

Expand Down Expand Up @@ -103,6 +127,8 @@ internal class MotionCoreAnimator<T: MotionAnimatorViewContext>: MotionAnimator
d = max(d, v.startAnimations())
}

invalidateCurrentTime()

return d
}

Expand All @@ -114,6 +140,8 @@ internal class MotionCoreAnimator<T: MotionAnimatorViewContext>: MotionAnimator
for v in viewToContexts.values {
v.seek(to: progress)
}

invalidateCurrentTime()
}

/**
Expand All @@ -127,13 +155,10 @@ internal class MotionCoreAnimator<T: MotionAnimatorViewContext>: MotionAnimator
var duration: TimeInterval = 0

for (_, v) in viewToContexts {
if nil == v.targetState.duration {
v.duration = max(v.duration, v.snapshot.optimizedDuration(targetState: v.targetState) + progress)
}

duration = max(duration, v.resume(at: progress, isReversed: isReversed))
}

invalidateCurrentTime()
return duration
}

Expand All @@ -148,6 +173,26 @@ internal class MotionCoreAnimator<T: MotionAnimatorViewContext>: MotionAnimator
}

v.apply(state: state)

invalidateCurrentTime()
}

/**
Returns MotionTargetState for the given view.
- Parameter for view: A UIView.
- Returns: A MotionTargetState.
*/
func targetState(for view: UIView) -> MotionTargetState? {
return context[view]
}

/**
Returns snapshot view for the given view.
- Parameter for view: A UIView.
- Returns: A snapshot UIView.
*/
func snapshotView(for view: UIView) -> UIView {
return context.snapshotView(for: view)
}
}

Expand All @@ -159,7 +204,7 @@ fileprivate extension MotionCoreAnimator {
view is appearing.
*/
func createViewContext(view: UIView, isAppearing: Bool) {
viewToContexts[view] = T(animator: self, snapshot: context.snapshotView(for: view), targetState: context[view]!, isAppearing: isAppearing)
viewToContexts[view] = T(animator: self, snapshot: snapshotView(for: view), targetState: targetState(for: view)!, isAppearing: isAppearing)
}
}

Expand Down
50 changes: 50 additions & 0 deletions Sources/Animator/MotionViewTransitionAnimator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* The MIT License (MIT)
*
* Copyright (C) 2018, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

import UIKit

internal class MotionViewTransitionAnimator: MotionCoreAnimator<MotionCoreAnimationViewContext> {
/**
Returns MotionTargetState for the given view.
- Parameter for view: A UIView.
- Returns: A MotionTargetState.
*/
override func targetState(for view: UIView) -> MotionTargetState? {
guard let modifiers = view.motionModifiers else {
return nil
}

return MotionTargetState(modifiers: modifiers)
}

/**
Returns snapshot view for the given view.
- Parameter for view: A UIView.
- Returns: A snapshot UIView.
*/
override func snapshotView(for view: UIView) -> UIView {
return view
}
}
70 changes: 18 additions & 52 deletions Sources/Extensions/Motion+CALayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@

import UIKit

@available(iOS 10, *)
extension CALayer: CAAnimationDelegate {}

internal extension CALayer {
/// Swizzle the `add(_:forKey:) selector.
internal static var motionAddedAnimations: [(CALayer, String, CAAnimation)]? = {
Expand Down Expand Up @@ -110,14 +107,11 @@ public extension CALayer {
*/
func animate(_ animations: [CAAnimation]) {
for animation in animations {
if nil == animation.delegate {
animation.delegate = self
}

if let a = animation as? CABasicAnimation {
a.fromValue = (presentation() ?? self).value(forKeyPath: a.keyPath!)
}

updateModel(animation)
if let a = animation as? CAPropertyAnimation {
add(a, forKey: a.keyPath!)
} else if let a = animation as? CAAnimationGroup {
Expand All @@ -128,46 +122,6 @@ public extension CALayer {
}
}

/**
Executed when an animation has started.
- Parameter _ anim: A CAAnimation.
*/
func animationDidStart(_ anim: CAAnimation) {}

/**
A delegation function that is executed when the backing layer stops
running an animation.
- Parameter animation: The CAAnimation instance that stopped running.
- Parameter flag: A boolean that indicates if the animation stopped
because it was completed or interrupted. True if completed, false
if interrupted.
*/
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
guard let a = anim as? CAPropertyAnimation else {
if let a = (anim as? CAAnimationGroup)?.animations {
for x in a {
animationDidStop(x, finished: true)
}
}
return
}

guard let b = a as? CABasicAnimation else {
return
}

guard let v = b.toValue else {
return
}

guard let k = b.keyPath else {
return
}

setValue(v, forKeyPath: k)
removeAnimation(forKey: k)
}

/**
A function that accepts a list of MotionAnimation values and executes them.
- Parameter animations: A list of MotionAnimation values.
Expand Down Expand Up @@ -321,11 +275,7 @@ fileprivate extension CALayer {
}
}

let g = Motion.animate(group: anims, duration: duration)
g.fillMode = .both
g.isRemovedOnCompletion = false
g.timingFunction = ts.timingFunction

let g = Motion.animate(group: anims, timingFunction: ts.timingFunction, duration: duration)
self.animate(g)

if let v = ts.completion {
Expand All @@ -338,3 +288,19 @@ fileprivate extension CALayer {
}
}
}

private extension CALayer {
/**
Updates the model with values provided in animation.
- Parameter animation: A CAAnimation.
*/
func updateModel(_ animation: CAAnimation) {
if let a = animation as? CABasicAnimation {
setValue(a.toValue, forKeyPath: a.keyPath!)
} else if let a = animation as? CAAnimationGroup {
a.animations?.forEach {
updateModel($0)
}
}
}
}
Loading

0 comments on commit 4a8fa6a

Please sign in to comment.