Improve gallery expand animation

Use spring timing, slide in top/bottom controls
This commit is contained in:
Shadowfacts 2020-09-08 23:41:11 -04:00
parent f5110c773a
commit e19a6528ad
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
4 changed files with 32 additions and 18 deletions

View File

@ -27,6 +27,10 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
} }
var animationSourceView: UIImageView? { sourceViews[currentIndex] } var animationSourceView: UIImageView? { sourceViews[currentIndex] }
var largeImageController: LargeImageViewController? {
// use protocol because page controllers may be loading or non-loading VCs
(pages[currentIndex] as? LargeImageAnimatableViewController)?.largeImageController
}
var animationImage: UIImage? { var animationImage: UIImage? {
if let page = pages[currentIndex] as? LargeImageAnimatableViewController, if let page = pages[currentIndex] as? LargeImageAnimatableViewController,
let image = page.animationImage { let image = page.animationImage {
@ -48,6 +52,9 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
override var prefersStatusBarHidden: Bool { override var prefersStatusBarHidden: Bool {
return true return true
} }
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .none
}
override var childForHomeIndicatorAutoHidden: UIViewController? { override var childForHomeIndicatorAutoHidden: UIViewController? {
return viewControllers?.first return viewControllers?.first
} }

View File

@ -13,6 +13,7 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
typealias ContentView = UIView & LargeImageContentView typealias ContentView = UIView & LargeImageContentView
weak var animationSourceView: UIImageView? weak var animationSourceView: UIImageView?
var largeImageController: LargeImageViewController? { self }
var animationImage: UIImage? { contentView.animationImage } var animationImage: UIImage? { contentView.animationImage }
var animationGifData: Data? { contentView.animationGifData } var animationGifData: Data? { contentView.animationGifData }
var dismissInteractionController: LargeImageInteractionController? var dismissInteractionController: LargeImageInteractionController?
@ -49,6 +50,9 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
override var prefersStatusBarHidden: Bool { override var prefersStatusBarHidden: Bool {
return true return true
} }
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .none
}
override var prefersHomeIndicatorAutoHidden: Bool { override var prefersHomeIndicatorAutoHidden: Bool {
return !controlsVisible return !controlsVisible

View File

@ -36,6 +36,7 @@ class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableVie
var shrinkGestureEnabled = true var shrinkGestureEnabled = true
weak var animationSourceView: UIImageView? weak var animationSourceView: UIImageView?
var largeImageController: LargeImageViewController? { largeImageVC }
var animationImage: UIImage? { largeImageVC?.animationImage ?? animationSourceView?.image } var animationImage: UIImage? { largeImageVC?.animationImage ?? animationSourceView?.image }
var animationGifData: Data? { largeImageVC?.animationGifData } var animationGifData: Data? { largeImageVC?.animationGifData }
var dismissInteractionController: LargeImageInteractionController? var dismissInteractionController: LargeImageInteractionController?
@ -43,6 +44,9 @@ class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableVie
override var prefersStatusBarHidden: Bool { override var prefersStatusBarHidden: Bool {
return true return true
} }
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .none
}
override var childForHomeIndicatorAutoHidden: UIViewController? { override var childForHomeIndicatorAutoHidden: UIViewController? {
return largeImageVC return largeImageVC
} }

View File

@ -11,6 +11,7 @@ import Gifu
protocol LargeImageAnimatableViewController: UIViewController { protocol LargeImageAnimatableViewController: UIViewController {
var animationSourceView: UIImageView? { get } var animationSourceView: UIImageView? { get }
var largeImageController: LargeImageViewController? { get }
var animationImage: UIImage? { get } var animationImage: UIImage? { get }
var animationGifData: Data? { get } var animationGifData: Data? { get }
var dismissInteractionController: LargeImageInteractionController? { get } var dismissInteractionController: LargeImageInteractionController? { get }
@ -37,7 +38,7 @@ extension LargeImageAnimatableViewController {
class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTransitioning { class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.2 return 0.5
} }
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
@ -47,7 +48,7 @@ class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTra
} }
let containerView = transitionContext.containerView let containerView = transitionContext.containerView
containerView.addSubview(toVC.view)
let finalVCFrame = transitionContext.finalFrame(for: toVC) let finalVCFrame = transitionContext.finalFrame(for: toVC)
guard let sourceView = toVC.animationSourceView, guard let sourceView = toVC.animationSourceView,
@ -58,8 +59,11 @@ class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTra
return return
} }
// use alpha, becaus isHidden makes stack views re-layout // use alpha, because isHidden makes stack views re-layout
sourceView.alpha = 0 sourceView.alpha = 0
toVC.view.alpha = 0
toVC.largeImageController?.contentView.isHidden = true
toVC.largeImageController?.setControlsVisible(false, animated: false)
var finalFrameSize = finalVCFrame.inset(by: fromVC.view.safeAreaInsets).size var finalFrameSize = finalVCFrame.inset(by: fromVC.view.safeAreaInsets).size
let newWidth = finalFrameSize.width / image.size.width let newWidth = finalFrameSize.width / image.size.width
@ -81,21 +85,17 @@ class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTra
imageView.layer.maskedCorners = sourceView.layer.maskedCorners imageView.layer.maskedCorners = sourceView.layer.maskedCorners
imageView.layer.masksToBounds = true imageView.layer.masksToBounds = true
let blackView = UIView(frame: finalVCFrame) containerView.addSubview(toVC.view)
blackView.backgroundColor = .black
blackView.alpha = 0
containerView.addSubview(blackView)
containerView.addSubview(imageView) containerView.addSubview(imageView)
toVC.view.isHidden = true
let duration = transitionDuration(using: transitionContext) let duration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, animations: { let velocity = 1 / CGFloat(duration)
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.65, initialSpringVelocity: velocity, options: []) {
imageView.frame = finalFrame imageView.frame = finalFrame
imageView.layer.cornerRadius = 0 imageView.layer.cornerRadius = 0
blackView.alpha = 1 toVC.view.alpha = 1
}, completion: { _ in toVC.largeImageController?.setControlsVisible(true, animated: false)
} completion: { (_) in
// This shouldn't be necessary. I believe it's a workaround for using a XIB // 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 // 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 // is not set to the propper rect (it uses the frame of the preview device
@ -103,15 +103,14 @@ class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTra
// (or UIKit does layout differently when loading the view) and this is not necessary. // (or UIKit does layout differently when loading the view) and this is not necessary.
toVC.view.frame = finalVCFrame toVC.view.frame = finalVCFrame
toVC.view.isHidden = false toVC.largeImageController?.contentView.isHidden = false
fromVC.view.isHidden = false fromVC.view.isHidden = false
blackView.removeFromSuperview()
imageView.removeFromSuperview() imageView.removeFromSuperview()
sourceView.alpha = 1 sourceView.alpha = 1
transitionContext.completeTransition(!transitionContext.transitionWasCancelled) transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}) }
} }
} }