element-ios/Riot/Modules/SlidingModal/SlidingModalPresentationAni...

133 lines
5.5 KiB
Swift

/*
Copyright 2019-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import UIKit
/// `SlidingModalPresentationAnimator` handles the animations for a custom sliding view controller transition.
final class SlidingModalPresentationAnimator: NSObject {
// MARK: - Constants
private enum AnimationDuration {
static let presentation: TimeInterval = 0.2
static let dismissal: TimeInterval = 0.3
}
// MARK: - Properties
private let isPresenting: Bool
private let options: SlidingModalOption
// MARK: - Setup
/// Instantiate a SlidingModalPresentationAnimator object.
///
/// - Parameter isPresenting: true to animate presentation or false to animate dismissal
/// - Parameter isSpanning: true to remove left, bottom and right spaces between the screen edges and the content view
required public init(isPresenting: Bool, options: SlidingModalOption) {
self.isPresenting = isPresenting
self.options = options
super.init()
}
// MARK: - Private
// Animate presented view controller presentation
private func animatePresentation(using transitionContext: UIViewControllerContextTransitioning) {
guard let presentedViewController = transitionContext.viewController(forKey: .to),
transitionContext.viewController(forKey: .from) != nil else {
return
}
guard let presentedViewControllerView = presentedViewController.view else {
return
}
let containerView = transitionContext.containerView
// Spanning not available for iPad
let slidingModalContainerView = options.contains(.spanning) && UIDevice.current.userInterfaceIdiom != .pad ? SpanningSlidingModalContainerView.instantiate() : SlidingModalContainerView.instantiate()
slidingModalContainerView.blurBackground = options.contains(.blurBackground)
slidingModalContainerView.centerInScreen = options.contains(.centerInScreen)
slidingModalContainerView.alpha = 0
slidingModalContainerView.updateDimmingViewAlpha(0.0)
// Add presented view controller view to slidingModalContainerView content view
slidingModalContainerView.setContentView(presentedViewControllerView)
// Add slidingModalContainerView to container view
containerView.vc_addSubViewMatchingParent(slidingModalContainerView)
containerView.layoutIfNeeded()
// Adapt slidingModalContainerView content view height from presentedViewControllerView height
if let slidingModalPresentable = presentedViewController as? SlidingModalPresentable {
let slidingModalContainerViewContentViewWidth = slidingModalContainerView.contentViewFrame.width
let presentableHeight = slidingModalPresentable.layoutHeightFittingWidth(slidingModalContainerViewContentViewWidth)
slidingModalContainerView.updateContentViewMaxHeight(presentableHeight)
slidingModalContainerView.updateContentViewLayout()
}
// Hide slidingModalContainerView content view
slidingModalContainerView.prepareDismissAnimation()
containerView.layoutIfNeeded()
let animationDuration = self.transitionDuration(using: transitionContext)
slidingModalContainerView.preparePresentAnimation()
slidingModalContainerView.alpha = 1
UIView.animate(withDuration: animationDuration, animations: {
containerView.layoutIfNeeded()
slidingModalContainerView.updateDimmingViewAlpha(1.0)
}, completion: { completed in
transitionContext.completeTransition(completed)
})
}
// Animate presented view controller dismissal
private func animateDismissal(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let slidingModalContainerView = self.slidingModalContainerView(from: transitionContext)
let animationDuration = self.transitionDuration(using: transitionContext)
slidingModalContainerView?.prepareDismissAnimation()
UIView.animate(withDuration: animationDuration, animations: {
containerView.layoutIfNeeded()
slidingModalContainerView?.updateDimmingViewAlpha(0.0)
}, completion: { completed in
transitionContext.completeTransition(completed)
})
}
private func slidingModalContainerView(from transitionContext: UIViewControllerContextTransitioning) -> SlidingModalContainerView? {
let modalContentView = transitionContext.containerView.subviews.first(where: { view -> Bool in
view is SlidingModalContainerView
}) as? SlidingModalContainerView
return modalContentView
}
}
// MARK: - UIViewControllerAnimatedTransitioning
extension SlidingModalPresentationAnimator: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return self.isPresenting ? AnimationDuration.presentation : AnimationDuration.dismissal
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if self.isPresenting {
self.animatePresentation(using: transitionContext)
} else {
self.animateDismissal(using: transitionContext)
}
}
}