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

// 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()
}
}