Expanded surfaces

A takeover on the notch, for something that needs the whole shelf.

An expanded surface replaces the shelf's normal contents with yours: a picker, a form, a full view of something a widget can only summarise. It is the biggest thing a droplet can put on screen, and the one that most needs restraint — while it is up, the user cannot see anything else Droppy would have shown.

Requires the expanded-surface capability.

Declaring one

extension WorldClockDroplet: ExpandedSurfaceProviding {
    public var expandedSurfaces: [ExpandedSurfaceDescriptor] {
        [
            ExpandedSurfaceDescriptor(
                id: "clocks-detail",
                title: "World Clock",
                systemImage: "globe",
                suppresses: [.shelfWidgets, .autoCollapse]
            )
        ]
    }
}

What it suppresses

ExpandedSurfaceHostSuppression is the host chrome your surface turns off while it is presented. Ask for the least you need:

OptionTurns off
shelfWidgetsThe normal widget row
favoritesBarThe favourites strip
floatingNavLaneThe floating navigation lane
notificationBannersBanners over the shelf
autoCollapseThe shelf closing on its own
outsideClickDismissalClicking away closing it
swipeGesturesSwipe-to-close

takeover is the full set. Reach for it only when a stray click really would lose the user's work: suppressing outsideClickDismissal means the obvious way out stops working, and a surface that traps someone is worse than one that closes early.

Sizing

public func expandedSurfaceSize(
    _ id: ExpandedSurfaceID,
    fitting proposal: ExpandedSurfaceSizeProposal
) -> CGSize? {
    CGSize(width: proposal.standardSize.width, height: CGFloat(clocks.count) * 44 + 64)
}

Return nil to take proposal.standardSize. The proposal also carries maximumSize, the surface kind, and the display, so a surface can be shorter on a small screen.

Presenting and dismissing

let presentation = host.notchSurface.presentExpandedSurface(
    ExpandedSurfacePresentationRequest(surfaceID: "clocks-detail", opensShelf: true)
)

ExpandedSurfacePresentation identifies the presentation, not the surface, and the difference matters. Tearing a takeover down runs after the close spring settles, and by then the user may have summoned the same surface again — a teardown keyed on the surface id would kill the fresh one. Capture the presentation you started with and compare before acting on a delayed callback.

expandedSurfaceDidDismiss(_:presentation:reason:) is called once per presentation, for every reason including your own dismissal. ExpandedSurfaceDismissalReason tells you which: the user collapsed the shelf, another surface pre-empted yours, the display changed, the shelf was turned off, or host policy ended it.