Commit 732bcdb5 by Daniel Dahan

progress on new Motion structure

parent 3d9641b4
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "0900" LastUpgradeVersion = "0920"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
......
...@@ -220,6 +220,7 @@ internal class SnapshotWrapperView: UIView { ...@@ -220,6 +220,7 @@ internal class SnapshotWrapperView: UIView {
init(contentView: UIView) { init(contentView: UIView) {
self.contentView = contentView self.contentView = contentView
super.init(frame: contentView.frame) super.init(frame: contentView.frame)
contentView.frame = bounds
addSubview(contentView) addSubview(contentView)
} }
...@@ -229,8 +230,7 @@ internal class SnapshotWrapperView: UIView { ...@@ -229,8 +230,7 @@ internal class SnapshotWrapperView: UIView {
override func layoutSubviews() { override func layoutSubviews() {
super.layoutSubviews() super.layoutSubviews()
contentView.bounds.size = bounds.size contentView.frame = bounds
contentView.center = bounds.center
} }
} }
...@@ -243,15 +243,19 @@ internal extension UIView { ...@@ -243,15 +243,19 @@ internal extension UIView {
if #available(iOS 9.0, *), isHidden && (superview is UICollectionView || superview is UIStackView || self is UITableViewCell) { if #available(iOS 9.0, *), isHidden && (superview is UICollectionView || superview is UIStackView || self is UITableViewCell) {
return [] return []
} else if isHidden && (superview is UICollectionView || self is UITableViewCell) { } else if isHidden && (superview is UICollectionView || self is UITableViewCell) {
return [] return []
} else if isMotionEnabledForSubviews { } else if isMotionEnabledForSubviews {
return [self] + subviews.flatMap { $0.flattenedViewHierarchy } return [self] + subviews.flatMap {
} else { $0.flattenedViewHierarchy
return [self]
} }
} }
return [self]
}
/** /**
Retrieves the optimized duration based on the given parameters. Retrieves the optimized duration based on the given parameters.
- Parameter fromPosition: A CGPoint. - Parameter fromPosition: A CGPoint.
...@@ -285,7 +289,13 @@ internal extension UIView { ...@@ -285,7 +289,13 @@ internal extension UIView {
*/ */
func slowSnapshotView() -> UIView { func slowSnapshotView() -> UIView {
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0) UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
layer.render(in: UIGraphicsGetCurrentContext()!)
guard let currentContext = UIGraphicsGetCurrentContext() else {
UIGraphicsEndImageContext()
return UIView()
}
layer.render(in: currentContext)
let image = UIGraphicsGetImageFromCurrentImageContext() let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext() UIGraphicsEndImageContext()
......
/*
* 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
extension Motion {
/**
Complete the transition.
- Parameter after: A TimeInterval.
- Parameter isFinished: A Boolean indicating if the transition
has completed.
*/
func complete(after: TimeInterval, isFinished: Bool) {
guard isTransitioning else {
return
}
if after <= 0.001 {
complete(isFinished: isFinished)
return
}
let v = (isFinished ? elapsedTime : 1 - elapsedTime) * totalDuration
self.isFinished = isFinished
currentAnimationDuration = after + v
beginTime = CACurrentMediaTime() - v
}
/**
Complete the transition.
- Parameter isFinished: A Boolean indicating if the transition
has completed.
*/
@objc
func complete(isFinished: Bool) {
if state == .notified {
forceFinishing = isFinished
}
guard .animating == state || .starting == state else {
return
}
defer {
transitionContext = nil
fromViewController = nil
toViewController = nil
isNavigationController = false
isTabBarController = false
forceNonInteractive = false
transitionPairs.removeAll()
transitionObservers = nil
transitionContainer = nil
completionCallback = nil
forceFinishing = nil
container = nil
startingProgress = nil
preprocessors.removeAll()
animators.removeAll()
plugins.removeAll()
context = nil
elapsedTime = 0
totalDuration = 0
state = .possible
}
state = .completing
progressRunner.stop()
context.clean()
if let tv = toView, let fv = fromView {
if isFinished && isPresenting && toOverFullScreen {
// finished presenting a overFullScreen view controller.
context.unhide(rootView: tv)
context.removeSnapshots(rootView: tv)
context.storeViewAlpha(rootView: fv)
fromViewController!.motionStoredSnapshot = container
fv.removeFromSuperview()
fv.addSubview(container)
} else if !isFinished && !isPresenting && fromOverFullScreen {
// Cancelled dismissing a overFullScreen view controller.
context.unhide(rootView: fv)
context.removeSnapshots(rootView: fv)
context.storeViewAlpha(rootView: tv)
toViewController!.motionStoredSnapshot = container
container.superview?.addSubview(tv)
tv.addSubview(container)
} else {
context.unhideAll()
context.removeAllSnapshots()
}
// Move fromView & toView back from our container back to the one supplied by UIKit.
if (toOverFullScreen && isFinished) || (fromOverFullScreen && !isFinished) {
transitionContainer?.addSubview(isFinished ? fv : tv)
}
transitionContainer?.addSubview(isFinished ? tv : fv)
if isPresenting != isFinished, !isContainerController {
// Only happens when present a .overFullScreen view controller.
// bug: http://openradar.appspot.com/radar?id=5320103646199808
UIApplication.shared.keyWindow?.addSubview(isPresenting ? fv : tv)
}
}
if container.superview == transitionContainer {
container.removeFromSuperview()
}
for a in animators {
a.clean()
}
transitionContainer?.isUserInteractionEnabled = true
completionCallback?(isFinished)
let tContext = transitionContext
let fvc = fromViewController
let tvc = toViewController
if isFinished {
processEndTransitionDelegation(transitionContext: tContext, fromViewController: fvc, toViewController: tvc)
} else {
processCancelTransitionDelegation(transitionContext: tContext, fromViewController: fvc, toViewController: tvc)
tContext?.cancelInteractiveTransition()
}
tContext?.completeTransition(isFinished)
}
}
...@@ -77,6 +77,7 @@ internal extension MotionContext { ...@@ -77,6 +77,7 @@ internal extension MotionContext {
func set(fromViews: [UIView], toViews: [UIView]) { func set(fromViews: [UIView], toViews: [UIView]) {
self.fromViews = fromViews self.fromViews = fromViews
self.toViews = toViews self.toViews = toViews
map(views: fromViews, identifierMap: &motionIdentifierToSourceView) map(views: fromViews, identifierMap: &motionIdentifierToSourceView)
map(views: toViews, identifierMap: &motionIdentifierToDestinationView) map(views: toViews, identifierMap: &motionIdentifierToDestinationView)
} }
...@@ -114,8 +115,8 @@ public extension MotionContext { ...@@ -114,8 +115,8 @@ public extension MotionContext {
get { get {
return viewToTargetState[view] return viewToTargetState[view]
} }
set { set(value) {
viewToTargetState[view] = newValue viewToTargetState[view] = value
} }
} }
} }
...@@ -185,6 +186,7 @@ public extension MotionContext { ...@@ -185,6 +186,7 @@ public extension MotionContext {
if let visualEffectView = containerView as? UIVisualEffectView { if let visualEffectView = containerView as? UIVisualEffectView {
containerView = visualEffectView.contentView containerView = visualEffectView.contentView
} }
case .global: case .global:
break break
} }
...@@ -221,8 +223,8 @@ public extension MotionContext { ...@@ -221,8 +223,8 @@ public extension MotionContext {
case .optimized: case .optimized:
#if os(tvOS) #if os(tvOS)
snapshot = view.snapshotView(afterScreenUpdates: true)! snapshot = view.snapshotView(afterScreenUpdates: true)!
#else
#else
if #available(iOS 9.0, *), let stackView = view as? UIStackView { if #available(iOS 9.0, *), let stackView = view as? UIStackView {
snapshot = stackView.slowSnapshotView() snapshot = stackView.slowSnapshotView()
...@@ -246,7 +248,6 @@ public extension MotionContext { ...@@ -246,7 +248,6 @@ public extension MotionContext {
// take a snapshot without the background // take a snapshot without the background
barView.layer.sublayers![0].opacity = 0 barView.layer.sublayers![0].opacity = 0
let realSnapshot = barView.snapshotView(afterScreenUpdates: true)! let realSnapshot = barView.snapshotView(afterScreenUpdates: true)!
barView.layer.sublayers![0].opacity = 1 barView.layer.sublayers![0].opacity = 1
...@@ -260,6 +261,7 @@ public extension MotionContext { ...@@ -260,6 +261,7 @@ public extension MotionContext {
} else { } else {
snapshot = view.snapshotView() ?? UIView() snapshot = view.snapshotView() ?? UIView()
} }
#endif #endif
} }
...@@ -267,6 +269,7 @@ public extension MotionContext { ...@@ -267,6 +269,7 @@ public extension MotionContext {
if let imageView = view as? UIImageView, imageView.adjustsImageWhenAncestorFocused { if let imageView = view as? UIImageView, imageView.adjustsImageWhenAncestorFocused {
snapshot.frame = imageView.focusedFrameGuide.layoutFrame snapshot.frame = imageView.focusedFrameGuide.layoutFrame
} }
#endif #endif
view.layer.cornerRadius = oldCornerRadius view.layer.cornerRadius = oldCornerRadius
...@@ -323,6 +326,7 @@ public extension MotionContext { ...@@ -323,6 +326,7 @@ public extension MotionContext {
for sibling in nextSiblings { for sibling in nextSiblings {
insertGlobalViewTree(view: sibling) insertGlobalViewTree(view: sibling)
} }
} else { } else {
containerView.addSubview(snapshot) containerView.addSubview(snapshot)
} }
...@@ -363,7 +367,7 @@ internal extension MotionContext { ...@@ -363,7 +367,7 @@ internal extension MotionContext {
- Parameter view: A UIView. - Parameter view: A UIView.
*/ */
func hide(view: UIView) { func hide(view: UIView) {
guard nil == viewToAlphas[view], .noSnapshot != self[view]?.snapshotType else { guard nil == viewToAlphas[view] else {
return return
} }
...@@ -372,7 +376,7 @@ internal extension MotionContext { ...@@ -372,7 +376,7 @@ internal extension MotionContext {
viewToAlphas[view] = 1 viewToAlphas[view] = 1
} else { } else {
viewToAlphas[view] = view.isOpaque ? .infinity : view.alpha viewToAlphas[view] = view.alpha
view.alpha = 0 view.alpha = 0
} }
} }
...@@ -389,10 +393,6 @@ internal extension MotionContext { ...@@ -389,10 +393,6 @@ internal extension MotionContext {
if view is UIVisualEffectView { if view is UIVisualEffectView {
view.isHidden = false view.isHidden = false
} else if oldAlpha == .infinity {
view.alpha = 1
view.isOpaque = true
} else { } else {
view.alpha = oldAlpha view.alpha = oldAlpha
} }
......
/*
* 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
protocol MotionProgressRunnerDelegate: class {
func update(elapsedTime: TimeInterval)
func complete(isFinished: Bool)
}
class MotionProgressRunner {
weak var delegate: MotionProgressRunnerDelegate?
var isRunning: Bool {
return displayLink != nil
}
internal var timePassed: TimeInterval = 0
internal var duration: TimeInterval = 0
internal var displayLink: CADisplayLink?
internal var isReversed: Bool = false
@objc
func displayUpdate(_ link: CADisplayLink) {
timePassed += isReversed ? -link.duration : link.duration
if isReversed, timePassed <= 1.0 / 120 {
delegate?.complete(isFinished: false)
stop()
return
}
if !isReversed, timePassed > duration - 1.0 / 120 {
delegate?.complete(isFinished: true)
stop()
return
}
delegate?.update(elapsedTime: timePassed / duration)
}
func start(timePassed: TimeInterval, totalTime: TimeInterval, reverse: Bool) {
stop()
self.timePassed = timePassed
self.isReversed = reverse
self.duration = totalTime
displayLink = CADisplayLink(target: self, selector: #selector(displayUpdate(_:)))
displayLink!.add(to: RunLoop.main, forMode: RunLoopMode(rawValue: RunLoopMode.commonModes.rawValue))
}
func stop() {
displayLink?.isPaused = true
displayLink?.remove(from: RunLoop.main, forMode: RunLoopMode(rawValue: RunLoopMode.commonModes.rawValue))
displayLink = nil
}
}
...@@ -26,42 +26,23 @@ ...@@ -26,42 +26,23 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
import UIKit @objc(MotionState)
public enum MotionState: Int {
/// Motion is able to start a new transition.
case possible
public class MotionIndependentController: MotionController { /// UIKit has notified Motion about a pending transition.
/// An initializer. /// Motion hasn't started preparation.
public override init() { case notified
super.init()
}
/** /// Motion's `start` method has been called. Preparing the animation.
Transitions source views to their corresponding destination view case starting
within a given root view.
- Parameter rootView: A UIView.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
- Parameter completion: An optional callback.
*/
public func transition(rootView: UIView, fromViews: [UIView], toViews: [UIView], completion: ((Bool) -> Void)? = nil) {
transitionContainer = rootView
completionCallback = completion
prepareTransition() /// Motions's `animate` method has been called. Animation has started.
prepareContext(fromViews: fromViews, toViews: toViews) case animating
prepareTransitionPairs()
animate() /// Motions's `complete` method has been called. Transition has ended or has
} /// been cancelled. Motion is cleaning up.
case completing
} }
fileprivate extension MotionIndependentController {
/**
Prepares the context.
- Parameter fromViews: An Array of UIViews.
- PArameter toViews: An Array of UIViews.
*/
func prepareContext(fromViews: [UIView], toViews: [UIView]) {
context.set(fromViews: fromViews, toViews: toViews)
processContext()
}
}
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