feat: update preview present/dismiss transition style to pin-to-source rect

This commit is contained in:
CMK 2021-04-28 19:06:45 +08:00
parent 7d8ffd187a
commit 6f0b4354a7
11 changed files with 344 additions and 96 deletions

View File

@ -533,14 +533,51 @@ extension StatusProviderFacade {
provider.status(for: cell, indexPath: nil)
.sink { [weak provider] status in
guard let provider = provider else { return }
guard let status = status?.reblog ?? status else { return }
guard let source = status else { return }
let status = source.reblog ?? source
let meta = MediaPreviewViewModel.StatusImagePreviewMeta(
statusObjectID: status.objectID,
initialIndex: index,
preloadThumbnailImages: mosaicImageView.imageViews.map { $0.image }
)
let mediaPreviewViewModel = MediaPreviewViewModel(context: provider.context, meta: meta)
let pushTransitionItem = MediaPreviewTransitionItem(
source: .mosaic(mosaicImageView),
previewableViewController: provider
)
pushTransitionItem.aspectRatio = {
if let image = imageView.image {
return image.size
}
guard let media = status.mediaAttachments?.sorted(by: { $0.index.compare($1.index) == .orderedAscending }) else { return nil }
guard index < media.count else { return nil }
let meta = media[index].meta
guard let width = meta?.original?.width, let height = meta?.original?.height else { return nil }
return CGSize(width: width, height: height)
}()
pushTransitionItem.sourceImageView = imageView
pushTransitionItem.initialFrame = {
let initialFrame = imageView.superview!.convert(imageView.frame, to: nil)
assert(initialFrame != .zero)
return initialFrame
}()
pushTransitionItem.image = {
if let image = imageView.image {
return image
}
if index < mosaicImageView.blurhashOverlayImageViews.count {
return mosaicImageView.blurhashOverlayImageViews[index].image
}
return nil
}()
let mediaPreviewViewModel = MediaPreviewViewModel(
context: provider.context,
meta: meta,
pushTransitionItem: pushTransitionItem
)
DispatchQueue.main.async {
provider.coordinator.present(scene: .mediaPreview(viewModel: mediaPreviewViewModel), from: provider, transition: .custom(transitioningDelegate: provider.mediaPreviewTransitionController))
}

View File

@ -12,6 +12,8 @@ import Pageboy
final class MediaPreviewViewController: UIViewController, NeedsDependency {
static let closeButtonSize = CGSize(width: 30, height: 30)
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
@ -20,6 +22,23 @@ final class MediaPreviewViewController: UIViewController, NeedsDependency {
let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterial))
let pagingViewConttroller = MediaPreviewPagingViewController()
let closeButtonBackground: UIVisualEffectView = {
let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterial))
backgroundView.alpha = 0.9
backgroundView.layer.masksToBounds = true
backgroundView.layer.cornerRadius = MediaPreviewViewController.closeButtonSize.width * 0.5
return backgroundView
}()
let closeButtonBackgroundVisualEffectView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: UIBlurEffect(style: .systemUltraThinMaterial)))
let closeButton: UIButton = {
let button = HitTestExpandedButton()
button.imageView?.tintColor = .label
button.setImage(UIImage(systemName: "xmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold))!, for: .normal)
return button
}()
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
@ -48,11 +67,57 @@ extension MediaPreviewViewController {
])
pagingViewConttroller.didMove(toParent: self)
closeButtonBackground.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(closeButtonBackground)
NSLayoutConstraint.activate([
closeButtonBackground.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 12),
closeButtonBackground.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor)
])
closeButtonBackgroundVisualEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
closeButtonBackground.contentView.addSubview(closeButtonBackgroundVisualEffectView)
closeButton.translatesAutoresizingMaskIntoConstraints = false
closeButtonBackgroundVisualEffectView.contentView.addSubview(closeButton)
NSLayoutConstraint.activate([
closeButton.topAnchor.constraint(equalTo: closeButtonBackgroundVisualEffectView.topAnchor),
closeButton.leadingAnchor.constraint(equalTo: closeButtonBackgroundVisualEffectView.leadingAnchor),
closeButtonBackgroundVisualEffectView.trailingAnchor.constraint(equalTo: closeButton.trailingAnchor),
closeButtonBackgroundVisualEffectView.bottomAnchor.constraint(equalTo: closeButton.bottomAnchor),
closeButton.heightAnchor.constraint(equalToConstant: MediaPreviewViewController.closeButtonSize.height).priority(.defaultHigh),
closeButton.widthAnchor.constraint(equalToConstant: MediaPreviewViewController.closeButtonSize.width).priority(.defaultHigh),
])
viewModel.mediaPreviewImageViewControllerDelegate = self
pagingViewConttroller.interPageSpacing = 10
pagingViewConttroller.delegate = self
pagingViewConttroller.dataSource = viewModel
closeButton.addTarget(self, action: #selector(MediaPreviewViewController.closeButtonPressed(_:)), for: .touchUpInside)
// bind view model
viewModel.currentPage
.receive(on: DispatchQueue.main)
.sink { [weak self] index in
guard let self = self else { return }
switch self.viewModel.pushTransitionItem.source {
case .mosaic(let mosaicImageViewContainer):
UIView.animate(withDuration: 0.3) {
mosaicImageViewContainer.setImageViews(alpha: 1)
mosaicImageViewContainer.setImageView(alpha: 0, index: index)
}
}
}
.store(in: &disposeBag)
}
}
extension MediaPreviewViewController {
@objc private func closeButtonPressed(_ sender: UIButton) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
dismiss(animated: true, completion: nil)
}
}
@ -61,20 +126,19 @@ extension MediaPreviewViewController {
extension MediaPreviewViewController: MediaPreviewingViewController {
func isInteractiveDismissable() -> Bool {
return true
// if let mediaPreviewImageViewController = pagingViewConttroller.currentViewController as? MediaPreviewImageViewController {
// let previewImageView = mediaPreviewImageViewController.previewImageView
// // TODO: allow zooming pan dismiss
// guard previewImageView.zoomScale == previewImageView.minimumZoomScale else {
// return false
// }
//
// let safeAreaInsets = previewImageView.safeAreaInsets
// let statusBarFrameHeight = view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
// return previewImageView.contentOffset.y <= -(safeAreaInsets.top - statusBarFrameHeight)
// }
//
// return false
if let mediaPreviewImageViewController = pagingViewConttroller.currentViewController as? MediaPreviewImageViewController {
let previewImageView = mediaPreviewImageViewController.previewImageView
// TODO: allow zooming pan dismiss
guard previewImageView.zoomScale == previewImageView.minimumZoomScale else {
return false
}
let safeAreaInsets = previewImageView.safeAreaInsets
let statusBarFrameHeight = view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
return previewImageView.contentOffset.y <= -(safeAreaInsets.top - statusBarFrameHeight)
}
return false
}
}
@ -107,6 +171,7 @@ extension MediaPreviewViewController: PageboyViewControllerDelegate {
) {
// update page control
// pageControl.currentPage = index
viewModel.currentPage.value = index
}
func pageboyViewController(

View File

@ -17,11 +17,13 @@ final class MediaPreviewViewModel: NSObject {
let context: AppContext
let initialItem: PreviewItem
weak var mediaPreviewImageViewControllerDelegate: MediaPreviewImageViewControllerDelegate?
let currentPage: CurrentValueSubject<Int, Never>
// output
let pushTransitionItem: MediaPreviewTransitionItem
let viewControllers: [UIViewController]
init(context: AppContext, meta: StatusImagePreviewMeta) {
init(context: AppContext, meta: StatusImagePreviewMeta, pushTransitionItem: MediaPreviewTransitionItem) {
self.context = context
self.initialItem = .status(meta)
var viewControllers: [UIViewController] = []
@ -45,6 +47,8 @@ final class MediaPreviewViewModel: NSObject {
}
}
self.viewControllers = viewControllers
self.currentPage = CurrentValueSubject(meta.initialIndex)
self.pushTransitionItem = pushTransitionItem
super.init()
}

View File

@ -45,7 +45,7 @@ extension MediaPreviewImageView {
isUserInteractionEnabled = true
showsVerticalScrollIndicator = false
showsHorizontalScrollIndicator = false
bouncesZoom = true
minimumZoomScale = 1.0
maximumZoomScale = 4.0
@ -139,6 +139,9 @@ extension MediaPreviewImageView: UIScrollViewDelegate {
func scrollViewDidZoom(_ scrollView: UIScrollView) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
centerScrollViewContents()
// set bounce when zoom in
alwaysBounceVertical = zoomScale > minimumZoomScale
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {

View File

@ -296,6 +296,25 @@ extension MosaicImageViewContainer {
}
// FIXME: set imageView source from blurhash and image
extension MosaicImageViewContainer {
func setImageViews(alpha: CGFloat) {
// blurhashOverlayImageViews.forEach { $0.alpha = alpha }
imageViews.forEach { $0.alpha = alpha }
}
func setImageView(alpha: CGFloat, index: Int) {
// if index < blurhashOverlayImageViews.count {
// blurhashOverlayImageViews[index].alpha = alpha
// }
if index < imageViews.count {
imageViews[index].alpha = alpha
}
}
}
extension MosaicImageViewContainer {
@objc private func visualEffectViewTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) {

View File

@ -48,29 +48,35 @@ struct MosaicMeta {
func blurhashImagePublisher() -> AnyPublisher<UIImage?, Never> {
return Future { promise in
guard let blurhash = blurhash else {
promise(.success(nil))
return
}
let imageSize: CGSize = {
let aspectRadio = size.width / size.height
if size.width > size.height {
let width: CGFloat = MosaicMeta.edgeMaxLength
let height = width / aspectRadio
return CGSize(width: width, height: height)
} else {
let height: CGFloat = MosaicMeta.edgeMaxLength
let width = height * aspectRadio
return CGSize(width: width, height: height)
}
}()
workingQueue.async {
let image = UIImage(blurHash: blurhash, size: imageSize)
let image = self.blurhashImage()
promise(.success(image))
}
}
.eraseToAnyPublisher()
}
func blurhashImage() -> UIImage? {
guard let blurhash = blurhash else {
return nil
}
let imageSize: CGSize = {
let aspectRadio = size.width / size.height
if size.width > size.height {
let width: CGFloat = MosaicMeta.edgeMaxLength
let height = width / aspectRadio
return CGSize(width: width, height: height)
} else {
let height: CGFloat = MosaicMeta.edgeMaxLength
let width = height * aspectRadio
return CGSize(width: width, height: height)
}
}()
let image = UIImage(blurHash: blurhash, size: imageSize)
return image
}
}

View File

@ -7,10 +7,10 @@
import os.log
import UIKit
import func AVFoundation.AVMakeRect
final class MediaHostToMediaPreviewViewControllerAnimatedTransitioning: ViewControllerAnimatedTransitioning {
let transitionItem: MediaPreviewTransitionItem
let panGestureRecognizer: UIPanGestureRecognizer
@ -32,7 +32,6 @@ final class MediaHostToMediaPreviewViewControllerAnimatedTransitioning: ViewCont
}
// MARK: - UIViewControllerAnimatedTransitioning
extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning {
@ -51,19 +50,48 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning {
let toView = transitionContext.view(forKey: .to) else {
fatalError()
}
let toViewEndFrame = transitionContext.finalFrame(for: toVC)
toView.frame = toViewEndFrame
toView.alpha = 0
transitionContext.containerView.addSubview(toView)
let animator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), curve: curve)
// set to image hidden
toVC.pagingViewConttroller.view.alpha = 0
// set from image hidden. update hidden when paging. seealso: `MediaPreviewViewController`
switch transitionItem.source {
case .mosaic(let mosaicImageViewContainer):
mosaicImageViewContainer.setImageView(alpha: 0, index: toVC.viewModel.currentPage.value)
}
// Set transition image view
assert(transitionItem.initialFrame != nil)
let initialFrame = transitionItem.initialFrame ?? toViewEndFrame
let transitionTargetFrame: CGRect = {
let aspectRatio = transitionItem.aspectRatio ?? CGSize(width: initialFrame.width, height: initialFrame.height)
return AVMakeRect(aspectRatio: aspectRatio, insideRect: toView.bounds)
}()
let transitionImageView: UIImageView = {
let imageView = UIImageView(frame: transitionContext.containerView.convert(initialFrame, from: nil))
imageView.clipsToBounds = true
imageView.contentMode = .scaleAspectFill
imageView.isUserInteractionEnabled = false
imageView.image = transitionItem.image
return imageView
}()
transitionItem.targetFrame = transitionTargetFrame
transitionItem.imageView = transitionImageView
transitionContext.containerView.addSubview(transitionImageView)
let animator = MediaHostToMediaPreviewViewControllerAnimatedTransitioning.animator(initialVelocity: .zero)
animator.addAnimations {
transitionImageView.frame = transitionTargetFrame
toView.alpha = 1
}
animator.addCompletion { position in
toVC.pagingViewConttroller.view.alpha = 1
transitionImageView.removeFromSuperview()
transitionContext.completeTransition(position == .end)
}
@ -72,17 +100,59 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning {
private func popTransition(using transitionContext: UIViewControllerContextTransitioning, curve: UIView.AnimationCurve = .easeInOut) -> UIViewPropertyAnimator {
guard let fromVC = transitionContext.viewController(forKey: .from) as? MediaPreviewViewController,
let fromView = transitionContext.view(forKey: .from) else {
let fromView = transitionContext.view(forKey: .from),
let mediaPreviewImageViewController = fromVC.pagingViewConttroller.currentViewController as? MediaPreviewImageViewController,
let index = fromVC.pagingViewConttroller.currentIndex else {
fatalError()
}
// assert view hierarchy not change
let toVC = transitionItem.previewableViewController
let targetFrame = toVC.sourceFrame(transitionItem: transitionItem, index: index)
let imageView = mediaPreviewImageViewController.previewImageView.imageView
let _snapshot: UIView? = {
transitionItem.snapshotRaw = imageView
let snapshot = imageView.snapshotView(afterScreenUpdates: false)
snapshot?.clipsToBounds = true
snapshot?.contentMode = .scaleAspectFill
return snapshot
}()
guard let snapshot = _snapshot else {
transitionContext.completeTransition(false)
fatalError()
}
mediaPreviewImageViewController.view.insertSubview(snapshot, aboveSubview: mediaPreviewImageViewController.previewImageView)
snapshot.center = transitionContext.containerView.center
let animator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), curve: curve)
transitionItem.imageView = imageView
transitionItem.snapshotTransitioning = snapshot
transitionItem.initialFrame = snapshot.frame
transitionItem.targetFrame = targetFrame
// disable interaction
fromVC.pagingViewConttroller.isUserInteractionEnabled = false
let animator = popInteractiveTransitionAnimator
self.transitionItem.snapshotRaw?.alpha = 0.0
animator.addAnimations {
fromView.alpha = 0
if let targetFrame = targetFrame {
self.transitionItem.snapshotTransitioning?.frame = targetFrame
} else {
fromView.alpha = 0
}
fromVC.closeButtonBackground.alpha = 0
fromVC.visualEffectView.effect = nil
}
animator.addCompletion { position in
self.transitionItem.snapshotTransitioning?.removeFromSuperview()
switch self.transitionItem.source {
case .mosaic(let mosaicImageViewContainer):
mosaicImageViewContainer.setImageViews(alpha: 1)
}
transitionContext.completeTransition(position == .end)
}
@ -99,35 +169,7 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning {
switch operation {
case .pop:
guard let mediaPreviewViewController = transitionContext.viewController(forKey: .from) as? MediaPreviewViewController,
let mediaPreviewImageViewController = mediaPreviewViewController.pagingViewConttroller.currentViewController as? MediaPreviewImageViewController else {
transitionContext.completeTransition(false)
return
}
let imageView = mediaPreviewImageViewController.previewImageView.imageView
let _snapshot: UIView? = {
// if imageView.image == nil {
// transitionItem.snapshotRaw = mediaPreviewImageViewController.progressBarView
// return mediaPreviewImageViewController.progressBarView.snapshotView(afterScreenUpdates: false)
// } else {
transitionItem.snapshotRaw = imageView
return imageView.snapshotView(afterScreenUpdates: false)
// }
}()
guard let snapshot = _snapshot else {
transitionContext.completeTransition(false)
return
}
mediaPreviewImageViewController.view.insertSubview(snapshot, aboveSubview: mediaPreviewImageViewController.previewImageView)
snapshot.center = transitionContext.containerView.center
transitionItem.imageView = imageView
transitionItem.snapshotTransitioning = snapshot
transitionItem.initialFrame = snapshot.frame
transitionItem.targetFrame = snapshot.frame
// Note: change item.imageView transform via pan gesture
panGestureRecognizer.addTarget(self, action: #selector(MediaHostToMediaPreviewViewControllerAnimatedTransitioning.updatePanGestureInteractive(_:)))
popInteractiveTransition(using: transitionContext)
default:
@ -138,27 +180,62 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning {
private func popInteractiveTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: .from) as? MediaPreviewViewController,
let fromView = transitionContext.view(forKey: .from) else {
let fromView = transitionContext.view(forKey: .from),
let mediaPreviewImageViewController = fromVC.pagingViewConttroller.currentViewController as? MediaPreviewImageViewController,
let index = fromVC.pagingViewConttroller.currentIndex else {
fatalError()
}
// assert view hierarchy not change
let toVC = transitionItem.previewableViewController
let targetFrame = toVC.sourceFrame(transitionItem: transitionItem, index: index)
let imageView = mediaPreviewImageViewController.previewImageView.imageView
let _snapshot: UIView? = {
transitionItem.snapshotRaw = imageView
let snapshot = imageView.snapshotView(afterScreenUpdates: false)
snapshot?.clipsToBounds = true
snapshot?.contentMode = .scaleAspectFill
return snapshot
}()
guard let snapshot = _snapshot else {
transitionContext.completeTransition(false)
return
}
mediaPreviewImageViewController.view.insertSubview(snapshot, aboveSubview: mediaPreviewImageViewController.previewImageView)
snapshot.center = transitionContext.containerView.center
transitionItem.imageView = imageView
transitionItem.snapshotTransitioning = snapshot
transitionItem.initialFrame = snapshot.frame
transitionItem.targetFrame = targetFrame
// disable interaction
fromVC.pagingViewConttroller.isUserInteractionEnabled = false
let animator = popInteractiveTransitionAnimator
let blurEffect = fromVC.visualEffectView.effect
self.transitionItem.imageView?.isHidden = true
self.transitionItem.snapshotRaw?.alpha = 0.0
animator.addAnimations {
self.transitionItem.snapshotTransitioning?.alpha = 0.4
// fromVC.mediaInfoDescriptionView.alpha = 0
// fromVC.closeButtonBackground.alpha = 0
// fromVC.pageControl.alpha = 0
fromVC.closeButtonBackground.alpha = 0
fromVC.visualEffectView.effect = nil
}
animator.addCompletion { position in
fromVC.pagingViewConttroller.isUserInteractionEnabled = true
fromVC.closeButtonBackground.alpha = position == .end ? 0 : 1
self.transitionItem.imageView?.isHidden = position == .end
self.transitionItem.snapshotRaw?.alpha = position == .start ? 1.0 : 0.0
self.transitionItem.snapshotTransitioning?.removeFromSuperview()
if position == .end {
switch self.transitionItem.source {
case .mosaic(let mosaicImageViewContainer):
mosaicImageViewContainer.setImageViews(alpha: 1)
}
}
fromVC.visualEffectView.effect = position == .end ? nil : blurEffect
transitionContext.completeTransition(position == .end)
}
@ -184,10 +261,10 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning {
case .ended, .cancelled:
let targetPosition = completionPosition()
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: target position: %s", ((#file as NSString).lastPathComponent), #line, #function, targetPosition == .end ? "end" : "start")
targetPosition == .end ? transitionContext.finishInteractiveTransition() : transitionContext.cancelInteractiveTransition()
isTransitionContextFinish = true
animate(targetPosition)
targetPosition == .end ? transitionContext.finishInteractiveTransition() : transitionContext.cancelInteractiveTransition()
default:
return
}
@ -239,10 +316,17 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning {
itemAnimator.addAnimations {
if toPosition == .end {
self.transitionItem.snapshotTransitioning?.alpha = 0
if let targetFrame = self.transitionItem.targetFrame {
self.transitionItem.snapshotTransitioning?.frame = targetFrame
} else {
self.transitionItem.snapshotTransitioning?.alpha = 0
}
} else {
self.transitionItem.snapshotTransitioning?.alpha = 1
self.transitionItem.snapshotTransitioning?.frame = self.transitionItem.initialFrame!
if let initialFrame = self.transitionItem.initialFrame {
self.transitionItem.snapshotTransitioning?.frame = initialFrame
} else {
self.transitionItem.snapshotTransitioning?.alpha = 1
}
}
}

View File

@ -74,10 +74,9 @@ extension MediaPreviewTransitionController: UIViewControllerTransitioningDelegat
self.mediaPreviewViewController = mediaPreviewViewController
self.mediaPreviewViewController?.view.addGestureRecognizer(panGestureRecognizer)
let transitionItem = MediaPreviewTransitionItem(id: UUID())
return MediaHostToMediaPreviewViewControllerAnimatedTransitioning(
operation: .push,
transitionItem: transitionItem,
transitionItem: mediaPreviewViewController.viewModel.pushTransitionItem,
panGestureRecognizer: panGestureRecognizer
)
}
@ -92,10 +91,10 @@ extension MediaPreviewTransitionController: UIViewControllerTransitioningDelegat
assertionFailure()
return nil
}
let transitionItem = MediaPreviewTransitionItem(id: UUID())
return MediaHostToMediaPreviewViewControllerAnimatedTransitioning(
operation: .pop,
transitionItem: transitionItem,
transitionItem: mediaPreviewViewController.viewModel.pushTransitionItem,
panGestureRecognizer: panGestureRecognizer
)
}

View File

@ -6,21 +6,40 @@
//
import UIKit
import CoreData
class MediaPreviewTransitionItem: Identifiable {
let id: UUID
let source: Source
var previewableViewController: MediaPreviewableViewController
// TODO:
// source
// value maybe invalid when preview paging
var image: UIImage?
var aspectRatio: CGSize?
var initialFrame: CGRect? = nil
var sourceImageView: UIImageView?
// target
var targetFrame: CGRect? = nil
// transitioning
var imageView: UIImageView?
var snapshotRaw: UIView?
var snapshotTransitioning: UIView?
var initialFrame: CGRect? = nil
var targetFrame: CGRect? = nil
var touchOffset: CGVector = CGVector.zero
init(id: UUID) {
init(id: UUID = UUID(), source: Source, previewableViewController: MediaPreviewableViewController) {
self.id = id
self.source = source
self.previewableViewController = previewableViewController
}
}
extension MediaPreviewTransitionItem {
enum Source {
case mosaic(MosaicImageViewContainer)
}
}

View File

@ -5,8 +5,20 @@
// Created by MainasuK Cirno on 2021-4-28.
//
import Foundation
import UIKit
protocol MediaPreviewableViewController: class {
protocol MediaPreviewableViewController: AnyObject {
var mediaPreviewTransitionController: MediaPreviewTransitionController { get }
func sourceFrame(transitionItem: MediaPreviewTransitionItem, index: Int) -> CGRect?
}
extension MediaPreviewableViewController {
func sourceFrame(transitionItem: MediaPreviewTransitionItem, index: Int) -> CGRect? {
switch transitionItem.source {
case .mosaic(let mosaicImageViewContainer):
guard index < mosaicImageViewContainer.imageViews.count else { return nil }
let imageView = mosaicImageViewContainer.imageViews[index]
return imageView.superview!.convert(imageView.frame, to: nil)
}
}
}

View File

@ -7,6 +7,6 @@
import Foundation
protocol MediaPreviewingViewController: class {
protocol MediaPreviewingViewController: AnyObject {
func isInteractiveDismissable() -> Bool
}