From 4249191b372cecde6d7dccd34af1a0c9eafc1f20 Mon Sep 17 00:00:00 2001 From: Bruno Muniz Azevedo Filho <bruno@elixxir.io> Date: Tue, 13 Dec 2022 21:18:28 -0300 Subject: [PATCH] Fixes scrolling of nickname drawer --- Package.swift | 4 + .../CustomTransitions/BottomTransition.swift | 8 +- .../BottomTransitioningDelegate.swift | 23 ++++ .../FullscreenTransition.swift | 124 ++++++++++++++++++ Sources/AppNavigation/PresentNickname.swift | 19 ++- 5 files changed, 166 insertions(+), 12 deletions(-) create mode 100644 Sources/AppNavigation/CustomTransitions/FullscreenTransition.swift diff --git a/Package.swift b/Package.swift index 3034bf51..b6116630 100644 --- a/Package.swift +++ b/Package.swift @@ -226,6 +226,10 @@ let package = Package( name: "XXModels", package: "client-ios-db" ), + .product( + name: "ScrollViewController", + package: "ScrollViewController" + ), .product( name: "Dependencies", package: "swift-composable-architecture" diff --git a/Sources/AppNavigation/CustomTransitions/BottomTransition.swift b/Sources/AppNavigation/CustomTransitions/BottomTransition.swift index 66b422c1..7c40eba0 100644 --- a/Sources/AppNavigation/CustomTransitions/BottomTransition.swift +++ b/Sources/AppNavigation/CustomTransitions/BottomTransition.swift @@ -1,5 +1,4 @@ import UIKit -import Combine final class BottomTransition: NSObject, UIViewControllerAnimatedTransitioning { enum Direction { @@ -9,11 +8,10 @@ final class BottomTransition: NSObject, UIViewControllerAnimatedTransitioning { let isDismissableOnBackground: Bool var direction: Direction = .present - private let onDismissal: (() -> Void)? + private let onDismissal: () -> Void private weak var darkOverlayView: UIControl? private weak var topConstraint: NSLayoutConstraint? private weak var bottomConstraint: NSLayoutConstraint? - private var cancellables = Set<AnyCancellable>() private var presentedConstraints: [NSLayoutConstraint] = [] private var dismissedConstraints: [NSLayoutConstraint] = [] @@ -21,7 +19,7 @@ final class BottomTransition: NSObject, UIViewControllerAnimatedTransitioning { init( _ isDismissableOnBackground: Bool = true, - onDismissal: (() -> Void)? + onDismissal: @escaping () -> Void ) { self.onDismissal = onDismissal self.isDismissableOnBackground = isDismissableOnBackground @@ -123,7 +121,7 @@ final class BottomTransition: NSObject, UIViewControllerAnimatedTransitioning { }, completion: { [weak self] _ in context.completeTransition(true) - self?.onDismissal?() + self?.onDismissal() } ) } diff --git a/Sources/AppNavigation/CustomTransitions/BottomTransitioningDelegate.swift b/Sources/AppNavigation/CustomTransitions/BottomTransitioningDelegate.swift index b30e5497..41e5ef29 100644 --- a/Sources/AppNavigation/CustomTransitions/BottomTransitioningDelegate.swift +++ b/Sources/AppNavigation/CustomTransitions/BottomTransitioningDelegate.swift @@ -23,3 +23,26 @@ final class BottomTransitioningDelegate: NSObject, UIViewControllerTransitioning return transition } } + +final class FullscreenTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate { + private var transition: FullscreenTransition? + + func animationController( + forPresented presented: UIViewController, + presenting: UIViewController, + source: UIViewController + ) -> UIViewControllerAnimatedTransitioning? { + transition = FullscreenTransition { [weak self] in + guard let self else { return } + self.transition = nil + } + return transition + } + + func animationController( + forDismissed dismissed: UIViewController + ) -> UIViewControllerAnimatedTransitioning? { + transition?.direction = .dismiss + return transition + } +} diff --git a/Sources/AppNavigation/CustomTransitions/FullscreenTransition.swift b/Sources/AppNavigation/CustomTransitions/FullscreenTransition.swift new file mode 100644 index 00000000..75d5fd04 --- /dev/null +++ b/Sources/AppNavigation/CustomTransitions/FullscreenTransition.swift @@ -0,0 +1,124 @@ +import UIKit + +final class FullscreenTransition: NSObject, UIViewControllerAnimatedTransitioning { + enum Direction { + case present + case dismiss + } + + var direction: Direction = .present + private let onDismissal: () -> Void + private weak var darkOverlayView: UIControl? + private weak var topConstraint: NSLayoutConstraint? + private weak var bottomConstraint: NSLayoutConstraint? + + private var presentedConstraints: [NSLayoutConstraint] = [] + private var dismissedConstraints: [NSLayoutConstraint] = [] + private var presentingController: UIViewController? + + init(onDismissal: @escaping () -> Void) { + self.onDismissal = onDismissal + super.init() + } + + func transitionDuration( + using context: UIViewControllerContextTransitioning? + ) -> TimeInterval { 0.5 } + + func animateTransition( + using context: UIViewControllerContextTransitioning + ) { + switch direction { + case .present: + present(using: context) + case .dismiss: + dismiss(using: context) + } + } + + private func present(using context: UIViewControllerContextTransitioning) { + guard let presentingController = context.viewController(forKey: .from), + let presentedView = context.view(forKey: .to) else { + context.completeTransition(false) + return + } + + let darkOverlayView = UIControl() + self.darkOverlayView = darkOverlayView + + darkOverlayView.alpha = 0.0 + darkOverlayView.backgroundColor = UIColor.black.withAlphaComponent(0.5) + context.containerView.addSubview(darkOverlayView) + darkOverlayView.frame = context.containerView.bounds + + darkOverlayView.addTarget(self, action: #selector(didTapOverlay), for: .touchUpInside) + self.presentingController = presentingController + + context.containerView.addSubview(presentedView) + presentedView.translatesAutoresizingMaskIntoConstraints = false + + presentedConstraints = [ + presentedView.topAnchor.constraint(equalTo: context.containerView.topAnchor), + presentedView.leftAnchor.constraint(equalTo: context.containerView.leftAnchor), + presentedView.rightAnchor.constraint(equalTo: context.containerView.rightAnchor), + presentedView.bottomAnchor.constraint(equalTo: context.containerView.bottomAnchor) + ] + + dismissedConstraints = [ + presentedView.leftAnchor.constraint(equalTo: context.containerView.leftAnchor), + presentedView.rightAnchor.constraint(equalTo: context.containerView.rightAnchor), + presentedView.topAnchor.constraint(equalTo: context.containerView.bottomAnchor), + presentedView.heightAnchor.constraint(equalTo: context.containerView.heightAnchor) + ] + + NSLayoutConstraint.activate(dismissedConstraints) + + context.containerView.setNeedsLayout() + context.containerView.layoutIfNeeded() + + NSLayoutConstraint.deactivate(dismissedConstraints) + NSLayoutConstraint.activate(presentedConstraints) + + UIView.animate( + withDuration: transitionDuration(using: context), + delay: 0, + usingSpringWithDamping: 1, + initialSpringVelocity: 0, + options: .curveEaseInOut, + animations: { + darkOverlayView.alpha = 1.0 + context.containerView.setNeedsLayout() + context.containerView.layoutIfNeeded() + }, + completion: { _ in + context.completeTransition(true) + }) + } + + private func dismiss(using context: UIViewControllerContextTransitioning) { + NSLayoutConstraint.deactivate(presentedConstraints) + NSLayoutConstraint.activate(dismissedConstraints) + + UIView.animate( + withDuration: transitionDuration(using: context), + delay: 0, + usingSpringWithDamping: 1, + initialSpringVelocity: 0, + options: .curveEaseInOut, + animations: { [weak darkOverlayView] in + darkOverlayView?.alpha = 0.0 + context.containerView.setNeedsLayout() + context.containerView.layoutIfNeeded() + }, + completion: { [weak self] _ in + context.completeTransition(true) + self?.onDismissal() + }) + } + + @objc private func didTapOverlay() { + if let presentingController { + presentingController.dismiss(animated: true) + } + } +} diff --git a/Sources/AppNavigation/PresentNickname.swift b/Sources/AppNavigation/PresentNickname.swift index 80121bbf..96bb9295 100644 --- a/Sources/AppNavigation/PresentNickname.swift +++ b/Sources/AppNavigation/PresentNickname.swift @@ -1,4 +1,5 @@ import UIKit +import ScrollViewController /// Opens up `Nickname` on a given parent view controller public struct PresentNickname: Action { @@ -35,27 +36,31 @@ public struct PresentNickname: Action { /// Performs `PresentNickname` action public struct PresentNicknameNavigator: TypedNavigator { /// Custom transitioning delegate - let transitioningDelegate = BottomTransitioningDelegate() + let transitioningDelegate = FullscreenTransitioningDelegate() /// View controller which should be opened up var viewController: (String, @escaping (String) -> Void) -> UIViewController /// - Parameters: /// - viewController: view controller which should be presented - public init(_ viewController: @escaping ( - String, @escaping (String) -> Void - ) -> UIViewController - ) { + public init(_ viewController: @escaping (String, @escaping (String) -> Void) -> UIViewController) { self.viewController = viewController } public func perform(_ action: PresentNickname, completion: @escaping () -> Void) { + let scrollViewController = ScrollViewController() let controller = viewController(action.prefilled, action.completion) + scrollViewController.addChild(controller) + scrollViewController.contentView = controller.view + scrollViewController.wrapperView.handlesTouchesOutsideContent = true + scrollViewController.wrapperView.alignContentToBottom = true + scrollViewController.scrollView.bounces = false + controller.didMove(toParent: scrollViewController) controller.transitioningDelegate = transitioningDelegate - controller.modalPresentationStyle = .overFullScreen + scrollViewController.modalPresentationStyle = .overFullScreen action.parent.present( - controller, + scrollViewController, animated: action.animated, completion: completion ) -- GitLab