iPhone Duo: Making a custom popup avoid the hinge by presenting it as an arrowless popover

Background

On iPhone Duo, when the device is unfolded and then partially folded, system alerts (UIAlertController) are automatically repositioned so they stay clear of the hinge. Apple's Tech Talk confirms this:

"The system automatically repositions components around the reserved region."

Our app uses a custom popup: a view controller presented with .overFullScreen that lays out its own dimming view and a custom-drawn view (here, a coupon ticket with notched edges) in the center of the screen. This popup is not repositioned. It stays centered on screen, right on top of the fold.

The recommended approach for custom UI is to query view.reservedRegions(kind: .division) and lay out around the fold manually. Before going that route, I tried something simpler.

What I tried

The Tech Talk lists popovers among the components the system repositions. So I presented the same custom view as a popover with no arrow, anchored to the center of the presenting view:

final class CouponPopoverViewController: UIViewController, UIPopoverPresentationControllerDelegate {

    private let ticketView = CouponTicketView()   // the custom popup view

    init(centeredIn sourceView: UIView) {
        super.init(nibName: nil, bundle: nil)
        modalPresentationStyle = .popover

        guard let popover = popoverPresentationController else { return }
        popover.sourceView = sourceView
        popover.sourceRect = Self.centerPoint(of: sourceView.bounds)
        popover.permittedArrowDirections = []
        // Drop the system popover chrome so only the custom view (and its own shadow) is visible.
        popover.popoverBackgroundViewClass = ClearPopoverBackgroundView.self
        popover.delegate = self
    }

    required init?(coder: NSCoder) { fatalError() }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .clear
        ticketView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(ticketView)
        NSLayoutConstraint.activate([
            ticketView.topAnchor.constraint(equalTo: view.topAnchor),
            ticketView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            ticketView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            ticketView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
        ])
        preferredContentSize = ticketView.systemLayoutSizeFitting(
            CGSize(width: 300, height: UIView.layoutFittingCompressedSize.height),
            withHorizontalFittingPriority: .required,
            verticalFittingPriority: .fittingSizeLevel
        )
    }

    private static func centerPoint(of bounds: CGRect) -> CGRect {
        CGRect(x: bounds.midX, y: bounds.midY, width: 0, height: 0)
    }

    // Keep it a popover on iPhone instead of adapting to a sheet.
    func adaptivePresentationStyle(for controller: UIPresentationController,
                                   traitCollection: UITraitCollection) -> UIModalPresentationStyle {
        .none
    }

    // Re-anchor to the center after rotation or fold changes.
    func popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController,
                                       willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>,
                                       in view: AutoreleasingUnsafeMutablePointer<UIView>) {
        rect.pointee = Self.centerPoint(of: view.pointee.bounds)
    }
}

/// A popover background that draws nothing: no arrow, no insets, no default appearance.
final class ClearPopoverBackgroundView: UIPopoverBackgroundView {
    override class func arrowBase() -> CGFloat { 0 }
    override class func arrowHeight() -> CGFloat { 0 }
    override class func contentViewInsets() -> UIEdgeInsets { .zero }
    override class var wantsDefaultContentAppearance: Bool { false }
    override var arrowOffset: CGFloat { get { 0 } set {} }
    override var arrowDirection: UIPopoverArrowDirection { get { .any } set {} }
}

Result

Partially folded, portrait (hinge runs horizontally across the middle of the screen):

Before: the custom popup (.overFullScreen) stays centered, right across the hinge.

After: the same view presented as an arrowless popover moves into the top half, clear of the hinge.

Partially folded, book pose (vertical hinge): the popover moves to the leading page, clear of the hinge. This matches where a system UIAlertController goes in the same pose.

I also checked that the popover shows correctly on the outer display (closed) and on a non-Duo iPhone (iPhone 18 Pro, iOS 27.0).

Tested on:

  • iPhone Duo simulator (iOS 27.1), Xcode 27.1 RC (27A9275)
  • iPhone 18 Pro simulator (iOS 27.0)

Things to handle when switching to a popover

  • Tap-outside dismissal: Popovers dismiss on outside taps by default. Set isModalInPresentation = true or return false from presentationControllerShouldDismiss(_:) if the popup should only close via its buttons.
  • Dimming: Popovers don't dim the background like a typical custom popup (compare the before and after screenshots). Add your own dimming view if needed.
  • Chrome: By default, the system draws its own background behind the content. For a custom-shaped view, this shows through any transparent areas and clips the view's shadow. A UIPopoverBackgroundView subclass that draws nothing (above) removes it.
  • Sizing: Set preferredContentSize explicitly. Recompute it for dynamic content and Dynamic Type.

Questions

This works well in my testing, but I couldn't find any documentation saying that an arrowless popover anchored to the center gets the same hinge avoidance as alerts.

  1. Is this supported behavior that we can rely on going forward?
  2. Or is reservedRegions(kind:options:) with manual layout the only recommended approach for custom popups?

It would also be great to have a public API that lets custom presentations opt into the same hinge-avoidance placement that system alerts use.

Hope this helps anyone else dealing with custom popups on iPhone Duo.

Answered by Frameworks Engineer in 909788022

Hi, arrow-less popovers are indeed expected to work this way, so it's supported! If a popover works for your use case, feel free to use it. Arrangement Views or Reserved Regions are a great option for more custom use cases.

Hi, arrow-less popovers are indeed expected to work this way, so it's supported! If a popover works for your use case, feel free to use it. Arrangement Views or Reserved Regions are a great option for more custom use cases.

Hello @Yongsoo_Kim,

Thank you for creating a detailed post with images and code examples. Please see the confirmation above which answers your second question explicitly.

I would like to provide more context around your first question

Is this supported behavior that we can rely on going forward?

Note, “Supported" can mean a few things in this context:

  • Supported can mean what is available in the SDK.
  • Supported can mean what is documented.
  • Supported can mean what is allowed in the App Store.

When it comes to support:

Whether something is documented or not does not solely determine support.

You may come across behavior that is not documented but runs on device, however, some of it may be an implementation detail that is not intended to be relied on by apps. 

While DTS cannot guarantee what is accepted by App Review, if you would like confirmation if this specific implementation is intended, sharing your code here on in Feedback Assistant is a great way to get a confirmation to the later – and it seems @Frameworks Engineer provided a response directly! 
 I hope this information is helpful.

 Travis

iPhone Duo: Making a custom popup avoid the hinge by presenting it as an arrowless popover
 
 
Q