0e27410460
This PR modifies the transitionDuration(using:) method in FloatingPanelController to return 0.0 instead of calling fatalError when the FloatingPanelController instance is not found. This change is crucial for several reasons: 1. Avoiding Crashes in Production: Using fatalError in production can lead to unexpected crashes, which are detrimental to user experience. It's safer to return a default value like 0.0 and handle the scenario gracefully. 2. Improved Stability: By returning 0.0, the application can continue running, allowing for better stability and user satisfaction. This approach also aligns with the principle of fail-safety, where the system continues operating under error conditions. 3. Real-World Case: In our large-scale project, we encountered a crash at this specific point due to the fatalError. Given the size and complexity of our application, it has been challenging to pinpoint the exact cause. Switching to a return value of 0.0 would significantly help us mitigate the issue and maintain app stability while we investigate further. 4. Maintainability: Returning a default value makes the codebase more maintainable and easier to debug, as it avoids abrupt termination and allows for logging or other error handling mechanisms.
150 lines
5.8 KiB
Swift
150 lines
5.8 KiB
Swift
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
|
|
|
import UIKit
|
|
|
|
class ModalTransition: NSObject, UIViewControllerTransitioningDelegate {
|
|
func animationController(forPresented presented: UIViewController,
|
|
presenting: UIViewController,
|
|
source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
|
return ModalPresentTransition()
|
|
}
|
|
|
|
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
|
return ModalDismissTransition()
|
|
}
|
|
|
|
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
|
|
return PresentationController(presentedViewController: presented, presenting: presenting)
|
|
}
|
|
}
|
|
|
|
class PresentationController: UIPresentationController {
|
|
override func presentationTransitionWillBegin() {
|
|
// Must call here even if duplicating on in containerViewWillLayoutSubviews()
|
|
// Because it let the panel present correctly with the presentation animation
|
|
addFloatingPanel()
|
|
}
|
|
|
|
override func presentationTransitionDidEnd(_ completed: Bool) {
|
|
// For non-animated presentation
|
|
if let fpc = presentedViewController as? FloatingPanelController, fpc.state == .hidden {
|
|
fpc.show(animated: false, completion: nil)
|
|
}
|
|
}
|
|
|
|
override func dismissalTransitionDidEnd(_ completed: Bool) {
|
|
if let fpc = presentedViewController as? FloatingPanelController {
|
|
// For non-animated dismissal
|
|
if fpc.state != .hidden {
|
|
fpc.hide(animated: false, completion: nil)
|
|
}
|
|
fpc.view.removeFromSuperview()
|
|
}
|
|
}
|
|
|
|
override func containerViewWillLayoutSubviews() {
|
|
guard
|
|
let fpc = presentedViewController as? FloatingPanelController,
|
|
/**
|
|
This condition fixes https://github.com/SCENEE/FloatingPanel/issues/369.
|
|
The issue is that this method is called in presenting a
|
|
UIImagePickerViewController and then a FloatingPanelController
|
|
view is added unnecessarily.
|
|
*/
|
|
fpc.presentedViewController == nil
|
|
else { return }
|
|
|
|
/*
|
|
* Layout the views managed by `FloatingPanelController` here for the
|
|
* sake of the presentation and dismissal modally from the controller.
|
|
*/
|
|
addFloatingPanel()
|
|
|
|
// Forward touch events to the presenting view controller
|
|
(fpc.view as? PassthroughView)?.eventForwardingView = presentingViewController.view
|
|
}
|
|
|
|
@objc func handleBackdrop(tapGesture: UITapGestureRecognizer) {
|
|
presentedViewController.dismiss(animated: true, completion: nil)
|
|
}
|
|
|
|
private func addFloatingPanel() {
|
|
guard
|
|
let containerView = self.containerView,
|
|
let fpc = presentedViewController as? FloatingPanelController
|
|
else { fatalError() }
|
|
|
|
containerView.addSubview(fpc.view)
|
|
fpc.view.frame = containerView.bounds
|
|
fpc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
}
|
|
}
|
|
|
|
class ModalPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
|
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
|
guard
|
|
let fpc = transitionContext?.viewController(forKey: .to) as? FloatingPanelController
|
|
else { return 0.0 }
|
|
|
|
let animator = fpc.animatorForPresenting(to: fpc.layout.initialState)
|
|
return TimeInterval(animator.duration)
|
|
}
|
|
|
|
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
|
|
guard
|
|
let fpc = transitionContext.viewController(forKey: .to) as? FloatingPanelController
|
|
else { fatalError() }
|
|
|
|
if let animator = fpc.transitionAnimator {
|
|
return animator
|
|
}
|
|
|
|
fpc.suspendTransitionAnimator(true)
|
|
fpc.show(animated: true) { [weak fpc] in
|
|
fpc?.suspendTransitionAnimator(false)
|
|
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
|
}
|
|
guard let transitionAnimator = fpc.transitionAnimator else {
|
|
fatalError("The panel state must be `hidden` but it is `\(fpc.state)`")
|
|
}
|
|
return transitionAnimator
|
|
}
|
|
|
|
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
|
self.interruptibleAnimator(using: transitionContext).startAnimation()
|
|
}
|
|
}
|
|
|
|
class ModalDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
|
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
|
guard
|
|
let fpc = transitionContext?.viewController(forKey: .from) as? FloatingPanelController
|
|
else { return 0.0 }
|
|
|
|
let animator = fpc.animatorForDismissing(with: .zero)
|
|
return TimeInterval(animator.duration)
|
|
}
|
|
|
|
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
|
|
guard
|
|
let fpc = transitionContext.viewController(forKey: .from) as? FloatingPanelController
|
|
else { fatalError() }
|
|
|
|
if let animator = fpc.transitionAnimator {
|
|
return animator
|
|
}
|
|
|
|
fpc.suspendTransitionAnimator(true)
|
|
fpc.hide(animated: true) { [weak fpc] in
|
|
fpc?.suspendTransitionAnimator(false)
|
|
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
|
}
|
|
return fpc.transitionAnimator!
|
|
}
|
|
|
|
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
|
self.interruptibleAnimator(using: transitionContext).startAnimation()
|
|
}
|
|
}
|
|
|