0

I want to build a custom UIPresenterController that present the subsequent UIViewController from the right side of the screen to the left. I have built out the following UIPresenterController; however, on present, this comes from the bottom of the screen (while the dismiss works perfect). Does anyone have any suggestions on how I could fix this?


@available(iOS 13.0, *)
open class CUICRightSidePopUpTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {

    var presentedController: CUICRightSidePopUpPresentationController? = nil

    public override init() {
        super.init()
    }

    public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        presentedController = CUICRightSidePopUpPresentationController(presentedViewController: presented, presenting: presenting)
        guard let presentedController = presentedController else { return nil }
        return presentedController
    }
}

@available(iOS 13.0, *)
open class CUICRightSidePopUpPresentationController: UIPresentationController {
    open override var frameOfPresentedViewInContainerView: CGRect {
        let bounds = presentingViewController.view.bounds

        let size = CGSize(width: UIScreen.main.bounds.width * viewRatio, height: UIScreen.main.bounds.height)
        let origin = CGPoint(x: UIScreen.main.bounds.width - size.width, y: bounds.midY - size.height / 2)
        return CGRect(origin: origin, size: size)
    }

    public override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
        super.init(presentedViewController: presentedViewController, presenting: presentingViewController)

        presentedViewController.view.addGestureRecognizer(panGestureRecognizer)

        presentedView?.autoresizingMask = [
            .flexibleTopMargin,
            .flexibleBottomMargin,
            .flexibleLeftMargin,
            .flexibleRightMargin
        ]

        presentedViewController.view.layer.cornerRadius = viewRadius
        presentedViewController.view.clipsToBounds = true

        dimmingView.backgroundColor = UIColor.black.withAlphaComponent(0.7)

        presentedView?.translatesAutoresizingMaskIntoConstraints = true
    }

    let viewRadius: CGFloat = 35
    let viewRatio: CGFloat = 4 / 5

    private let dimmingView: UIView = {
        let dimmingView = UIView(frame: .zero)
        dimmingView.translatesAutoresizingMaskIntoConstraints = false
        return dimmingView
    }()

    @objc func dimmingViewTapped(_ sender: UITapGestureRecognizer) {
        presentedViewController.dismiss(animated: true)
    }

    private lazy var panGestureRecognizer: UIPanGestureRecognizer = {
        let recognizer = UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognizerAction(sender:)))
        return recognizer
    }()

    private let indicatorView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .lightGray
        view.layer.cornerRadius = 4
        view.clipsToBounds = true
        return view
    }()

    open override func presentationTransitionWillBegin() {
        guard let containerView = containerView else { return }

        containerView.addSubview(dimmingView)
        containerView.addSubview(indicatorView)
        containerView.addSubview(presentedViewController.view)

        let initialFrame = CGRect(x: containerView.bounds.width, y: 0, width: frameOfPresentedViewInContainerView.width, height: frameOfPresentedViewInContainerView.height)
        presentedViewController.view.frame = initialFrame

        NSLayoutConstraint.activate([
            dimmingView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
            dimmingView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
            dimmingView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
            dimmingView.topAnchor.constraint(equalTo: containerView.topAnchor),
        ])
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dimmingViewTapped(_:)))
        dimmingView.addGestureRecognizer(tapGesture)

        NSLayoutConstraint.activate([
            indicatorView.widthAnchor.constraint(equalToConstant: 8),
            indicatorView.heightAnchor.constraint(equalToConstant: 65),
            indicatorView.trailingAnchor.constraint(equalTo: presentedViewController.view.leadingAnchor, constant: .smallPaddingRight),
            indicatorView.centerYAnchor.constraint(equalTo: presentedViewController.view.centerYAnchor)
        ])

        dimmingView.alpha = 0
        presentedViewController.view.frame = CGRect(x: self.containerView?.bounds.width ?? 0, y: 0, width: self.frameOfPresentedViewInContainerView.width, height: self.frameOfPresentedViewInContainerView.height)
        presentedViewController.transitionCoordinator?.animate(alongsideTransition: { [weak self] _ in
            self?.dimmingView.alpha = 0.7
            self?.presentedViewController.view.frame = self?.frameOfPresentedViewInContainerView ?? initialFrame
        }, completion: nil)
    }

    open override func dismissalTransitionWillBegin() {
        presentingViewController.transitionCoordinator?.animate(alongsideTransition: { [weak self] _ in
            guard let self = self else { return }
            self.dimmingView.alpha = 0
            self.presentedViewController.view.frame = CGRect(x: self.containerView?.bounds.width ?? 0, y: 0, width: self.frameOfPresentedViewInContainerView.width, height: self.frameOfPresentedViewInContainerView.height)
            self.indicatorView.frame.origin.x = (self.containerView?.bounds.width ?? 0)
        }, completion: { [weak self] _ in
            self?.dimmingView.removeFromSuperview()
            self?.indicatorView.removeFromSuperview()
        })
    }

    @objc func panGestureRecognizerAction(sender: UIPanGestureRecognizer) {
        guard let containerView = containerView else { return }
        let translation = sender.translation(in: containerView)

        // Not allowing the user to drag the view upward
        guard translation.x >= 0 else { return }

        let initialFrame = frameOfPresentedViewInContainerView

        // Move the main view and the indicator view horizontally
        let newX = initialFrame.origin.x + translation.x
        presentedViewController.view.frame.origin.x = newX
        indicatorView.frame.origin.x = newX - CGFloat.smallPaddingLeft - indicatorView.frame.width

        if sender.state == .ended {
            let draggedToDismiss = (translation.x > containerView.bounds.width / 3.0)
            let dragVelocity = sender.velocity(in: containerView)
            if (dragVelocity.x >= 1300) || draggedToDismiss {
                presentedViewController.dismiss(animated: true, completion: nil)
            } else {
                // Set back to original position of the view controller
                UIView.animate(withDuration: 0.3) {
                    self.presentedViewController.view.frame.origin.x = initialFrame.origin.x
                    self.indicatorView.frame.origin.x = initialFrame.origin.x - 12 - self.indicatorView.frame.width
                }
            }
        }
    }
}

For additional context, this is how I am configuring transition in my view controller where configure() is run in the viewDidLoad() and the customTransitionDelegate is set to be CUICRightSidePopUpTransitioningDelegate:

extension MyViewController {
    func configure() {
        modalPresentationStyle = .custom
        transitioningDelegate = customTransitioningDelegate
    }
}
helloworld12345
  • 176
  • 1
  • 4
  • 22

1 Answers1

-1

Use navigation controller, you can change the direction according to your need, Please check the given link too.

self.navigationController?.pushViewController(viewController, animated: true)

swift: make navigation bar Right-to-left (rtl) with both right and left items like back

  • This doesn't allow me to push the new view controller over current context though – helloworld12345 May 01 '23 at 21:56
  • That means your current view controller doesn't have Navigation controller. You can easily create that view controller into navigation controller. **let navigationController = UINavigationController(rootViewController: vc) navigationController.isModalInPresentation = true navigationController.modalPresentationStyle = .fullScreen self.present(navigationController, animated: true, completion: nil)** After that you can easily, push new view controller according to your choice. – Ranjit Singh May 02 '23 at 05:46