140 lines
5.9 KiB
Swift
140 lines
5.9 KiB
Swift
//
|
|
// LargeImageExpandAnimationController.swift
|
|
// Tusker
|
|
//
|
|
// Created by Shadowfacts on 9/1/18.
|
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
|
|
protocol LargeImageAnimatableViewController: UIViewController {
|
|
var animationSourceView: UIImageView? { get }
|
|
var largeImageController: LargeImageViewController? { get }
|
|
var animationImage: UIImage? { get }
|
|
var dismissInteractionController: LargeImageInteractionController? { get }
|
|
var isInteractivelyAnimatingDismissal: Bool { get set }
|
|
}
|
|
|
|
extension LargeImageAnimatableViewController {
|
|
func sourceViewFrame(in coordinateSpace: UIView) -> CGRect? {
|
|
guard let sourceView = animationSourceView else { return nil }
|
|
|
|
var sourceFrame = sourceView.convert(sourceView.bounds, to: coordinateSpace)
|
|
if let scrollView = coordinateSpace as? UIScrollView {
|
|
let scale = scrollView.zoomScale
|
|
let width = sourceFrame.width * scale
|
|
let height = sourceFrame.height * scale
|
|
let x = sourceFrame.minX * scale - scrollView.contentOffset.x + scrollView.frame.minX
|
|
let y = sourceFrame.minY * scale - scrollView.contentOffset.y + scrollView.frame.minY
|
|
sourceFrame = CGRect(x: x, y: y, width: width, height: height)
|
|
}
|
|
|
|
return sourceFrame
|
|
}
|
|
}
|
|
|
|
class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
|
|
|
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
|
if UIAccessibility.prefersCrossFadeTransitions {
|
|
return 0.2
|
|
} else {
|
|
return 0.4
|
|
}
|
|
}
|
|
|
|
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
|
guard let fromVC = transitionContext.viewController(forKey: .from),
|
|
let toVC = transitionContext.viewController(forKey: .to) as? LargeImageAnimatableViewController else {
|
|
return
|
|
}
|
|
|
|
if UIAccessibility.prefersCrossFadeTransitions {
|
|
animateCrossFadeTransition(using: transitionContext)
|
|
return
|
|
}
|
|
|
|
let containerView = transitionContext.containerView
|
|
containerView.addSubview(toVC.view)
|
|
|
|
let finalVCFrame = transitionContext.finalFrame(for: toVC)
|
|
guard let sourceView = toVC.animationSourceView,
|
|
let sourceFrame = toVC.sourceViewFrame(in: fromVC.view),
|
|
let image = toVC.animationImage else {
|
|
toVC.view.frame = finalVCFrame
|
|
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
|
return
|
|
}
|
|
|
|
// use alpha, because isHidden makes stack views re-layout
|
|
sourceView.alpha = 0
|
|
toVC.view.alpha = 0
|
|
toVC.largeImageController?.contentView.isHidden = true
|
|
toVC.largeImageController?.setControlsVisible(false, animated: false)
|
|
|
|
var finalFrameSize = finalVCFrame.inset(by: toVC.view.safeAreaInsets).size
|
|
let newWidth = finalFrameSize.width / image.size.width
|
|
let newHeight = finalFrameSize.height / image.size.height
|
|
if newHeight < newWidth {
|
|
finalFrameSize.width = newHeight * image.size.width
|
|
} else {
|
|
finalFrameSize.height = newWidth * image.size.height
|
|
}
|
|
let finalFrame = CGRect(origin: CGPoint(x: finalVCFrame.midX - finalFrameSize.width / 2, y: finalVCFrame.midY - finalFrameSize.height / 2), size: finalFrameSize)
|
|
|
|
let imageView = GIFImageView(frame: sourceFrame)
|
|
imageView.image = image
|
|
if let gifController = (sourceView as? GIFImageView)?.gifController {
|
|
gifController.attach(to: imageView)
|
|
}
|
|
imageView.contentMode = .scaleAspectFill
|
|
imageView.layer.cornerRadius = sourceView.layer.cornerRadius
|
|
imageView.layer.maskedCorners = sourceView.layer.maskedCorners
|
|
imageView.layer.masksToBounds = true
|
|
|
|
containerView.addSubview(imageView)
|
|
|
|
let duration = transitionDuration(using: transitionContext)
|
|
let velocity = 1 / CGFloat(duration)
|
|
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.75, initialSpringVelocity: velocity, options: []) {
|
|
imageView.frame = finalFrame
|
|
imageView.layer.cornerRadius = 0
|
|
toVC.view.alpha = 1
|
|
toVC.largeImageController?.setControlsVisible(true, animated: false)
|
|
} completion: { (_) in
|
|
// This shouldn't be necessary. I believe it's a workaround for using a XIB
|
|
// for the large image VC. Without this, the final frame of the large image VC
|
|
// is not set to the propper rect (it uses the frame of the preview device
|
|
// in the XIB). When using a storyboard, the final frame is automatically set
|
|
// (or UIKit does layout differently when loading the view) and this is not necessary.
|
|
toVC.view.frame = finalVCFrame
|
|
|
|
toVC.largeImageController?.contentView.isHidden = false
|
|
fromVC.view.isHidden = false
|
|
imageView.removeFromSuperview()
|
|
|
|
sourceView.alpha = 1
|
|
|
|
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
|
}
|
|
}
|
|
|
|
func animateCrossFadeTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
|
guard let toVC = transitionContext.viewController(forKey: .to) as? LargeImageAnimatableViewController else {
|
|
return
|
|
}
|
|
|
|
transitionContext.containerView.addSubview(toVC.view)
|
|
toVC.view.alpha = 0
|
|
|
|
let duration = transitionDuration(using: transitionContext)
|
|
UIView.animate(withDuration: duration) {
|
|
toVC.view.alpha = 1
|
|
} completion: { (_) in
|
|
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
|
}
|
|
}
|
|
|
|
}
|