136 lines
5.1 KiB
Swift
136 lines
5.1 KiB
Swift
|
//
|
||
|
// GalleryPresentationAnimationController.swift
|
||
|
// GalleryVC
|
||
|
//
|
||
|
// Created by Shadowfacts on 12/28/23.
|
||
|
//
|
||
|
|
||
|
import UIKit
|
||
|
|
||
|
class GalleryPresentationAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||
|
private let sourceView: UIView
|
||
|
private var completionHandlers: [() -> Void] = []
|
||
|
|
||
|
init(sourceView: UIView) {
|
||
|
self.sourceView = sourceView
|
||
|
}
|
||
|
|
||
|
func addCompletionHandler(_ block: @escaping () -> Void) {
|
||
|
completionHandlers.append(block)
|
||
|
}
|
||
|
|
||
|
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||
|
return 0.4
|
||
|
}
|
||
|
|
||
|
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||
|
guard let to = transitionContext.viewController(forKey: .to) as? GalleryViewController else {
|
||
|
fatalError()
|
||
|
}
|
||
|
|
||
|
to.presentationAnimationController = self
|
||
|
|
||
|
let itemViewController = to.currentItemViewController
|
||
|
|
||
|
if !itemViewController.content.canAnimateFromSourceView || UIAccessibility.prefersCrossFadeTransitions {
|
||
|
animateCrossFadeTransition(using: transitionContext)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
let container = transitionContext.containerView
|
||
|
itemViewController.view.layoutIfNeeded()
|
||
|
|
||
|
let sourceFrameInContainer = container.convert(sourceView.bounds, from: sourceView)
|
||
|
let destFrameInContainer = container.convert(itemViewController.content.view.bounds, from: itemViewController.content.view)
|
||
|
|
||
|
// Use a transformation to make the actual source view appear to move into the destination frame.
|
||
|
// Doing this while having the content view fade-in papers over the z-index change when
|
||
|
// there was something overlapping the source view.
|
||
|
let origSourceTransform = sourceView.transform
|
||
|
let sourceToDestTransform: CGAffineTransform?
|
||
|
if destFrameInContainer.width > 0 && destFrameInContainer.height > 0 {
|
||
|
sourceToDestTransform = origSourceTransform
|
||
|
.translatedBy(x: destFrameInContainer.midX - sourceFrameInContainer.midX, y: destFrameInContainer.midY - sourceFrameInContainer.midY)
|
||
|
.scaledBy(x: destFrameInContainer.width / sourceFrameInContainer.width, y: destFrameInContainer.height / sourceFrameInContainer.height)
|
||
|
} else {
|
||
|
sourceToDestTransform = nil
|
||
|
}
|
||
|
|
||
|
let content = itemViewController.takeContent()
|
||
|
content.view.translatesAutoresizingMaskIntoConstraints = true
|
||
|
|
||
|
container.addSubview(to.view)
|
||
|
container.addSubview(content.view)
|
||
|
|
||
|
to.view.layer.opacity = 0
|
||
|
content.view.frame = sourceFrameInContainer
|
||
|
content.view.layer.opacity = 0
|
||
|
|
||
|
container.layoutIfNeeded()
|
||
|
|
||
|
// This needs to take place after the layout, so that the transform is correct.
|
||
|
itemViewController.setControlsVisible(false, animated: false)
|
||
|
|
||
|
let duration = self.transitionDuration(using: transitionContext)
|
||
|
// rougly equivalent to duration: 0.4, bounce: 0.3
|
||
|
let spring = UISpringTimingParameters(mass: 1, stiffness: 247, damping: 22, initialVelocity: .zero)
|
||
|
let animator = UIViewPropertyAnimator(duration: duration, timingParameters: spring)
|
||
|
|
||
|
animator.addAnimations {
|
||
|
to.view.layer.opacity = 1
|
||
|
|
||
|
content.view.frame = destFrameInContainer
|
||
|
content.view.layer.opacity = 1
|
||
|
|
||
|
itemViewController.setControlsVisible(true, animated: false)
|
||
|
|
||
|
if let sourceToDestTransform {
|
||
|
self.sourceView.transform = sourceToDestTransform
|
||
|
}
|
||
|
}
|
||
|
|
||
|
animator.addCompletion { _ in
|
||
|
if sourceToDestTransform != nil {
|
||
|
self.sourceView.transform = origSourceTransform
|
||
|
}
|
||
|
|
||
|
itemViewController.addContent()
|
||
|
|
||
|
transitionContext.completeTransition(true)
|
||
|
|
||
|
for block in self.completionHandlers {
|
||
|
block()
|
||
|
}
|
||
|
|
||
|
to.presentationAnimationController = nil
|
||
|
}
|
||
|
|
||
|
animator.startAnimation()
|
||
|
}
|
||
|
|
||
|
private func animateCrossFadeTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||
|
guard let to = transitionContext.viewController(forKey: .to) as? GalleryViewController else {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
transitionContext.containerView.addSubview(to.view)
|
||
|
to.view.alpha = 0
|
||
|
|
||
|
let duration = transitionDuration(using: transitionContext)
|
||
|
let animator = UIViewPropertyAnimator(duration: duration, curve: .easeInOut)
|
||
|
animator.addAnimations {
|
||
|
to.view.alpha = 1
|
||
|
}
|
||
|
animator.addCompletion { _ in
|
||
|
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||
|
|
||
|
for block in self.completionHandlers {
|
||
|
block()
|
||
|
}
|
||
|
|
||
|
to.presentationAnimationController = nil
|
||
|
}
|
||
|
animator.startAnimation()
|
||
|
}
|
||
|
}
|