You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
139 lines
5.4 KiB
139 lines
5.4 KiB
// 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
|
|
|
|
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<ViewContext: HeroAnimatorViewContext>: 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()
|
|
}
|
|
}
|