mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-22 00:01:00 +00:00
Zoom transition
This commit is contained in:
parent
9bb7ae0609
commit
8de227d780
11 changed files with 570 additions and 8 deletions
|
@ -22,6 +22,10 @@
|
||||||
D08B8D3D253F929E00B1EBEF /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */; };
|
D08B8D3D253F929E00B1EBEF /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */; };
|
||||||
D08B8D42253F92B600B1EBEF /* ImagePageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */; };
|
D08B8D42253F92B600B1EBEF /* ImagePageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */; };
|
||||||
D08B8D4A253FC36500B1EBEF /* ImageNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D49253FC36500B1EBEF /* ImageNavigationController.swift */; };
|
D08B8D4A253FC36500B1EBEF /* ImageNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D49253FC36500B1EBEF /* ImageNavigationController.swift */; };
|
||||||
|
D08B8D602540DE3B00B1EBEF /* ZoomAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D5D2540DE3A00B1EBEF /* ZoomAnimator.swift */; };
|
||||||
|
D08B8D612540DE3B00B1EBEF /* ZoomDismissalInteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D5E2540DE3A00B1EBEF /* ZoomDismissalInteractionController.swift */; };
|
||||||
|
D08B8D622540DE3B00B1EBEF /* ZoomTransitionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D5F2540DE3A00B1EBEF /* ZoomTransitionController.swift */; };
|
||||||
|
D08B8D672540DEB200B1EBEF /* ZoomAnimatableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D662540DEB200B1EBEF /* ZoomAnimatableView.swift */; };
|
||||||
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */; };
|
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */; };
|
||||||
D0A3C2F725390A9700739F88 /* AppPreferences+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A3C2F625390A9700739F88 /* AppPreferences+Extensions.swift */; };
|
D0A3C2F725390A9700739F88 /* AppPreferences+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A3C2F625390A9700739F88 /* AppPreferences+Extensions.swift */; };
|
||||||
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B32F4F250B373600311912 /* RegistrationView.swift */; };
|
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B32F4F250B373600311912 /* RegistrationView.swift */; };
|
||||||
|
@ -122,6 +126,10 @@
|
||||||
D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = "<group>"; };
|
D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = "<group>"; };
|
||||||
D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePageViewController.swift; sourceTree = "<group>"; };
|
D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePageViewController.swift; sourceTree = "<group>"; };
|
||||||
D08B8D49253FC36500B1EBEF /* ImageNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageNavigationController.swift; sourceTree = "<group>"; };
|
D08B8D49253FC36500B1EBEF /* ImageNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageNavigationController.swift; sourceTree = "<group>"; };
|
||||||
|
D08B8D5D2540DE3A00B1EBEF /* ZoomAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZoomAnimator.swift; sourceTree = "<group>"; };
|
||||||
|
D08B8D5E2540DE3A00B1EBEF /* ZoomDismissalInteractionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZoomDismissalInteractionController.swift; sourceTree = "<group>"; };
|
||||||
|
D08B8D5F2540DE3A00B1EBEF /* ZoomTransitionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZoomTransitionController.swift; sourceTree = "<group>"; };
|
||||||
|
D08B8D662540DEB200B1EBEF /* ZoomAnimatableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZoomAnimatableView.swift; sourceTree = "<group>"; };
|
||||||
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = "<group>"; };
|
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = "<group>"; };
|
||||||
D0A3C2F625390A9700739F88 /* AppPreferences+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppPreferences+Extensions.swift"; sourceTree = "<group>"; };
|
D0A3C2F625390A9700739F88 /* AppPreferences+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppPreferences+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D0AD03552505814D0085A466 /* Base16 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Base16; sourceTree = "<group>"; };
|
D0AD03552505814D0085A466 /* Base16 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Base16; sourceTree = "<group>"; };
|
||||||
|
@ -234,6 +242,7 @@
|
||||||
D0C7D41D24F76169001EBDBB /* Supporting Files */,
|
D0C7D41D24F76169001EBDBB /* Supporting Files */,
|
||||||
D0C7D45324F76169001EBDBB /* System */,
|
D0C7D45324F76169001EBDBB /* System */,
|
||||||
D0666A2224C677B400F3F04B /* Tests */,
|
D0666A2224C677B400F3F04B /* Tests */,
|
||||||
|
D08B8D5C2540DDFC00B1EBEF /* Transitions */,
|
||||||
D0C7D43024F76169001EBDBB /* View Controllers */,
|
D0C7D43024F76169001EBDBB /* View Controllers */,
|
||||||
D0E2C1CF24FD8BA400854680 /* ViewModels */,
|
D0E2C1CF24FD8BA400854680 /* ViewModels */,
|
||||||
D0C7D42024F76169001EBDBB /* Views */,
|
D0C7D42024F76169001EBDBB /* Views */,
|
||||||
|
@ -278,6 +287,17 @@
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
D08B8D5C2540DDFC00B1EBEF /* Transitions */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D08B8D662540DEB200B1EBEF /* ZoomAnimatableView.swift */,
|
||||||
|
D08B8D5D2540DE3A00B1EBEF /* ZoomAnimator.swift */,
|
||||||
|
D08B8D5E2540DE3A00B1EBEF /* ZoomDismissalInteractionController.swift */,
|
||||||
|
D08B8D5F2540DE3A00B1EBEF /* ZoomTransitionController.swift */,
|
||||||
|
);
|
||||||
|
path = Transitions;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
D0A1F4F5252E7D2A004435BF /* Data Sources */ = {
|
D0A1F4F5252E7D2A004435BF /* Data Sources */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -569,12 +589,14 @@
|
||||||
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */,
|
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */,
|
||||||
D0F0B126251A90F400942152 /* AccountListCell.swift in Sources */,
|
D0F0B126251A90F400942152 /* AccountListCell.swift in Sources */,
|
||||||
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */,
|
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */,
|
||||||
|
D08B8D612540DE3B00B1EBEF /* ZoomDismissalInteractionController.swift in Sources */,
|
||||||
D0E569E0252931B100FA1D72 /* LoadMoreContentConfiguration.swift in Sources */,
|
D0E569E0252931B100FA1D72 /* LoadMoreContentConfiguration.swift in Sources */,
|
||||||
D0FE1C9825368A9D003EF1EB /* PlayerCache.swift in Sources */,
|
D0FE1C9825368A9D003EF1EB /* PlayerCache.swift in Sources */,
|
||||||
D0F0B136251AA12700942152 /* CollectionItem+Extensions.swift in Sources */,
|
D0F0B136251AA12700942152 /* CollectionItem+Extensions.swift in Sources */,
|
||||||
D0625E5D250F0B5C00502611 /* StatusContentConfiguration.swift in Sources */,
|
D0625E5D250F0B5C00502611 /* StatusContentConfiguration.swift in Sources */,
|
||||||
D0BEB1F324F8EE8C001B0F04 /* StatusAttachmentView.swift in Sources */,
|
D0BEB1F324F8EE8C001B0F04 /* StatusAttachmentView.swift in Sources */,
|
||||||
D0C7D49A24F7616A001EBDBB /* TableView.swift in Sources */,
|
D0C7D49A24F7616A001EBDBB /* TableView.swift in Sources */,
|
||||||
|
D08B8D622540DE3B00B1EBEF /* ZoomTransitionController.swift in Sources */,
|
||||||
D0F0B12E251A97E400942152 /* TableViewController.swift in Sources */,
|
D0F0B12E251A97E400942152 /* TableViewController.swift in Sources */,
|
||||||
D0FE1C8F253686F9003EF1EB /* PlayerView.swift in Sources */,
|
D0FE1C8F253686F9003EF1EB /* PlayerView.swift in Sources */,
|
||||||
D0F0B113251A86A000942152 /* AccountContentConfiguration.swift in Sources */,
|
D0F0B113251A86A000942152 /* AccountContentConfiguration.swift in Sources */,
|
||||||
|
@ -586,6 +608,8 @@
|
||||||
D0C7D49D24F7616A001EBDBB /* PostingReadingPreferencesView.swift in Sources */,
|
D0C7D49D24F7616A001EBDBB /* PostingReadingPreferencesView.swift in Sources */,
|
||||||
D0B5FE9B251583DB00478838 /* ProfileCollection+Extensions.swift in Sources */,
|
D0B5FE9B251583DB00478838 /* ProfileCollection+Extensions.swift in Sources */,
|
||||||
D0C7D49E24F7616A001EBDBB /* SecondaryNavigationView.swift in Sources */,
|
D0C7D49E24F7616A001EBDBB /* SecondaryNavigationView.swift in Sources */,
|
||||||
|
D08B8D602540DE3B00B1EBEF /* ZoomAnimator.swift in Sources */,
|
||||||
|
D08B8D672540DEB200B1EBEF /* ZoomAnimatableView.swift in Sources */,
|
||||||
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */,
|
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */,
|
||||||
D0C7D4D524F7616A001EBDBB /* String+Extensions.swift in Sources */,
|
D0C7D4D524F7616A001EBDBB /* String+Extensions.swift in Sources */,
|
||||||
D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */,
|
D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */,
|
||||||
|
|
43
Transitions/ZoomAnimatableView.swift
Normal file
43
Transitions/ZoomAnimatableView.swift
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import AVFoundation
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
protocol ZoomAnimatableView {
|
||||||
|
func transitionView() -> UIView
|
||||||
|
func frame(inView view: UIView) -> CGRect
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UIImageView: ZoomAnimatableView {
|
||||||
|
func transitionView() -> UIView {
|
||||||
|
let transitionView = UIImageView(image: image)
|
||||||
|
|
||||||
|
transitionView.contentMode = .scaleAspectFill
|
||||||
|
transitionView.clipsToBounds = true
|
||||||
|
|
||||||
|
return transitionView
|
||||||
|
}
|
||||||
|
|
||||||
|
func frame(inView view: UIView) -> CGRect {
|
||||||
|
guard let image = image else { return .zero }
|
||||||
|
|
||||||
|
return AVMakeRect(aspectRatio: image.size, insideRect: view.frame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PlayerView: ZoomAnimatableView {
|
||||||
|
func transitionView() -> UIView {
|
||||||
|
let transitionView = PlayerView()
|
||||||
|
|
||||||
|
transitionView.videoGravity = .resizeAspectFill
|
||||||
|
transitionView.player = player
|
||||||
|
|
||||||
|
return transitionView
|
||||||
|
}
|
||||||
|
|
||||||
|
func frame(inView view: UIView) -> CGRect {
|
||||||
|
guard let item = player?.currentItem else { return .zero }
|
||||||
|
|
||||||
|
return AVMakeRect(aspectRatio: item.presentationSize, insideRect: view.frame)
|
||||||
|
}
|
||||||
|
}
|
128
Transitions/ZoomAnimator.swift
Normal file
128
Transitions/ZoomAnimator.swift
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
protocol ZoomAnimatorDelegate: class {
|
||||||
|
func transitionWillStartWith(zoomAnimator: ZoomAnimator)
|
||||||
|
func transitionDidEndWith(zoomAnimator: ZoomAnimator)
|
||||||
|
func referenceView(for zoomAnimator: ZoomAnimator) -> UIView?
|
||||||
|
func referenceViewFrameInTransitioningView(for zoomAnimator: ZoomAnimator) -> CGRect?
|
||||||
|
}
|
||||||
|
|
||||||
|
class ZoomAnimator: NSObject {
|
||||||
|
weak var fromDelegate: ZoomAnimatorDelegate?
|
||||||
|
weak var toDelegate: ZoomAnimatorDelegate?
|
||||||
|
|
||||||
|
var transitionView: UIView?
|
||||||
|
var isPresenting = true
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ZoomAnimator: UIViewControllerAnimatedTransitioning {
|
||||||
|
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||||
|
isPresenting ? .defaultAnimationDuration : .shortAnimationDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||||
|
fromDelegate?.transitionWillStartWith(zoomAnimator: self)
|
||||||
|
toDelegate?.transitionWillStartWith(zoomAnimator: self)
|
||||||
|
|
||||||
|
if isPresenting {
|
||||||
|
animateZoomInTransition(using: transitionContext)
|
||||||
|
} else {
|
||||||
|
animateZoomOutTransition(using: transitionContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension ZoomAnimator {
|
||||||
|
private func animateZoomInTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||||
|
guard
|
||||||
|
let toVC = transitionContext.viewController(forKey: .to),
|
||||||
|
let fromVC = transitionContext.viewController(forKey: .from),
|
||||||
|
let fromReferenceView = fromDelegate?.referenceView(for: self),
|
||||||
|
let toReferenceView = toDelegate?.referenceView(for: self),
|
||||||
|
let fromReferenceViewFrame = fromDelegate?.referenceViewFrameInTransitioningView(for: self)
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
toVC.view.alpha = 0
|
||||||
|
toReferenceView.isHidden = true
|
||||||
|
transitionContext.containerView.addSubview(toVC.view)
|
||||||
|
|
||||||
|
if transitionView == nil, let transitionView = (fromReferenceView as? ZoomAnimatableView)?.transitionView() {
|
||||||
|
transitionView.frame = fromReferenceViewFrame
|
||||||
|
self.transitionView = transitionView
|
||||||
|
transitionContext.containerView.addSubview(transitionView)
|
||||||
|
}
|
||||||
|
|
||||||
|
fromReferenceView.isHidden = true
|
||||||
|
|
||||||
|
let finalTransitionSize = (fromReferenceView as? ZoomAnimatableView)?.frame(inView: toVC.view) ?? .zero
|
||||||
|
|
||||||
|
UIView.animate(
|
||||||
|
withDuration: transitionDuration(using: transitionContext),
|
||||||
|
delay: 0,
|
||||||
|
usingSpringWithDamping: 0.8,
|
||||||
|
initialSpringVelocity: 0,
|
||||||
|
options: [.transitionCrossDissolve]) {
|
||||||
|
self.transitionView?.frame = finalTransitionSize
|
||||||
|
toVC.view.alpha = 1.0
|
||||||
|
fromVC.tabBarController?.tabBar.alpha = 0
|
||||||
|
} completion: { _ in
|
||||||
|
self.transitionView?.removeFromSuperview()
|
||||||
|
toReferenceView.isHidden = false
|
||||||
|
fromReferenceView.isHidden = false
|
||||||
|
|
||||||
|
self.transitionView = nil
|
||||||
|
|
||||||
|
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||||
|
self.toDelegate?.transitionDidEndWith(zoomAnimator: self)
|
||||||
|
self.fromDelegate?.transitionDidEndWith(zoomAnimator: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func animateZoomOutTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||||
|
let containerView = transitionContext.containerView
|
||||||
|
|
||||||
|
guard
|
||||||
|
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to),
|
||||||
|
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
|
||||||
|
let fromReferenceView = fromDelegate?.referenceView(for: self),
|
||||||
|
let fromReferenceViewFrame = fromDelegate?.referenceViewFrameInTransitioningView(for: self)
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
let toReferenceView = toDelegate?.referenceView(for: self)
|
||||||
|
let toReferenceViewFrame = toDelegate?.referenceViewFrameInTransitioningView(for: self)
|
||||||
|
|
||||||
|
toReferenceView?.isHidden = true
|
||||||
|
|
||||||
|
if transitionView == nil, let transitionView = (fromReferenceView as? ZoomAnimatableView)?.transitionView() {
|
||||||
|
transitionView.frame = fromReferenceViewFrame
|
||||||
|
self.transitionView = transitionView
|
||||||
|
containerView.addSubview(transitionView)
|
||||||
|
}
|
||||||
|
|
||||||
|
containerView.insertSubview(toVC.view, belowSubview: fromVC.view)
|
||||||
|
fromReferenceView.isHidden = true
|
||||||
|
|
||||||
|
UIView.animate(
|
||||||
|
withDuration: transitionDuration(using: transitionContext)) {
|
||||||
|
fromVC.view.alpha = 0
|
||||||
|
|
||||||
|
if let toReferenceViewFrame = toReferenceViewFrame {
|
||||||
|
self.transitionView?.frame = toReferenceViewFrame
|
||||||
|
} else {
|
||||||
|
self.transitionView?.alpha = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
toVC.tabBarController?.tabBar.alpha = 1
|
||||||
|
} completion: { _ in
|
||||||
|
self.transitionView?.removeFromSuperview()
|
||||||
|
toReferenceView?.isHidden = false
|
||||||
|
fromReferenceView.isHidden = false
|
||||||
|
|
||||||
|
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||||
|
self.toDelegate?.transitionDidEndWith(zoomAnimator: self)
|
||||||
|
self.fromDelegate?.transitionDidEndWith(zoomAnimator: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
154
Transitions/ZoomDismissalInteractionController.swift
Normal file
154
Transitions/ZoomDismissalInteractionController.swift
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class ZoomDismissalInteractionController: NSObject {
|
||||||
|
var transitionContext: UIViewControllerContextTransitioning?
|
||||||
|
var animator: UIViewControllerAnimatedTransitioning?
|
||||||
|
|
||||||
|
var fromReferenceViewFrame: CGRect?
|
||||||
|
var toReferenceViewFrame: CGRect?
|
||||||
|
|
||||||
|
// swiftlint:disable:next function_body_length
|
||||||
|
func didPanWith(gestureRecognizer: UIPanGestureRecognizer) {
|
||||||
|
guard let transitionContext = self.transitionContext,
|
||||||
|
let animator = self.animator as? ZoomAnimator,
|
||||||
|
let transitionView = animator.transitionView,
|
||||||
|
let fromVC = transitionContext.viewController(forKey: .from),
|
||||||
|
let toVC = transitionContext.viewController(forKey: .to),
|
||||||
|
let fromReferenceView = animator.fromDelegate?.referenceView(for: animator),
|
||||||
|
let fromReferenceViewFrame = self.fromReferenceViewFrame
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
let toReferenceView = animator.toDelegate?.referenceView(for: animator)
|
||||||
|
|
||||||
|
fromReferenceView.isHidden = true
|
||||||
|
|
||||||
|
let anchorPoint = CGPoint(x: fromReferenceViewFrame.midX, y: fromReferenceViewFrame.midY)
|
||||||
|
let dismissThreshold = fromReferenceViewFrame.height / 8
|
||||||
|
let translatedPoint = gestureRecognizer.translation(in: fromReferenceView)
|
||||||
|
|
||||||
|
let backgroundAlpha = backgroundAlphaFor(view: fromVC.view, withPanningVerticalDelta: translatedPoint.y)
|
||||||
|
let scale = scaleFor(view: fromVC.view, withPanningVerticalDelta: translatedPoint.y)
|
||||||
|
|
||||||
|
fromVC.view.alpha = backgroundAlpha
|
||||||
|
|
||||||
|
transitionView.transform = CGAffineTransform(scaleX: scale, y: scale)
|
||||||
|
let newCenter = CGPoint(
|
||||||
|
x: anchorPoint.x + translatedPoint.x,
|
||||||
|
y: anchorPoint.y + translatedPoint.y - transitionView.frame.height * (1 - scale) / 2.0)
|
||||||
|
transitionView.center = newCenter
|
||||||
|
|
||||||
|
toReferenceView?.isHidden = true
|
||||||
|
|
||||||
|
transitionContext.updateInteractiveTransition(1 - scale)
|
||||||
|
|
||||||
|
toVC.tabBarController?.tabBar.alpha = 1 - backgroundAlpha
|
||||||
|
|
||||||
|
if gestureRecognizer.state == .ended {
|
||||||
|
if abs(anchorPoint.y - newCenter.y) < dismissThreshold {
|
||||||
|
// cancel
|
||||||
|
UIView.animate(
|
||||||
|
withDuration: 0.5,
|
||||||
|
delay: 0,
|
||||||
|
usingSpringWithDamping: 0.9,
|
||||||
|
initialSpringVelocity: 0,
|
||||||
|
options: []) {
|
||||||
|
transitionView.frame = fromReferenceViewFrame
|
||||||
|
fromVC.view.alpha = 1.0
|
||||||
|
toVC.tabBarController?.tabBar.alpha = 0
|
||||||
|
} completion: { _ in
|
||||||
|
toReferenceView?.isHidden = false
|
||||||
|
fromReferenceView.isHidden = false
|
||||||
|
transitionView.removeFromSuperview()
|
||||||
|
animator.transitionView = nil
|
||||||
|
transitionContext.cancelInteractiveTransition()
|
||||||
|
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||||
|
animator.toDelegate?.transitionDidEndWith(zoomAnimator: animator)
|
||||||
|
animator.fromDelegate?.transitionDidEndWith(zoomAnimator: animator)
|
||||||
|
self.transitionContext = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// start animation
|
||||||
|
UIView.animate(
|
||||||
|
withDuration: .shortAnimationDuration) {
|
||||||
|
fromVC.view.alpha = 0
|
||||||
|
|
||||||
|
if let toReferenceViewFrame = self.toReferenceViewFrame {
|
||||||
|
transitionView.frame = toReferenceViewFrame
|
||||||
|
} else {
|
||||||
|
transitionView.alpha = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
toVC.tabBarController?.tabBar.alpha = 1
|
||||||
|
} completion: { _ in
|
||||||
|
transitionView.removeFromSuperview()
|
||||||
|
toReferenceView?.isHidden = false
|
||||||
|
fromReferenceView.isHidden = false
|
||||||
|
|
||||||
|
self.transitionContext?.finishInteractiveTransition()
|
||||||
|
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||||
|
animator.toDelegate?.transitionDidEndWith(zoomAnimator: animator)
|
||||||
|
animator.fromDelegate?.transitionDidEndWith(zoomAnimator: animator)
|
||||||
|
self.transitionContext = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func backgroundAlphaFor(view: UIView, withPanningVerticalDelta verticalDelta: CGFloat) -> CGFloat {
|
||||||
|
let startingAlpha: CGFloat = 1.0
|
||||||
|
let finalAlpha: CGFloat = 0.0
|
||||||
|
let totalAvailableAlpha = startingAlpha - finalAlpha
|
||||||
|
|
||||||
|
let maximumDelta = view.bounds.height / 4.0
|
||||||
|
let deltaAsPercentageOfMaximun = min(abs(verticalDelta) / maximumDelta, 1.0)
|
||||||
|
|
||||||
|
return startingAlpha - (deltaAsPercentageOfMaximun * totalAvailableAlpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
func scaleFor(view: UIView, withPanningVerticalDelta verticalDelta: CGFloat) -> CGFloat {
|
||||||
|
let startingScale: CGFloat = 1.0
|
||||||
|
let finalScale: CGFloat = 0.5
|
||||||
|
let totalAvailableScale = startingScale - finalScale
|
||||||
|
|
||||||
|
let maximumDelta = view.bounds.height / 2.0
|
||||||
|
let deltaAsPercentageOfMaximun = min(abs(verticalDelta) / maximumDelta, 1.0)
|
||||||
|
|
||||||
|
return startingScale - (deltaAsPercentageOfMaximun * totalAvailableScale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ZoomDismissalInteractionController: UIViewControllerInteractiveTransitioning {
|
||||||
|
func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
|
||||||
|
guard let animator = animator as? ZoomAnimator else { return }
|
||||||
|
|
||||||
|
animator.fromDelegate?.transitionWillStartWith(zoomAnimator: animator)
|
||||||
|
animator.toDelegate?.transitionWillStartWith(zoomAnimator: animator)
|
||||||
|
|
||||||
|
self.transitionContext = transitionContext
|
||||||
|
|
||||||
|
let containerView = transitionContext.containerView
|
||||||
|
|
||||||
|
guard
|
||||||
|
let fromVC = transitionContext.viewController(forKey: .from),
|
||||||
|
let toVC = transitionContext.viewController(forKey: .to),
|
||||||
|
let fromReferenceViewFrame = animator.fromDelegate?.referenceViewFrameInTransitioningView(for: animator),
|
||||||
|
let fromReferenceView = animator.fromDelegate?.referenceView(for: animator)
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
self.fromReferenceViewFrame = fromReferenceViewFrame
|
||||||
|
toReferenceViewFrame = animator.toDelegate?.referenceViewFrameInTransitioningView(for: animator)
|
||||||
|
|
||||||
|
containerView.insertSubview(toVC.view, belowSubview: fromVC.view)
|
||||||
|
|
||||||
|
if animator.transitionView == nil,
|
||||||
|
let transitionView = (fromReferenceView as? ZoomAnimatableView)?.transitionView() {
|
||||||
|
transitionView.frame = fromReferenceViewFrame
|
||||||
|
animator.transitionView = transitionView
|
||||||
|
containerView.addSubview(transitionView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
79
Transitions/ZoomTransitionController.swift
Normal file
79
Transitions/ZoomTransitionController.swift
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class ZoomTransitionController: NSObject {
|
||||||
|
var isInteractive = false
|
||||||
|
|
||||||
|
weak var fromDelegate: ZoomAnimatorDelegate?
|
||||||
|
weak var toDelegate: ZoomAnimatorDelegate?
|
||||||
|
|
||||||
|
private let animator = ZoomAnimator()
|
||||||
|
private let interactionController = ZoomDismissalInteractionController()
|
||||||
|
|
||||||
|
func didPanWith(gestureRecognizer: UIPanGestureRecognizer) {
|
||||||
|
interactionController.didPanWith(gestureRecognizer: gestureRecognizer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ZoomTransitionController: UIViewControllerTransitioningDelegate {
|
||||||
|
func animationController(
|
||||||
|
forPresented presented: UIViewController,
|
||||||
|
presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||||
|
presentingAnimator()
|
||||||
|
}
|
||||||
|
|
||||||
|
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||||
|
dismissingAnimator()
|
||||||
|
}
|
||||||
|
|
||||||
|
func interactionControllerForDismissal(
|
||||||
|
using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
|
||||||
|
interactionController(animator: animator)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ZoomTransitionController: UINavigationControllerDelegate {
|
||||||
|
func navigationController(_ navigationController: UINavigationController,
|
||||||
|
animationControllerFor operation: UINavigationController.Operation,
|
||||||
|
from fromVC: UIViewController,
|
||||||
|
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||||
|
operation == .push ? presentingAnimator() : dismissingAnimator()
|
||||||
|
}
|
||||||
|
|
||||||
|
func navigationController(
|
||||||
|
_ navigationController: UINavigationController,
|
||||||
|
interactionControllerFor animationController: UIViewControllerAnimatedTransitioning)
|
||||||
|
-> UIViewControllerInteractiveTransitioning? {
|
||||||
|
interactionController(animator: animator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension ZoomTransitionController {
|
||||||
|
private func presentingAnimator() -> UIViewControllerAnimatedTransitioning {
|
||||||
|
animator.isPresenting = true
|
||||||
|
animator.fromDelegate = fromDelegate
|
||||||
|
animator.toDelegate = toDelegate
|
||||||
|
|
||||||
|
return animator
|
||||||
|
}
|
||||||
|
|
||||||
|
private func dismissingAnimator() -> UIViewControllerAnimatedTransitioning {
|
||||||
|
animator.isPresenting = false
|
||||||
|
let tmp = fromDelegate
|
||||||
|
animator.fromDelegate = toDelegate
|
||||||
|
animator.toDelegate = tmp
|
||||||
|
|
||||||
|
return animator
|
||||||
|
}
|
||||||
|
|
||||||
|
private func interactionController(
|
||||||
|
animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
|
||||||
|
guard isInteractive else { return nil }
|
||||||
|
|
||||||
|
interactionController.animator = animator
|
||||||
|
|
||||||
|
return interactionController
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,11 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import AVFoundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class ImageNavigationController: UINavigationController {
|
class ImageNavigationController: UINavigationController {
|
||||||
|
let transitionController = ZoomTransitionController()
|
||||||
|
|
||||||
private let imagePageViewController: ImagePageViewController
|
private let imagePageViewController: ImagePageViewController
|
||||||
|
|
||||||
init(imagePageViewController: ImagePageViewController) {
|
init(imagePageViewController: ImagePageViewController) {
|
||||||
|
@ -21,5 +24,91 @@ class ImageNavigationController: UINavigationController {
|
||||||
|
|
||||||
hidesBarsOnTap = true
|
hidesBarsOnTap = true
|
||||||
modalPresentationStyle = .fullScreen
|
modalPresentationStyle = .fullScreen
|
||||||
|
|
||||||
|
let panGestureRecognizer = UIPanGestureRecognizer(
|
||||||
|
target: self,
|
||||||
|
action: #selector(didPanWith(gestureRecognizer:)))
|
||||||
|
|
||||||
|
panGestureRecognizer.delegate = self
|
||||||
|
view.addGestureRecognizer(panGestureRecognizer)
|
||||||
|
|
||||||
|
transitioningDelegate = transitionController
|
||||||
|
transitionController.toDelegate = self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ImageNavigationController {
|
||||||
|
var currentViewController: ImageViewController? {
|
||||||
|
imagePageViewController.viewControllers?.first as? ImageViewController
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func didPanWith(gestureRecognizer: UIPanGestureRecognizer) {
|
||||||
|
guard let currentViewController = currentViewController else { return }
|
||||||
|
|
||||||
|
switch gestureRecognizer.state {
|
||||||
|
case .began:
|
||||||
|
currentViewController.scrollView.isScrollEnabled = false
|
||||||
|
transitionController.isInteractive = true
|
||||||
|
|
||||||
|
presentingViewController?.dismiss(animated: true)
|
||||||
|
case .ended:
|
||||||
|
if transitionController.isInteractive {
|
||||||
|
currentViewController.scrollView.isScrollEnabled = true
|
||||||
|
transitionController.isInteractive = false
|
||||||
|
transitionController.didPanWith(gestureRecognizer: gestureRecognizer)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if transitionController.isInteractive {
|
||||||
|
transitionController.didPanWith(gestureRecognizer: gestureRecognizer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ImageNavigationController: ZoomAnimatorDelegate {
|
||||||
|
func transitionWillStartWith(zoomAnimator: ZoomAnimator) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func transitionDidEndWith(zoomAnimator: ZoomAnimator) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func referenceView(for zoomAnimator: ZoomAnimator) -> UIView? {
|
||||||
|
if currentViewController?.playerView.player != nil {
|
||||||
|
return currentViewController?.playerView
|
||||||
|
} else {
|
||||||
|
return currentViewController?.imageView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func referenceViewFrameInTransitioningView(for zoomAnimator: ZoomAnimator) -> CGRect? {
|
||||||
|
guard let currentViewController = currentViewController else { return .zero }
|
||||||
|
|
||||||
|
let rect: CGRect
|
||||||
|
|
||||||
|
if let image = currentViewController.imageView.image {
|
||||||
|
rect = AVMakeRect(aspectRatio: image.size, insideRect: currentViewController.imageView.frame)
|
||||||
|
} else if let item = currentViewController.playerView.player?.currentItem {
|
||||||
|
rect = AVMakeRect(aspectRatio: item.presentationSize, insideRect: currentViewController.playerView.frame)
|
||||||
|
} else {
|
||||||
|
return .zero
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentViewController.scrollView.convert(rect, to: currentViewController.view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ImageNavigationController: UIGestureRecognizerDelegate {
|
||||||
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
|
||||||
|
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
if
|
||||||
|
let currentViewController = currentViewController,
|
||||||
|
otherGestureRecognizer == currentViewController.scrollView.panGestureRecognizer,
|
||||||
|
currentViewController.scrollView.contentOffset.y == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,12 @@ import UIKit
|
||||||
import ViewModels
|
import ViewModels
|
||||||
|
|
||||||
class ImageViewController: UIViewController {
|
class ImageViewController: UIViewController {
|
||||||
|
let scrollView = UIScrollView()
|
||||||
|
let imageView = AnimatedImageView()
|
||||||
|
let playerView = PlayerView()
|
||||||
|
|
||||||
private let viewModel: AttachmentViewModel
|
private let viewModel: AttachmentViewModel
|
||||||
private let scrollView = UIScrollView()
|
|
||||||
private let contentView = UIView()
|
private let contentView = UIView()
|
||||||
private let imageView = AnimatedImageView()
|
|
||||||
private let playerView = PlayerView()
|
|
||||||
private let descriptionBackgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemChromeMaterial))
|
private let descriptionBackgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemChromeMaterial))
|
||||||
private let descriptionTextView = UITextView()
|
private let descriptionTextView = UITextView()
|
||||||
|
|
||||||
|
@ -93,6 +94,7 @@ class ImageViewController: UIViewController {
|
||||||
|
|
||||||
switch viewModel.attachment.type {
|
switch viewModel.attachment.type {
|
||||||
case .image:
|
case .image:
|
||||||
|
imageView.tag = viewModel.tag
|
||||||
playerView.isHidden = true
|
playerView.isHidden = true
|
||||||
imageView.isHidden = false
|
imageView.isHidden = false
|
||||||
imageView.kf.indicatorType = .activity
|
imageView.kf.indicatorType = .activity
|
||||||
|
@ -111,6 +113,7 @@ class ImageViewController: UIViewController {
|
||||||
options: [.keepCurrentImageWhileLoading])
|
options: [.keepCurrentImageWhileLoading])
|
||||||
})
|
})
|
||||||
case .gifv:
|
case .gifv:
|
||||||
|
playerView.tag = viewModel.tag
|
||||||
playerView.isHidden = false
|
playerView.isHidden = false
|
||||||
imageView.isHidden = true
|
imageView.isHidden = true
|
||||||
let player = PlayerCache.shared.player(url: viewModel.attachment.url)
|
let player = PlayerCache.shared.player(url: viewModel.attachment.url)
|
||||||
|
|
|
@ -13,6 +13,7 @@ class TableViewController: UITableViewController {
|
||||||
private let webfingerIndicatorView = WebfingerIndicatorView()
|
private let webfingerIndicatorView = WebfingerIndicatorView()
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
private var cellHeightCaches = [CGFloat: [CollectionItem: CGFloat]]()
|
private var cellHeightCaches = [CGFloat: [CollectionItem: CGFloat]]()
|
||||||
|
private var transitionViewTag = -1
|
||||||
|
|
||||||
private lazy var dataSource: TableViewDataSource = {
|
private lazy var dataSource: TableViewDataSource = {
|
||||||
.init(tableView: tableView, viewModelProvider: viewModel.viewModel(indexPath:))
|
.init(tableView: tableView, viewModelProvider: viewModel.viewModel(indexPath:))
|
||||||
|
@ -181,10 +182,36 @@ extension TableViewController: AVPlayerViewControllerDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension TableViewController {
|
extension TableViewController: ZoomAnimatorDelegate {
|
||||||
static let autoplayViews = [PlayerView](repeating: .init(), count: 4)
|
func transitionWillStartWith(zoomAnimator: ZoomAnimator) {
|
||||||
static var visibleVideoURLs = Set<URL>()
|
view.layoutIfNeeded()
|
||||||
|
|
||||||
|
guard let imageViewController = (presentedViewController as? ImageNavigationController)?.currentViewController
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
if imageViewController.playerView.tag != 0 {
|
||||||
|
transitionViewTag = imageViewController.playerView.tag
|
||||||
|
} else if imageViewController.imageView.tag != 0 {
|
||||||
|
transitionViewTag = imageViewController.imageView.tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func transitionDidEndWith(zoomAnimator: ZoomAnimator) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func referenceView(for zoomAnimator: ZoomAnimator) -> UIView? {
|
||||||
|
tableView.visibleCells.compactMap { $0.viewWithTag(transitionViewTag) }.first
|
||||||
|
}
|
||||||
|
|
||||||
|
func referenceViewFrameInTransitioningView(for zoomAnimator: ZoomAnimator) -> CGRect? {
|
||||||
|
guard let referenceView = referenceView(for: zoomAnimator) else { return nil }
|
||||||
|
|
||||||
|
return tabBarController?.view.convert(referenceView.frame, from: referenceView.superview)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension TableViewController {
|
||||||
var visibleLoadMoreViews: [LoadMoreView] {
|
var visibleLoadMoreViews: [LoadMoreView] {
|
||||||
tableView.visibleCells.compactMap { $0.contentView as? LoadMoreView }
|
tableView.visibleCells.compactMap { $0.contentView as? LoadMoreView }
|
||||||
}
|
}
|
||||||
|
@ -315,6 +342,9 @@ private extension TableViewController {
|
||||||
statusViewModel: statusViewModel)
|
statusViewModel: statusViewModel)
|
||||||
let imageNavigationController = ImageNavigationController(imagePageViewController: imagePageViewController)
|
let imageNavigationController = ImageNavigationController(imagePageViewController: imagePageViewController)
|
||||||
|
|
||||||
|
imageNavigationController.transitionController.fromDelegate = self
|
||||||
|
transitionViewTag = attachmentViewModel.tag
|
||||||
|
|
||||||
present(imageNavigationController, animated: true)
|
present(imageNavigationController, animated: true)
|
||||||
case .unknown:
|
case .unknown:
|
||||||
break
|
break
|
||||||
|
|
|
@ -6,12 +6,19 @@ import Mastodon
|
||||||
public struct AttachmentViewModel {
|
public struct AttachmentViewModel {
|
||||||
public let attachment: Attachment
|
public let attachment: Attachment
|
||||||
|
|
||||||
init(attachment: Attachment) {
|
private let status: Status
|
||||||
|
|
||||||
|
init(attachment: Attachment, status: Status) {
|
||||||
self.attachment = attachment
|
self.attachment = attachment
|
||||||
|
self.status = status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension AttachmentViewModel {
|
public extension AttachmentViewModel {
|
||||||
|
var tag: Int {
|
||||||
|
attachment.id.appending(status.id).hashValue
|
||||||
|
}
|
||||||
|
|
||||||
var aspectRatio: Double? {
|
var aspectRatio: Double? {
|
||||||
if
|
if
|
||||||
let info = attachment.meta?.original,
|
let info = attachment.meta?.original,
|
||||||
|
|
|
@ -40,7 +40,7 @@ public struct StatusViewModel: CollectionItemViewModel {
|
||||||
: statusService.status.account.displayName
|
: statusService.status.account.displayName
|
||||||
rebloggedByDisplayNameEmoji = statusService.status.account.emojis
|
rebloggedByDisplayNameEmoji = statusService.status.account.emojis
|
||||||
attachmentViewModels = statusService.status.displayStatus.mediaAttachments
|
attachmentViewModels = statusService.status.displayStatus.mediaAttachments
|
||||||
.map(AttachmentViewModel.init(attachment:))
|
.map { AttachmentViewModel(attachment: $0, status: statusService.status) }
|
||||||
pollOptionTitles = statusService.status.displayStatus.poll?.options.map { $0.title } ?? []
|
pollOptionTitles = statusService.status.displayStatus.poll?.options.map { $0.title } ?? []
|
||||||
pollEmoji = statusService.status.displayStatus.poll?.emojis ?? []
|
pollEmoji = statusService.status.displayStatus.poll?.emojis ?? []
|
||||||
events = eventsSubject.eraseToAnyPublisher()
|
events = eventsSubject.eraseToAnyPublisher()
|
||||||
|
|
|
@ -14,8 +14,12 @@ final class StatusAttachmentView: UIView {
|
||||||
didSet {
|
didSet {
|
||||||
if playing {
|
if playing {
|
||||||
play()
|
play()
|
||||||
|
imageView.tag = 0
|
||||||
|
playerView.tag = viewModel.tag
|
||||||
} else {
|
} else {
|
||||||
stop()
|
stop()
|
||||||
|
imageView.tag = viewModel.tag
|
||||||
|
playerView.tag = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,6 +83,7 @@ private extension StatusAttachmentView {
|
||||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
imageView.contentMode = .scaleAspectFill
|
imageView.contentMode = .scaleAspectFill
|
||||||
imageView.clipsToBounds = true
|
imageView.clipsToBounds = true
|
||||||
|
imageView.tag = viewModel.tag
|
||||||
|
|
||||||
let blurEffect = UIBlurEffect(style: .systemUltraThinMaterial)
|
let blurEffect = UIBlurEffect(style: .systemUltraThinMaterial)
|
||||||
let playView = UIVisualEffectView(effect: blurEffect)
|
let playView = UIVisualEffectView(effect: blurEffect)
|
||||||
|
|
Loading…
Reference in a new issue