Provides a card-based view controller for mapping applications where the card's content is in sync with a map, similar to how Apple Maps works. For an application of this see the TripKitUI SDK and TripGo by SkedGo.
Via Swift Package Manager (recommended)
- Add it to your
Package.swiftfile (or add it as a dependency through Xcode):
.package(url: "https://github.com/skedgo/TGCardViewController.git", from: "1.7.5")Via CocoaPods
-
Check out the repo and make it accessible to your project, e.g., as a git submodule
-
Add it to your
Podfile, e.g.:pod 'TGCardViewController -
Run
pod update
-
Create a
TGCardViewControllersubclass and use it in your storyboard -
Override
init(coder:)so that the instance from the storyboard isn't used, but insteadTGCardViewController.xib:import TGCardViewController class CardViewController: TGCardViewController { required init(coder aDecoder: NSCoder) { // When loading from the storyboard we don't want to use the controller // as defined in the storyboard but instead use the TGCardViewController.xib super.init(nibName: "TGCardViewController", bundle: TGCardViewController.bundle) } ... }
-
Create a
TGCardsubclass, that represents the card at the top level, and add then push that in your view controller'sviewDidLoad:override func viewDidLoad() { rootCard = MyRootCard() super.viewDidLoad() }
On iOS 27 and later, the cards are shown in a system sheet (UISheetPresentationController). The sheet adapts to the size classes by itself, like Maps':
| Presentation | |
|---|---|
| Compact width, e.g., iPhone in portrait | Sheet at the bottom, full width |
| Otherwise, e.g., iPad or iPhone in landscape | Sheet on the leading edge, 400pt wide |
Mac Catalyst, visionOS, mode = .sidebar |
Classic |
| The card controller is itself presented, but not full screen | Classic |
| iOS 26 and earlier, or built with Xcode 26 and earlier | Classic |
This can't be turned off: presentationStyle and its .classic option are deprecated on iOS 27, and will be removed once that's the minimum. Check usesSystemSheet for what's currently in use.
Cards, map managers, headers, map buttons and paging work the same in both. Differences with a system sheet:
- The map isn't dimmed and stays interactive when the card is extended.
- The sheet provides the grab handle, the material, the corners and the dragging.
- When a header is showing, the extended card stops below it.
- Sheets presented by cards (e.g., with detents) stack on top of the card's sheet.
Because the sheet is the card controller's presentedViewController:
present(_:animated:completion:)anddismiss(animated:completion:)on the card controller present on top of the sheet, and dismiss what's on top of it, but never the sheet itself.- Use
presentedOverlayViewControllerto check what's presented on top of the cards, instead ofpresentedViewController. - Use
cardOverlayViewinstead ofviewfor views that you constrain to views inside a card, e.g., tooltips.
On devices with a vertical bar, such as the iPhone Duo's outer display (iOS 27.1+), the system puts a sheet's bar items into that bar. The card controller does the same for cards, like Mail does: the top card's close button moves to the top of the bar, followed by buttons for the previous and next page of a TGPageCard, and the card's verticalBarActions go to the bottom of the bar as icon-only buttons. They stay in the bar at every height of the sheet, and the cards stay clear of it. Cards can observe showsVerticalBarActions to leave those actions out of their own content while they're in the bar.
Behaviour:
- Card positions:
collapsed: Only shows header of cardpeaking: Shows half of card's content and the map contentextended: Shows card fully, map shows a little bit on top but is greyed out
- Pushing a card from another card
- Adds (x) button unless it’s the root card
- Card has a preferred position which is used when pushing
- Animation: Slide up from the bottom; fading black view on card below with alpha from 0% to 25%
- Pass on appearance callbacks appropriately to involved cards
- Popping a card
- Tap (x) to pop card
- When popping top card, restore card position of card below when something got pushed on it
- Animation: Slide back down; fading out black view on card below with alpha from 25% to 0%
- Pass on appearance callbacks appropriately to involved cards
- Cards are draggable
- Snap to collapsed (only title), peaking (near half-way showing both map and card content), extended (still shows a bit of the map on top, but darkened)
- Cards can be dragged up and down anywhere on the card
- Tap title when collapsed: go to peaking
- Tap title when peaking: go to extended
- Tap title when extended: do nothing
- Tap map when extended: go to peaking
- Cards are scrollable
- Cards typically have scrolling content: when scrolling down the card's header stays at the top and a bit of the map still keeps peaking through at the top.
- When scrolling down show a thin separator line between the card's scrolling content and the card's header
- When scrolling to the top and keeping to scroll, start dragging card
Styles:
- Animation curve for push and pop
- Blurry view under status bar (like Maps app)
- When rotating device and card is collapsed, make sure card ends up in correct position
Card types:
- Plain card
- On top: Title, (x), optional subtitle and optional accessory view
- Add accessory view
- Content can be scrollable and size adjusts to content. If it fits, it shouldn’t be scrollable
- Add optional floaty button
- Table card
- Same as plain card, but with a table view as its content
- Allow specifying plain (e.g., for departures) or grouped style (e.g., for profile)
- Collection card
- Same as plain card, but with a collection view as its content
- Allow specifying collection view layout
- Hosting card
- Allow using a SwiftUI view as the card's content
- Paging card
- Handles list of child cards on the same hierarchical level which can be paged programatically and through gestures
- Has header view: Used for titles (child cards shouldn't show them then) and navigation; Header view is separate from sticky bar, i.e., you can have both.
- Re-uses the top card's map manager
- Pass on appearance callbacks appropriately to child cards
Card styles:
- Rounded corners to cards
- Grab handle for cards
- Nice close buttons (and next button for paging cards)
- Title and subtitle styling
- Add mini drop shadow to card views
- Bottom view
Map content:
- Cards can optionally have map content
- When showing the content, the insets should be respected to account for the card overlapping the map
- If there’s no map content: Show card always extended and don't allow dragging it down (or just snap back up when using tries)
Map buttons:
- Optional list of buttons that float on the right above the card or in the top right corner (when collapsed or peaking)
- When dragging up the card to
extended, the buttons fade away
- Move card to the side with min (iPhone Plus) and max (iPad) width
- Make sure transitions work when changing size and traits
- State restoration, using
NSUserActivity - VoiceOver Accessibility
- Keyboard shortcuts
- Card controller
- ⌃+↑: Expand card
- ⌃+↓: Expand/collapse card
- ⌘+w: Pop card or dismiss modal
- Table view cards
- ↑: Highlight previous item
- ↓: Highlight next item
- ⌘+↑: Highlight item at start of list
- ⌘+↓: Highlight item at end of list
- Space or enter: select item
- Esc: deselect
- Paging cards
- ⌃+←: Previous card
- ⌃+→: Next card



