Platforms
iPhone Duo readiness
What the app needs for iPhone Duo.
Every item of Apple's Preparing your app for iPhone Duo, checked against 82560e7. Deployment target is iOS 17.2, so each iOS 27 API is gated with #available.
API availability
| iOS | SwiftUI | UIKit |
|---|---|---|
| 26.0 | backgroundExtensionEffect() | UIBackgroundExtensionView |
| 27.0 | presentationPlacement(_:), topBarPinnedTrailing, visibilityPriority(_:), ToolbarOverflowMenu | preferredPlacement, pinnedTrailingGroup, additionalOverflowItems |
| 27.1 (beta) | ArrangementView, reservedRegions(kind:options:layoutDirectionBehavior:), toolbarVerticalBehavior(_:), toolbarVerticalEdge, axisBehavior(_:) | UIView.reservedRegions(kind:options:), UIArrangementViewController, preferredVerticalBarBehavior, verticalBarEdge |
| Simulator | iPhone Duo Simulator in Xcode 27.1 beta. StandBy and most app extensions are unavailable there. | |
Map tab on both displays
.division is active, use an overlay ArrangementView, or keep the panel within one region (G3).Checklist
| Apple item | Apple rule | App today (82560e7) | Status | Fix |
|---|---|---|---|---|
| Build and test | ||||
| Latest Xcode | Xcode 26 builds don't extend under the status bar and camera | xcode-version: '26.0' (.github/workflows/testflight.yml:51) | Gap | Build with Xcode 27, then 27.1. G1 |
| Simulator and device | Both displays: closed, open and partially folded, rotated in each pose | Not run | Gap | Run the test plan. G8 |
| Layout and resizing | ||||
| System containers | Prefer split views, tab bars, arrangement views and navigation stacks | Native TabView and NavigationStack per tab. The Map panel is a third-party custom container (views/MapView.swift:389-406). | Partly | The panel needs its own fold handling. G3 |
| Container-relative sizes | Not fixed iPhone dimensions | Fixed 172 / 128 card columns (views/StopCardView.swift:225, 230), 82 pt time columns (views/StopView.swift:1145, 1192, 1200), 136 pt direction control (:388) | Gap | Use flexible or container-relative widths. Keep ViewThatFits. G7 |
| Scene and container bounds | Not screen dimensions | No UIScreen. Panel anchors are relative to the safe area. The map uses GeometryReader (views/MapView.swift:489, 506-508). | Compliant | — |
| Size classes | Track horizontalSizeClass and verticalSizeClass | Not read. FloatingPanel re-lays out on size-class change, but we pass one layout. | Gap | Use a regular-width panel layout. G3 / G7 |
| No idiom or orientation logic | Don't lay out from userInterfaceIdiom or orientation | None. Portrait-only plist (Config/chitransit-app-Info.plist:58-61). | Verify | Verify the inner display. Allow landscape. G7 |
| Bars presented vertically | ||||
toolbar(content:) on a NavigationStack | Use container bars, not custom bars | Home and Search use NavigationStack toolbars. The Map panel has no bar. | Compliant | — |
| Tab bar on the side | The system moves tab bars to the side | Native TabView, visible on every screen including the pushed stop detail. | Compliant | — |
| Custom panel and the vertical bar | Read toolbarVerticalEdge if needed | FloatingPanel's default prepareLayout pins the surface to the safe-area left and right. Not overridden. | Verify | Verify on the outer display. G8 |
toolbarVerticalBehavior(_:) | Sheet bars are vertical on the outer display by default | Not set (.automatic). The Settings sheet has a bar. | Compliant | — |
presentationPlacement(_:) | Inner-display sheet: center, leading or trailing | No native stop sheet. The pushed stop detail uses the Map panel’s FloatingPanel. | n/a | Covered by the panel layout. G3 |
backgroundExtensionEffect() | Extend hero images under a vertical bar | Live map, bottom safe area ignored (views/MapView.swift:505) | Verify | The map must draw under the side bar. Don't mirror a live map. G8 |
| Organizing bar items | ||||
| Back / Close at the top | Custom Back or Close uses .cancellationAction | Custom Back at .topBarLeading (views/StopView.swift:77). Panel X is in content. | Gap | .cancellationAction. G6 |
| Semantic placements | ToolbarItemPlacement groups | Settings .topBarTrailing. Done .confirmationAction. | Compliant | — |
topBarPinnedTrailing | Prominent Done (iOS 27) | Done is .confirmationAction | Compliant | Optional |
axisBehavior · visibilityPriority | Axis inclusion and overflow order | At most two items per bar | n/a | — |
ToolbarOverflowMenu | Use the system overflow | No custom overflow menu | Compliant | — |
| Icon + title | Title-only or custom-view items aren't vertical | Settings and Back are Labels. Done is text-only. The .principal title is custom. | Gap | Button("Done", systemImage: "checkmark"). G6 |
| Arrangement views | ||||
ArrangementView | Overlay: primary trailing or bottom, secondary leading or top, when a division is active | Map + panel is already an overlay layout, but a custom one | Evaluate | Prototype: panel content primary, Map secondary, iOS 27.1, at the Map tab root. G3 |
| Placement | Not inside a scroll view, list or split view | — | n/a | Map tab root only |
| Reserved regions | ||||
| Custom panel | UIView.reservedRegions(kind:options:): divisions and occlusions, active or inactive | Not read. A full-width bottom panel spans the fold. | Gap | On an active .division, keep the surface in one region. G3 |
| Panel header | Avoid occlusions (inner camera while active) | Nearby title and X at the top of the surface | Verify | Pad from .occlusion regions. G8 |
| Map focus | Keep content out of divisions | The focused stop is centred above the panel (views/MapView.swift:504, 616-621) | Gap | Pad around an active .division. G4 |
| Map controls | Framework views adapt | System .mapControls only | Compliant | — |
| Camera | ||||
| Capture | AVKit and AVFoundation display changes | No capture | n/a | — |
Test plan
| Surface | Closed | Open | Partially folded | Rotated | Check |
|---|---|---|---|---|---|
| Tab bar (Home, Map, Search) | ✓ | ✓ | ✓ | ✓ | On the side when vertical. Search role kept. |
| Map panel: tip, half, full · Nearby and station | ✓ | ✓ | ✓ | ✓ | Clears the vertical bar, never spans the fold, and VoiceOver changes the anchor. |
| Map focus and pins | ✓ | ✓ | ✓ | ✓ | Selected stop never in the fold or under the panel. Map draws under the side bar. |
| Pushed stop detail (panel above the tab bar) | ✓ | ✓ | ✓ | ✓ | Panel clears the vertical bar and the fold, anchors, Back position, tab bar visible. |
| Settings sheet, alerts, confirmation dialog | ✓ | ✓ | ✓ | — | Done reachable on every axis. Moved off the fold. |
| Open or close mid-task | Search with the keyboard up. Station at full. Panel at tip. | State is preserved. | |||
| Widgets, Live Activity, StandBy | ✓ | ✓ | — | — | Device only. |