UIPresentationController with interactive presenting view

649 views Asked by At

I looked at this question, but it does not help: Interacting with presenting view and UIPresentationController


I am trying to implement a sheet presentation controller, similar to the UISheetPresentationController for iOS 15, except I need it to run on iOS 14 as well. And I am also wanting to make it so that it has a small detent, similar to how it is done in the Maps app.

So I have a custom UIPresentationController class and I don't have much in it yet, but is what I have so far:

- (CGRect)frameOfPresentedViewInContainerView {
    [super frameOfPresentedViewInContainerView];
    CGRect presentedViewFrame = CGRectZero;
    CGRect containerBounds = self.containerView.bounds;
    presentedViewFrame.size = CGSizeMake(containerBounds.size.width, floor(containerBounds.size.height * 0.5));
    presentedViewFrame.origin = CGPointMake(0, containerBounds.size.height - presentedViewFrame.size.height);
    return presentedViewFrame;
}

- (BOOL)shouldPresentInFullscreen {
    return NO;
}

- (BOOL)shouldRemovePresentersView {
    return NO;
}

And this does work. It does display the view controller at half of the height of the presenting view controller. The problem is that the presenting view is no longer interactive because there is a view that gets added by the presentation controller class apparently.

So my question is how do I get the presenting view to be interactive, where I can scroll it and interact with buttons and the other controls? I want to be able to use a presentation controller to present the view controller.

1

There are 1 answers

0
HangarRash On

The following allows you to present a shorter modal view controller while still allowing interaction with the presenting view controller. This doesn't attempt to implement what you get with the newer UISheetPresentationController. This only solves the issue of being able to interact with both view controllers while the shorter second controller is in view.

This approach makes use of a custom UIPresentationController. This avoids the need to deal with custom container views and animating the display of the presented view.

Start with the following custom UIPresentationController class:

import UIKit

class ShortPresentationController: UIPresentationController {
    override var shouldPresentInFullscreen: Bool {
        // We don't want full screen
        return false
    }

    override var frameOfPresentedViewInContainerView: CGRect {
        let size = containerView?.frame.size ?? presentingViewController.view.frame.size

        // Since the containerView's frame has been resized already, we just need to return a frame of the same
        // size with a 0,0 origin.
        return CGRect(origin: .zero, size: size)
    }

    override func presentationTransitionWillBegin() {
        super.presentationTransitionWillBegin()

        guard let containerView = containerView else { return }

        // By default the containerView's frame covers the screen which prevents interacting with the presenting view controller.
        // Update the containerView's frame to match the area needed by the presented view controller. This allows
        // interection with the presenting view controller even while the presented view controller is in view.
        //
        // This code assumes we want the presented view controller to use the full width of the presenting view controller
        // while honoring the preferredContentSize height. It also assumes we want the bottom of the presented view
        // controller to appear at the bottom of the presenting view controller. Adjust as needed.
        let containerSize = containerView.bounds.size
        let preferredSize = presentedViewController.preferredContentSize
        containerView.frame = CGRect(x: 0, y: containerSize.height - preferredSize.height,
                                     width: containerSize.width, height: preferredSize.height)
    }
}

In the presenting view controller you need to create and present the short view controller. This is fairly typical code for presenting a modal view controller with the important differences of setting the style to custom and assigning the transitioningDelegate.

FirstViewController.swift:

let vc = SecondViewController()
let nc = UINavigationController(rootViewController: vc)
nc.modalPresentationStyle = .custom
nc.transitioningDelegate = self
present(nc, animated: true)

You need to implement one method of the transition delegate in FirstViewController to return the custom presentation controller:

extension FirstViewController: UIViewControllerTransitioningDelegate {
    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        return ShortPresentationController(presentedViewController: presented, presenting: presenting)
    }
}

And lastly, make sure you set the preferredContentSize property of the second view controller. One typical place is in the viewDidLoad of SecondViewController:

preferredContentSize = CGSize(width: 320, height: 300)

That does not include the navigation controller bars (if any). If you want the presenting view controller to set the final size, including the bars, you could set the preferredContentSize on nc just before presenting it. It depends on who you want to dictate the preferred size.