21
votes

is there a way to easily recreate the modal presentation style of ios 13' new share sheet? (At first, it's only presented halfway and you can swipe up to make it a "full" modal sheet) I can do it using a completely custom presentation and stuff but is there a "native" api for this behavior so that you don't have to use custom code?

Thanks!

enter image description here

2
Isn’t it just a frozen interactive version of the normal sheet presentation? - matt
Not entirely, the background doesn't start shrinking until you swipe up to make it an actual modal sheet. - Quantm
I looked into this and can share what I learned. I think what it comes down to is somehow getting the modally presented view controller to respect the preferredContentSize you set... if you inspect view hierarchy the root VC on the system share sheet is presented using a vc.modalPresentationStyle = .formSheet, then there's an embedded navVC/activityVC that both are presented with a .pageSheet style. I can confirm there are no transition or interaction controllers of any kind, and although the system uses a slightly different UIPresentationController the values on both are identical - gadu
I confirmed that only the root VC for the system share sheet (the one presented with .formSheet has a custom preferredContentSize and the children ones do not, so I think it only matters on the original presented VC. I tried overriding size(forChildContentContainer: withParentContainerSize:) on the view controller presenting the child, and I tried setting the preferredContentSize like ALL over the view lifecycle on the child, but although the value can be inspected to exist, it doesn't respect it. Transition style on default + system both default (vertical). - gadu
Theres also no vertical constraints with heights, it seems to be exclusively bc of preferredContentSize. While typically used for .popover style, docs in theory say this should be supported "in some cases" too: developer.apple.com/documentation/uikit/uiviewcontroller/…. Perhaps noodling with some of the other methods in the UIContentContainer might help? (viewWillTransition(to:with:)) - gadu

2 Answers

31
votes

Here's what I've tried. I've created a new ViewController class extending UIActivityViewController. And in the viewDidLoad function, I removed all the child views from the controller and added my viewController as a child to it. It seems to be working fine. Although, it is more of a work around it is still sufficient enough for the requirement it seems. Code snippet is as follows. Give it a try.

import UIKit

class CustomActivityViewController: UIActivityViewController {

    private let controller: UIViewController!

    required init(controller: UIViewController) {
        self.controller = controller
        super.init(activityItems: [], applicationActivities: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let subViews = self.view.subviews
        for view in subViews {
            view.removeFromSuperview()
        }

        self.addChild(controller)
        self.view.addSubview(controller.view)
    }

}

Above is the CustomActivityViewController. And you can add your viewController into it as follows.

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "YourViewController")
let activityViewController = CustomActivityViewController(controller: controller)
self.present(activityViewController, animated: true, completion: nil)

enter image description here

6
votes

If you are interested in private API, this is how Apple does it:

There is a class named _UISheetDetent. With this class, you can either create system defined "detents"—medium and large—or provide your own block-based logic (input param is the presentation controller's container view, and the return value is a double—the percent to open the sheet).

You create an array of these "detents" and provide them to the sheet presentation controller using the _setDetents: method (or setValue:forKey:). To replicate the share sheet behavior, you need an array with two "detents": medium and large.

There is also the _indexOfLastUndimmedDetent property, which controls which "detent" starts the dimming process.

I'm not sure why Apple hasn't exposed this as public API. It is concise, simple and works well.

You should probably not use this API, but if you decide to use it, it should be very easy to hide it. In any case, make sure to open a Feedback with Apple to expose this API in a future version of the SDK.