From a99fb7f0b0123a18183f6ff599c40cf40923af2d Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sun, 24 Nov 2024 19:17:02 -0500 Subject: [PATCH] Improve gallery transitions when there is something displaying on top of the source view See #520 --- .../GalleryDismissAnimationController.swift | 23 ++++++++++++++ ...lleryPresentationAnimationController.swift | 30 ++++++++++++++++--- .../Sources/GalleryVC/UIView+Utilities.swift | 26 ++++++++++++++++ 3 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 Packages/GalleryVC/Sources/GalleryVC/UIView+Utilities.swift diff --git a/Packages/GalleryVC/Sources/GalleryVC/GalleryDismissAnimationController.swift b/Packages/GalleryVC/Sources/GalleryVC/GalleryDismissAnimationController.swift index e09d3788..11fb9866 100644 --- a/Packages/GalleryVC/Sources/GalleryVC/GalleryDismissAnimationController.swift +++ b/Packages/GalleryVC/Sources/GalleryVC/GalleryDismissAnimationController.swift @@ -47,6 +47,18 @@ class GalleryDismissAnimationController: NSObject, UIViewControllerAnimatedTrans container.addSubview(to.view) } + // This trick does not work as well here as it does for presentation, seemingly + // because the delayFactor:0.9 still results in the snapshot fading out too quickly :/ + let sourceSnapshot = sourceView.snapshotView(afterScreenUpdates: false) + if let sourceSnapshot { + let snapshotContainer = sourceView.ancestorForInsertingSnapshot + snapshotContainer.addSubview(sourceSnapshot) + let sourceFrameInShapshotContainer = snapshotContainer.convert(sourceView.bounds, from: sourceView) + sourceSnapshot.frame = sourceFrameInShapshotContainer + sourceSnapshot.layer.opacity = 1 + self.sourceView.layer.opacity = 0 + } + let sourceFrameInContainer = container.convert(sourceView.bounds, from: sourceView) let destFrameInContainer = container.convert(itemViewController.content.view.bounds, from: itemViewController.content.view) @@ -59,6 +71,7 @@ class GalleryDismissAnimationController: NSObject, UIViewControllerAnimatedTrans .translatedBy(x: destFrameInContainer.midX - sourceFrameInContainer.midX, y: destFrameInContainer.midY - sourceFrameInContainer.midY) .scaledBy(x: scale, y: scale) sourceView.transform = sourceToDestTransform + sourceSnapshot?.transform = sourceToDestTransform } else { appliedSourceToDestTransform = false } @@ -142,6 +155,7 @@ class GalleryDismissAnimationController: NSObject, UIViewControllerAnimatedTrans if appliedSourceToDestTransform { self.sourceView.transform = origSourceTransform + sourceSnapshot?.transform = origSourceTransform } contentContainer.frame = sourceFrameInContainer @@ -151,7 +165,16 @@ class GalleryDismissAnimationController: NSObject, UIViewControllerAnimatedTrans itemViewController.setControlsVisible(false, animated: false, dueToUserInteraction: false) } + if let sourceSnapshot { + animator.addAnimations({ + self.sourceView.layer.opacity = 1 + sourceSnapshot.layer.opacity = 0 + }, delayFactor: 0.9) + } + animator.addCompletion { _ in + sourceSnapshot?.removeFromSuperview() + // Having dismissed, we don't need to undo any of the changes to the content VC. transitionContext.completeTransition(true) diff --git a/Packages/GalleryVC/Sources/GalleryVC/GalleryPresentationAnimationController.swift b/Packages/GalleryVC/Sources/GalleryVC/GalleryPresentationAnimationController.swift index 94d7eb78..3f33b3bc 100644 --- a/Packages/GalleryVC/Sources/GalleryVC/GalleryPresentationAnimationController.swift +++ b/Packages/GalleryVC/Sources/GalleryVC/GalleryPresentationAnimationController.swift @@ -30,6 +30,25 @@ class GalleryPresentationAnimationController: NSObject, UIViewControllerAnimated return } + // Try to effectively "fade out" anything that's on top of the source view. + // The 0.05 duration makes this happen faster than the rest of the animation, + // and so less noticeable. + let sourceSnapshot = sourceView.snapshotView(afterScreenUpdates: false) + if let sourceSnapshot { + let snapshotContainer = sourceView.ancestorForInsertingSnapshot + snapshotContainer.addSubview(sourceSnapshot) + let sourceFrameInShapshotContainer = snapshotContainer.convert(sourceView.bounds, from: sourceView) + sourceSnapshot.frame = sourceFrameInShapshotContainer + sourceSnapshot.transform = sourceView.transform + sourceSnapshot.layer.opacity = 0 + UIView.animate(withDuration: 0.05) { + sourceSnapshot.layer.opacity = 1 + // Also fade out the actual source view underneath the snapshot to try + // and account for semi-transparent images. + self.sourceView.layer.opacity = 0 + } + } + let container = transitionContext.containerView to.view.frame = container.bounds container.addSubview(to.view) @@ -149,20 +168,23 @@ class GalleryPresentationAnimationController: NSObject, UIViewControllerAnimated itemViewController.setControlsVisible(true, animated: false, dueToUserInteraction: false) if let sourceToDestTransform { + sourceSnapshot?.transform = sourceToDestTransform self.sourceView.transform = sourceToDestTransform } } animator.addCompletion { _ in + sourceSnapshot?.removeFromSuperview() + self.sourceView.layer.opacity = 1 + if sourceToDestTransform != nil { + self.sourceView.transform = origSourceTransform + } + contentContainer.removeFromSuperview() dimmingView.removeFromSuperview() to.view.backgroundColor = .black - if sourceToDestTransform != nil { - self.sourceView.transform = origSourceTransform - } - // Reset the properties we changed before re-adding the content to the scroll view. // (I would expect UIScrollView to effectively do this itself, but w/e.) content.view.transform = origContentTransform diff --git a/Packages/GalleryVC/Sources/GalleryVC/UIView+Utilities.swift b/Packages/GalleryVC/Sources/GalleryVC/UIView+Utilities.swift new file mode 100644 index 00000000..411122be --- /dev/null +++ b/Packages/GalleryVC/Sources/GalleryVC/UIView+Utilities.swift @@ -0,0 +1,26 @@ +// +// UIView+Utilities.swift +// GalleryVC +// +// Created by Shadowfacts on 11/24/24. +// + +import UIKit + +extension UIView { + + var ancestorForInsertingSnapshot: UIView { + var view = self + while let superview = view.superview { + if superview.layer.masksToBounds { + return superview + } else if superview is UIScrollView { + return self + } else { + view = superview + } + } + return view + } + +}