// The MIT License (MIT) // // Copyright (c) 2016 Luke Zhao // // 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 optimizedDurationTo(position: CGPoint?, size: CGSize?, transform: CATransform3D?) -> TimeInterval { let fromPos = (layer.presentation() ?? layer).position let toPos = position ?? 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 } } internal class HeroDefaultAnimator: HeroAnimator { weak public var hero: HeroTransition! public var context: HeroContext! { return hero?.context } var viewContexts: [UIView: ViewContext] = [:] 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 (_, viewContext) in viewContexts { if viewContext.targetState.duration == nil { viewContext.duration = max(viewContext.duration, calculateOptimizedDuration(snapshot: viewContext.snapshot, targetState: viewContext.targetState) + timePassed) } let timeUntilStopped = viewContext.resume(timePassed: timePassed, reverse: reverse) duration = max(duration, timeUntilStopped) } return duration } public func apply(state: HeroTargetState, to view: UIView) { if let context = viewContexts[view] { context.apply(state: state) } } public func changeTarget(state: HeroTargetState, isDestination: Bool, to view: UIView) { if let context = viewContexts[view] { context.changeTarget(state: state, isDestination: isDestination) } } 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 maxDuration: TimeInterval = 0 for v in fromViews { createViewContext(view: v, appearing: false) } for v in toViews { createViewContext(view: v, appearing: true) } for viewContext in viewContexts.values { if let duration = viewContext.targetState.duration, duration != .infinity { viewContext.duration = duration maxDuration = max(maxDuration, duration) } else { let duration = calculateOptimizedDuration(snapshot: viewContext.snapshot, targetState: viewContext.targetState) if viewContext.targetState.duration == nil { viewContext.duration = duration } maxDuration = max(maxDuration, duration) } } for viewContext in viewContexts.values { if viewContext.targetState.duration == .infinity { viewContext.duration = maxDuration } let timeUntilStopped = viewContext.startAnimations() maxDuration = max(maxDuration, timeUntilStopped) } return maxDuration } func calculateOptimizedDuration(snapshot: UIView, targetState: HeroTargetState) -> TimeInterval { return snapshot.optimizedDurationTo(position: targetState.position, size: targetState.size, transform: targetState.transform) } func createViewContext(view: UIView, appearing: Bool) { let snapshot = context.snapshotView(for: view) let viewContext = ViewContext(animator: self, snapshot: snapshot, targetState: context[view]!, appearing: appearing) viewContexts[view] = viewContext } public func clean() { for vc in viewContexts.values { vc.clean() } viewContexts.removeAll() } }