Repository navigation
Show cards in a system sheet on iOS 27+ - #33
Merged
Merged
Conversation
Member
Author
|
Suggested squash-merge commit message: |
nighthawk
commented
Oct 9, 2026
Xcode 27 no longer builds for iOS 13, and the system sheet work needs iOS 16 APIs (custom detents). TripKitUI already requires iOS 16, so consumers are unaffected. Bumps tools-version to 5.7 for `.v16`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds `presentationStyle` (`.automatic`, `.classic`, `.systemSheet`). With `.automatic`, on iOS 26+ in compact width and regular height, the card stack moves into a `UISheetPresentationController` so cards get the system's floating glass sheet, dragging and scroll-to-expand. Regular width, compact height, Catalyst, sidebar mode and controllers that are themselves presented in a sheet keep the classic presentation, and the controller switches back and forth as size classes change. Only the cards move into the sheet. The card wrapper stays behind as an invisible placeholder that mirrors the sheet's top edge on every layout pass, so map buttons, map insets, the header and map managers work as before. The three card positions map to custom detents, the extended one leaving room for a header. The sheet never dims, so the map and header stay interactive. Since the sheet is this controller's `presentedViewController`, `present` and `dismiss` are routed to whatever is on top of the sheet, and appearance callbacks for full screen presentations are forwarded. New `presentedOverlayViewController` and `cardOverlayView` help consumers that relied on `presentedViewController` or on adding views to `view`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UIKit's own present and dismiss are lenient for callers that aren't on the main actor yet, while the new overrides weren't, which broke such callers in TripGo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cards below the top card used to stay in the view hierarchy with an alpha of 0, so every card in the stack kept taking part in layout, rendering and accessibility. Once a push has finished, the covered card's view is now removed from its superview and only added back when the card above it gets popped. The views stay alive, so cards keep their state, such as scroll positions and bindings set up in `didBuild`. SwiftUI views in covered cards now get `onDisappear` and their `.task`s get cancelled, and both run again when the card is revealed. Also no longer hides a card that was popped back to before the push of the card above it had finished. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The system sheet rounds its background but doesn't clip its content, so opaque card content, like TripKitUI's grouped table cells, stuck out past the sheet's bottom corners. Clips the sheet's view with a concentric corner radius. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The classic slide-up-and-bounce suggests a new card landing on top of the old one, which fits the classic presentation, but looks odd inside a sheet that's already there. In a system sheet, cards now cross-fade in place instead, while the sheet animates to the new card's detent. Also keeps swiping from the screen's edge to pop working on the map in a system sheet, and adds it to the sheet itself, too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
iOS 27.1 no longer launches apps that don't support scenes, so the Example app crashed on launch on the iPhone Duo simulator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On devices with a vertical bar, like the iPhone Duo's outer display, the system puts a sheet's close button into that bar, below the status bar once the sheet is at full height. Cards have their close buttons in their titles, which then ended up under the status bar, or in a different spot depending on whether the title view respected the safe area. While the cards are in a system sheet next to a vertical bar, the close buttons in the top card's titles are now hidden (keeping their layout), and a stand-in close button is pinned to the sheet's bar layout region (`layoutGuide(for: .bar(onEdge:extent:))`, iOS 27.1), which forwards taps to the card's own button. It follows the bar as the sheet changes height, when the device folds, and when switching pages of a page card. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On devices with a vertical bar, such as the iPhone Duo's outer display, the top card's items now go where the system puts a navigation bar's: the close button and, for page cards, previous/next buttons at the top of the bar, and the card's new `barActions` grouped at the bottom, all icon-only. They stay in the bar at every height of the sheet: one copy sits over the map and another on the sheet, each showing where it's in front. Cards can observe `showsBarActions` to leave those actions out of their own content while they're in the bar. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cards in the sheet now stay within its safe area horizontally. On the iPhone Duo, that's what keeps content out from under the vertical bar and its items, which the sheet reserves at every height. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When collapsed, the sheet now leaves as much space below the title's content as there is above it, where the grabber sits. That's measured from what the title draws, as title views have their own margins, e.g., the home card's search bar sits close to the grab handle. It applies while the sheet is small enough to float. Taller sheets attach to the bottom edge, where UIKit adds the bottom safe area below the title, so those keep ending where the header does. Like classic cards, the content now also fades out towards the collapsed height, so that nothing below the title peeks into the sheet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`TGCardStyle.expandedBackgroundColor` fades in behind the whole card as it moves up from collapsed, along with its content, and fully shows when it's peaking or extended. With a clear `backgroundColor` on iOS 26, that keeps the map from shining through the content of a raised card, while a collapsed card still shows its material. Applies to both system sheets and classic cards; not on Mac Catalyst. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In a system sheet, the cards stay within the sheet's safe area, which leaves out the column next to a vertical bar, e.g., on the iPhone Duo. The expanded background now goes on the sheet itself, so that it also covers that column, rather than leaving a strip of glass next to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The vertical bar APIs for the iPhone Duo come with the iOS 27.1 SDK. With older Xcodes, the cards keep their own close buttons and actions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The runner no longer has the named simulator, so the job failed before building anything. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
visionOS has no sheet detents, and neither visionOS nor Mac Catalyst have the vertical bar APIs. Cards never use a system sheet there, so leave those parts out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`TGCard.barActions` is now `verticalBarActions`, and `showsBarActions` is `showsVerticalBarActions`, which says where they show. Internal names follow suit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The classic card keeps its own look, including its rounded corners, so `TGCardStyle.expandedBackgroundColor` now only applies to the sheet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nighthawk
force-pushed
the
feature/26329-system-sheets
branch
from
October 9, 2026 08:46
eaa5ef4 to
7ef0e5a
Compare
The sheet's safe area covers the vertical bar's column when the bar is on the trailing edge, but not when it's on the leading edge, e.g., on an unfolded iPhone Duo with the app on the left screen. The cards in the sheet now get inset by the bar's column on whichever side it is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On iOS 27, the sheet is set up once to adapt to the size classes by itself, like Maps': at the bottom and full width in compact width, and 400pt wide on the leading edge otherwise, e.g., on iPad and on an iPhone in landscape. As the sheet no longer needs to make way for the classic card when the size classes change, the controller no longer switches between the two on rotation or resizing. That needs the sheet placement API of iOS 27, so system sheets are now iOS 27+ only, and `PresentationStyle.systemSheet`, which forced them on earlier versions, is gone. iOS 26 and earlier keep the classic card. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UIKit extends a sheet underneath the vertical bar of an iPhone Duo, which can look bulgy next to the bar's items. With the new `sheetAvoidsVerticalBar`, the view that UIKit lays out the sheet in is narrowed to the space beside the bar, so the sheet sizes itself to that, and the bar's items always sit over the map. The Example app opts in with the `-avoidVerticalBar` launch argument. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`sheetAvoidsVerticalBar` now defaults to `true`. Set it to `false` to let the sheet extend underneath the bar, as UIKit does by itself. The Example app's launch argument for that is now `-sheetUnderVerticalBar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drops `sheetAvoidsVerticalBar`, going back to UIKit's own sheet layout next to the vertical bar of an iPhone Duo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the sheet is collapsed, it only shows the card's title, which now uses the sheet's full width rather than staying clear of the vertical bar, and the card's actions aren't shown in the bar. They fade in with the card's content as the sheet rises towards peaking. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While the sheet is collapsed next to a vertical bar on the trailing edge, the bottom map buttons now ignore the bar's safe area inset, so they line up with the collapsed sheet below them, rather than stopping short of the bar. Above that, they stay clear of the bar as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The full-width content and the bottom floating views ignoring the bar only kicked in when the top card had vertical bar items, so cards without any (e.g. TripGo's home card) kept the bar inset when collapsed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
iOS 27 floats taller collapsed sheets a few points above the bottom of the screen, so measuring from the screen's bottom left them slightly "expanded": vertical bar actions stayed faintly visible and the collapsed layout didn't kick in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cards in a system sheet now span its full width, with the sheet host's safe area covering the vertical bar's column, so that only backgrounds and the scroll indicator go under the bar, like in Apple's apps. Titles and plain card content stay within the safe area, as do cell contents. Pages of a page card are hosted in child view controllers, as UIKit only passes a scroll view's safe area and layout margins on to its content at the edges of that content. This also gives inset grouped tables on pages their margins back, e.g., in the trip overview, in both presentations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sheet's safe area and the cards' constraints are updated while the sheet lays out, after the cards in it are laid out already. When that was the last layout pass, e.g., when the sheet settled after a drag, the cards kept the previous inset, such as reserving the bar's column while collapsed, until the next drag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In compact width, the header above a system sheet now spans the full width of the screen with square corners, like a toolbar, rather than a rounded panel the width of the card. Its content gets some more room around it, and stays within the safe area, e.g., clear of a vertical bar. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the system sheet is on the side of the screen, e.g., on iPad or an unfolded iPhone, the header now floats right above it as a matching rounded panel: aligned with the sheet's edges, as far from the top as the sheet is from the side, and with the same room around its content. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The header above a system sheet now shows the top card's `expandedBackgroundColor` behind its content, fading in and out with the sheet's height exactly like the sheet does, so the header always matches the card below it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A header that floats above a system sheet on the side now has top corners concentric with the screen's, rather than a fixed radius that clashes with the display's rounded corners. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hosted pages of a page card get the system's safe area, but not what the sheet adds for a vertical bar on the leading edge, e.g., when the app is on the left screen of an unfolded iPhone Duo. Only the first page got it, through the pager's edge. Each page's host now makes up the difference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fersagui
approved these changes
Oct 13, 2026
fersagui
left a comment
Member
There was a problem hiding this comment.
LGTM. My AI found these improvements:
[P1] The compiler guard enables iOS 27.1 vertical-bar APIs absent from the 27.0 SDK. Confirmed by building the Example app.
[P2] Side sheets receive bottom map padding. On iPad and landscape iPhone, map managers receive zero horizontal padding despite the leading sheet, so fitted map content can remain obscured.
nighthawk
added a commit
to skedgo/tripkit-ios
that referenced
this pull request
Oct 19, 2026
TripKitUI uses API from skedgo/TGCardViewController#33. Switch back to a version requirement, from 2.6.0, once that's tagged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of RM26329.
Why
On iOS 27, cards should be native Liquid Glass sheets, like Maps', with the system's dragging, snapping and scroll-to-expand. That makes them consistent with other apps, and leaves less hand-rolled pan machinery to maintain. Apps shouldn't need to change, and cards keep what they rely on: per-card map content, headers, paging, and a map that stays interactive at every height.
What
presentationStyle, defaulting to.automatic: on iOS 27+, the cards move into aUISheetPresentationController. It's set up once and adapts to the size classes by itself: at the bottom in compact width, and 400pt wide on the leading edge otherwise, e.g., on iPad and on an iPhone in landscape. iOS 26 and earlier, Mac Catalyst, visionOS and sidebar mode keep the classic card;.classicopts out. The sheet placement API needs Xcode 27 to build.presentedViewController. Use the newpresentedOverlayViewControllerandcardOverlayViewinstead;presentanddismisson the controller route around the sheet.TGCard.verticalBarActionsgo into the vertical bar, like Mail's.TGCardStyle.expandedBackgroundColorfades in behind raised cards in the sheet. The classic card keeps its look.It also fixes the Example CI job. After merging, tag 2.6.0, which skedgo/tripkit-ios needs.
Testing
In the Example app:
-classicCardslaunch argument: the classic card, unchanged.🤖 Generated with Claude Code