Zum Hauptinhalt springen

iPhone Duo in Capacitor-Apps unterstützen

Bereite Capacitor WebView-Apps auf das iPhone Duo vor: viewport-fit=cover, env(safe-area-inset-*), System Bars und Status Bar Plugins, Fold- und Resize-Tests sowie @capgo/capacitor-native-navigation in der Seitenleiste.

iPhone Duo im Querformat mit nativen Leisten in der Seitenleiste und im Hochformat mit Tab-Leiste unten

Apples faltbares iPhone Duo verändert, wie System-UI und Safe Areas funktionieren. Expo hat einen praktischen Guide veröffentlicht für React Native Apps auf SDK 58. Dieser Beitrag ist das Capacitor-Äquivalent: eine WebView, CSS env Insets, Capacitor-Kernplugins und optionale native Leisten von @capgo/capacitor-native-navigation.

Was sich beim iPhone Duo ändert

Bei vielen Haltungen verschiebt iOS die Statusleiste, Navigationsleiste und Tab-Leiste in eine vertikale Seitenleiste am Leading- oder Trailing-Rand. Horizontale Leisten erscheinen weiterhin im Hochformat auf dem geöffneten inneren Display und in klassischen Smartphone-Layouts.

Das obige Bild zeigt das geöffnete Display. Im Querformat stapeln sich Toolbar und Tab-Leiste in der Seitenleiste. Im Hochformat kehrt die Toolbar nach oben und die Tab-Leiste nach unten zurück.

Für Web-Content innerhalb einer Capacitor-WebView sind die wichtigsten Verschiebungen:

  • env(safe-area-inset-top) kann 0 wenn die Statusleiste im Rail liegt, nicht unter dem Notch.
  • Leading- und Trailing-Insets wachsen (Apple dokumentiert ca. 84pt für das Rail in Landscape- und Folded-Posen). Links und rechts sind keine Spiegelbilder voneinander.
  • Fenstergröße ändert sich wenn der Nutzer faltet, entfaltet, rotiert oder Split View auf dem inneren Display nutzt. GeCachte Layout-Werte werden schnell veraltet.
  • Native Tab Bars folgen dem Rail when you use UITabBar (oder Capgo Native Navigation mit der System-Tab-Leiste). Eigene, fixe HTML-Tab-Leisten tun das nicht.

Behandle das gefaltete Außendisplay als kompakte Breite und das geöffnete Innendisplay als reguläre Breite. Erweitere Layouts, statt auf ein anderes App-Shell zu wechseln.

Eine Mail-App auf dem iPhone Duo in drei Zuständen. Gefaltet stapeln sich Toolbar und Tab-Leiste in einer vertikalen Leiste rechts. Im Split View behält jedes Fenster eine Leiste an seiner äußeren Kante. Offen im Hochformat verläuft die Toolbar oben und die Tab-Leiste sitzt als Pille unten.

Wo die Systemleisten liegen: eine Leiste rechts, wenn das Gerät gefaltet ist, an der äußeren Kante jedes Fensters im Split View und wieder horizontale Leisten auf dem geöffneten Display im Hochformat.

Anforderungen

  • Capacitor 8 auf iOS 27 (Xcode 27.1 mit dem iPhone Duo Simulator-Runtime).
  • Device Hub (Xcode > Open Developer Tool > Device Hub), um den Duo-Simulator zu falten, zu knicken und zu drehen.
  • Ein viewport der Safe Areas nutzt (siehe unten).

viewport-fit=cover und CSS-Safe-Areas

Safe-Area-Env-Variablen gelten nur, wenn der viewport das gesamte Display abdeckt:

<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />

Pad interaktiv Content pro Kante. Lass Hintergründe bis an die Ränder reichen, wenn du Immersion willst.

Wähle ein inset-Owner für die gesamte App. Die beiden Snippets unten sind Alternativenund kein Paar, das man kombinieren sollte.

Option A: Shell pro Seite

.page {
  min-height: 100dvh;
  padding-top: var(--safe-area-inset-top, env(safe-area-inset-top, 0px));
  padding-right: var(--safe-area-inset-right, env(safe-area-inset-right, 0px));
  padding-bottom: var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px));
  padding-left: var(--safe-area-inset-left, env(safe-area-inset-left, 0px));
}

Überspringe Option B, wenn du Option A verwendest.

Bei Scroll-Views den Inhaltmit Inset versehen, nicht nur den äußeren Scroll-Container, damit Hintergründe in voller Breite scrollen können, während Text und Buttons den Rail-Bereich frei lassen.

Nutzt vier unabhängige Werte. Gehe nicht davon aus, padding-left entspricht padding-rightdass die Rail in RTL auf der Kameraseite bleibt; beide left und right env-Werte lesen.

Zwei Stellen werden leicht übersehen:

  • Ein horizontaler Scroller in einer Seite, die bereits Padding hat, braucht trotzdem eigenes Padding am Ende. Sonst rutscht die letzte Karte unter die Leiste.
  • Ein Floating Button, der auf right: 16px ignoriert die Leiste. Füge den Live-Left- oder Right-Inset hinzu und berechne ihn neu, wenn sich die Fenstergröße ändert.

Für Tailwind-Apps @capgo/tailwind-capacitor stellt pt-safe, px-safeund verwandte Utilities bereit, statt env() überall zu wiederholen.

Capacitor System Bars (Safe Area auf Android und Config auf iOS)

Capacitor 8 bringt Systemleisten in einem @capacitor/core. Es ersetzt das alte Android-only- adjustMarginsForEdgeToEdge Flag. Konfiguriere es in capacitor.config.ts:

import type { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  plugins: {
    SystemBars: {
      insetsHandling: 'css',
    },
  },
};

export default config;
  • insetsHandling: 'css' (Standard auf Android): injiziert --safe-area-inset-* CSS-Variablen, wenn die WebView-Umgebungswerte falsch sind (ältere Android-WebView-Builds).
  • Option B (websiteweite Shell) verwendet das Fallback-Muster vom System-Bars-Dokumentation. Überspringe Option A, wenn du Option B verwendest.
html {
  padding-left: var(--safe-area-inset-left, env(safe-area-inset-left, 0px));
  padding-right: var(--safe-area-inset-right, env(safe-area-inset-right, 0px));
  padding-top: var(--safe-area-inset-top, env(safe-area-inset-top, 0px));
  padding-bottom: var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px));
}

Zur Laufzeit kannst du Bars stylen oder ausblenden mit SystemBars.setStyle, SystemBars.show, und SystemBars.hide von @capacitor/core. Es gibt keine separate @capacitor/safe-area package: Safe Areas für Capacitor-Apps sind CSS env, System Bars-Variablen auf Android, und eigene Listener auf resize / visualViewport.

@capacitor/status-bar

Der Status Bar-Plugin steuert auf iOS weiterhin Overlay und Stil:

import { StatusBar, Style } from '@capacitor/status-bar';

await StatusBar.setOverlaysWebView({ overlay: true });
await StatusBar.setStyle({ style: Style.Dark });

setOverlaysWebView({ overlay: true }) lässt HTML unter dem Statusbar-Bereich zeichnen. Kombinieren Sie es mit env-Padding, sonst wird Inhalt abgeschnitten. Auf dem iPhone Duo gilt weiterhin der Overlay-Modus; die Geometrie davon, ob der Inset oben oder seitlich liegt, stammt aus der System-Safe-Area, nicht nur aus Statusbar-APIs.

Nicht stapeln: automatische WebView-Margen, env-Padding für die gesamte Seite, und Inset-Variablen der nativen Leiste ohne explizite Strategie. Dieses Muster verursacht doppelte Abstände auf jedem iPhone und wird schlimmer, wenn die Rail Insets auf die Seiten verschiebt.

iOS WebView contentInset

Unter capacitor.config.ts, ios.contentInset entspricht der Scroll-View des WebViews contentInsetAdjustmentBehavior. Viele Apps nutzen 'never' wenn sie Insets in CSS behandeln. Wenn du auf die automatische UIKit-Anpassung und CSS env Padding, wähle eine einzige Quelle der Wahrheit.

Siehe Capacitor iOS-Konfiguration.

WebView-Größenanpassung beim Auf- und Zuklappen

Capacitor behält eine einzige bildschirmfüllende WebView bei. Wenn das Duo seine Pose ändert:

  1. UIKit aktualisiert die Safe Area Insets des View Controllers.
  2. Der WebView-Layout-Pass wird mit neuer Breite und Höhe ausgeführt.
  3. CSS- env(safe-area-inset-*) und 100dvh / visualViewport Werte werden beim nächsten Style-Flush aktualisiert.

Geometrie-Änderungen in JavaScript abfangen wenn du Inset-abhängige Positionen cachst (Floating Buttons, Toasts, Canvas-Overlays):

function onGeometryChange() {
  const vv = window.visualViewport;
  const width = vv?.width ?? window.innerWidth;
  const height = vv?.height ?? window.innerHeight;
  const offsetLeft = vv?.offsetLeft ?? 0;
  const offsetTop = vv?.offsetTop ?? 0;
  document.documentElement.style.setProperty('--app-vv-width', `${width}px`);
  document.documentElement.style.setProperty('--app-vv-height', `${height}px`);
  document.documentElement.style.setProperty('--app-vv-offset-left', `${offsetLeft}px`);
  document.documentElement.style.setProperty('--app-vv-offset-top', `${offsetTop}px`);
}

window.addEventListener('resize', onGeometryChange);
window.visualViewport?.addEventListener('resize', onGeometryChange);
window.visualViewport?.addEventListener('scroll', onGeometryChange);
onGeometryChange();

Der Scroll-Listener deckt Visual-Viewport-Verschiebungen ab, die offsetLeft / offsetTop ohne Änderung von Breite oder Höhe erfolgen (z. B. tastaturbedingte Viewport-Shifts unter iOS).

Unter Android füge density in einem android:configChanges in deiner Main Activity, damit Foldables und Änderungen der Displaygröße die WebView nicht zerstören. Capacitor 8 Templates dokumentieren dies in der Capacitor 8 Upgrade-Guide.

Ausrichtung und Window Size Classes

Nutzt live Breite für das Branch-Layout, nicht screen.width einmalig beim Start erfasst:

const compactQuery = window.matchMedia('(max-width: 600px)');

function readCompactWidth() {
  return compactQuery.matches;
}

compactQuery.addEventListener('change', () => {
  document.documentElement.dataset.widthClass = readCompactWidth() ? 'compact' : 'regular';
});
document.documentElement.dataset.widthClass = readCompactWidth() ? 'compact' : 'regular';

Nutze in CSS bevorzugt Flex und Grid mit minmax() statt fester 100vw Breiten. 100vw ignoriert oft Scrollbalken und asymmetrische Ränder und verursacht horizontalen Überlauf.

Für Split View auf dem inneren Display kann jedes Fenster schmal sein (Apple nennt eine Breite von ca. 469pt). Teste deine App, wenn sie links und rechts andockt: Die Leiste ist an die Fenster kante angeheftet, daher können horizontale Insets zwischen Fenstern die Seite wechseln.

Eigenes HTML-Chrome vs. native Leisten

Expos Guide empfiehlt native Stack und NativeTabs so iOS steuert die Rail-Platzierung. Dasselbe gilt für Capacitor:

Ansatz iPhone-Duo-Verhalten
Fester HTML-Header + untere Tabs Bleibt in den klassischen oberen/unteren Safe-Area-Rechtecken. Verschiebt sich nicht in die Rail.
@capgo/capacitor-native-navigation mit System UITabBar / UINavigationBar Bars folgen der Systemplatzierung, einschließlich der vertikalen Rail.

Wenn du HTML-Chrome beibehalten musst, prüfe auf jedem Bildschirm links und rechts env-Padding, nicht nur oben und unten.

Derselbe Bildschirm mit festem HTML-Header und unteren Tabs. Im gefalteten Zustand bleiben Header und Tabs in den klassischen Rechtecken und meiden nur die Rail. Im aufgeschlagenen Hochformat bleiben sie oben und unten.

Festes HTML-Chrome folgt nicht der Leiste. Es bleibt in den oberen und unteren Safe-Areas. Native Bars, gezeichnet von UIKit oder von @capgo/capacitor-native-navigation, bewegen sich mit dem System.

@capgo/capacitor-native-navigation auf iPhone Duo

Native Navigation zeichnet echte UINavigationBar und UITabBar (oder Material-Bars auf Android) über einer Capacitor-WebView. Auf iOS 27 mit iPhone Duo bewegen sich System-Tabbars in die Seitenleiste in gefalteten und vielen Landscape-Positionen. Das Plugin Kurve Die Tab-Bar-Form bleibt am unteren Rand. Die zentrale Aktion behält eine kreisförmige Aussparung, während das Fenster breiter wird.

Zwei offene Landscape-Layouts von @capgo/capacitor-native-navigation.: Links sitzt die System-Tab-Bar in der Seitenleiste. Rechts bleibt die Curved-Tab-Bar unten, und die zentrale Aktion sitzt in einer kreisförmigen Aussparung.

Links sitzt die System-Tab-Bar in der Seitenleiste. Rechts bleibt die Curved-Tab-Bar unten; die zentrale Aktion sitzt in ihrer Aussparung.

Einmal konfigurieren

import { NativeNavigation } from '@capgo/capacitor-native-navigation';

await NativeNavigation.configure({
  contentInsetMode: 'css',
});

contentInsetMode: 'css' schreibt Größen für Bar- und Safe-Area-Überlappung in die Dokumentwurzel:

  • --cap-native-navigation-top
  • --cap-native-navigation-right
  • --cap-native-navigation-bottom
  • --cap-native-navigation-left
  • --cap-native-navbar-height
  • --cap-native-tabbar-height
  • --cap-native-system-tabbar-height (nominale Tab-Bar-Höhe, wenn tabbarHeight ist 0 für gehostete System-Bars)

Wenn Native Navigation die Bars verwaltet, nutze für das Padding die Plugin-Variablen unten. Sie enthalten bereits native Bar-Überlappung und UIKit-Safe-Area-Edges, wenn eine Bar aktiv ist. Nutze max() mit Env-Fallbacks, damit die Edges korrekt bleiben, wenn eine Variable 0 (z. B. gehostete System-Tab-Bars) ohne hinzuzufügen Plugin- und Env-Padding zusammen.

main {
  padding-top: max(var(--cap-native-navigation-top, 0px), env(safe-area-inset-top, 0px));
  padding-right: max(var(--cap-native-navigation-right, 0px), env(safe-area-inset-right, 0px));
  padding-bottom: max(var(--cap-native-navigation-bottom, 0px), env(safe-area-inset-bottom, 0px));
  padding-left: max(var(--cap-native-navigation-left, 0px), env(safe-area-inset-left, 0px));
}

Wende nicht zusätzlich das Padding aus Option A oder Option B auf demselben main Element an,

Auf iOS 26+ mit einer systemeigenen Tab-Leiste, tabbarHeight können die Plugin-Insets 0 damit der Webinhalt nicht doppelt angehoben wird. Verwende --cap-native-system-tabbar-height wenn du Abstände für schwebende Aktionen über der Leiste benötigst.

safeAreaChanged

Abonnieren, wenn sich Faltung, Rail-Seite oder Balkensichtbarkeit ändern:

await NativeNavigation.addListener('safeAreaChanged', ({ insets }) => {
  console.log(insets.top, insets.right, insets.bottom, insets.left);
  console.log(insets.navbarHeight, insets.tabbarHeight, insets.systemTabbarHeight);
});

Das Plugin sendet außerdem capNativeNavigation:safeAreaChanged auf window mit demselben insets Payload.

Native code wendet eine kleine obere Lücke, wenn safeInsets.top null ist (Statusleiste im Rail), damit die Navbar-Buttons die abgerundete Display-Ecke nicht berühren. Deine Web-Padding-Werte sollten weiterhin env left/right für das Rail verwenden.

setNavbar, setTabbar, und configure geben alle zurück { insets } wenn du nach dem Aktualisieren von Chrome einen einmaligen Read benötigst.

Vollständige API-Referenz: Native Navigation-Doku.

Testing-Checkliste

Im Device Hub mit dem iPhone-Duo-Simulator:

  1. Zuklappbar: Die Steuerelemente liegen nicht unter dem Rail; Taps werden registriert; die horizontalen env-Insets sind dort nicht null, wo erwartet.
  2. Hochformat öffnen: Die oberen/unteren Balken erscheinen dort, wo iOS sie platziert; das Layout springt nicht zu einer anderen Informationsarchitektur.
  3. Querformat / Rail öffnen: Der Inhalt vermeidet die Seitenleiste; schwebende Aktionen nutzen insets.right oder insets.left, nicht einen hartkodierten 16px.
  4. Teilweise Faltung: Keine kritische UI im Scharnierbereich; Modals und Sheets bleiben bedienbar.
  5. Split View: Die App in linken und rechten Fenstern ausführen; die Rail-Kante folgt jedem Fenster.
  6. Falten, während ein Bildschirm geöffnet ist: Routenzustand, Scrollposition und Formulareingaben überstehen den Übergang.
  7. Klassischer iPhone-Simulator: keine Regressionen.

Agent-Prompt (kopieren und einfügen)

Verwende diesen Block mit Claude Code, Cursor, Codex oder einem anderen Coding-Agenten, der an einem Capacitor-Repo arbeitet:

Task: Make this Capacitor app safe for iPhone Duo (iOS 27 foldable).

Context:
- Read https://capgo.app/blog/capacitor-iphone-duo-safe-area-guide/ and Expo's https://expo.dev/blog/supporting-iphone-duo for platform behavior.
- One WebView. Use viewport-fit=cover and per-edge env(safe-area-inset-*) padding for interactive content; backgrounds may ignore safe areas.
- Capacitor 8: configure plugins.SystemBars (insetsHandling css). Set viewport-fit=cover in index.html. Use var(--safe-area-inset-*, env(...)) fallbacks on Android.
- @capacitor/status-bar: only if the app already uses it; setOverlaysWebView must match CSS padding strategy.
- Do not double-apply Capacitor margin adjustment, full-page env padding, and native navigation CSS variables.
- On resize and visualViewport resize, recompute any cached inset or FAB positions.
- If the app uses @capgo/capacitor-native-navigation: contentInsetMode css, listen for safeAreaChanged, pad with max(var(--cap-native-navigation-*, 0px), env(safe-area-inset-*, 0px)) per edge (not sum); verify system tab bar rail poses in the iOS 27 Duo simulator.
- If the app uses custom HTML headers or tab bars, add left/right safe padding on every screen and document that bars will not move into the rail.

Deliverables:
1. List files changed and the inset strategy (single source of truth).
2. Replace fixed top-only safe padding with four-edge padding where needed.
3. Add or update capacitor.config and index.html viewport meta if missing.
4. Note manual test poses: folded, open portrait, open landscape rail, Split View left/right.

Constraints:
- Only use APIs that exist in @capacitor/core, @capacitor/status-bar, and @capgo/capacitor-native-navigation definitions.
- No em dashes or en dashes in user-facing strings you add.

Installieren Capgo Skills damit Agenten wissen, wie sie Native Navigation einbinden: npx skills add Cap-go/capgo-skills --skill capacitor-plugins.

Weiterlesen

Live-Updates für Capacitor-Apps

Wenn ein Bug im Web-Layer live ist, liefere den Fix über Capgo aus, statt Tage auf die App-Store-Freigabe zu warten. Nutzer bekommen das Update im Hintergrund, während native Änderungen im normalen Review-Prozess bleiben.

Neues aus unserem Blog

Capgo gibt dir die besten Insights, um eine wirklich professionelle Mobile App zu erstellen.