From 9aaf3ece6301b4a2354b05a9aaed405ba1ba8b89 Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Mon, 30 Mar 2026 16:04:38 -0400 Subject: [PATCH 1/2] Fix iOS top bar breaking edge-to-edge webview layout The safeAreaInset(edge: .top) modifier pushed the webview down below the top bar, leaving a white gap behind the status bar. Changed to a ZStack overlay approach so the webview stays edge-to-edge and the top bar sits on top. Updated --inset-top CSS injection to include the nav bar height so .nativephp-safe-area padding still works correctly. Co-Authored-By: Claude Opus 4.6 (1M context) --- resources/xcode/NativePHP/ContentView.swift | 30 ++++++++++++------- .../NativePHP/NativeUI/NativeUIState.swift | 3 ++ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/resources/xcode/NativePHP/ContentView.swift b/resources/xcode/NativePHP/ContentView.swift index fe310f65..f5a44433 100644 --- a/resources/xcode/NativePHP/ContentView.swift +++ b/resources/xcode/NativePHP/ContentView.swift @@ -12,14 +12,19 @@ struct ContentView: View { @StateObject private var uiState = NativeUIState.shared var body: some View { - NativeSideNavigation(onNavigate: handleNavigation) { - WebViewLayoutContainer(onTabSelected: handleNavigation) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .safeAreaInset(edge: .top, spacing: 0) { - if uiState.hasTopBar() { - NativeTopBar(onNavigate: handleNavigation) - } + ZStack { + NativeSideNavigation(onNavigate: handleNavigation) { + WebViewLayoutContainer(onTabSelected: handleNavigation) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + + // Top bar overlay — sits on top of webview so web content extends edge-to-edge behind it + if uiState.hasTopBar() { + VStack(spacing: 0) { + NativeTopBar(onNavigate: handleNavigation) + Spacer() } + } } } @@ -130,7 +135,7 @@ struct WebViewLayoutContainer: View { // Single WebView instance - fills available space WebView(shared: SharedWebView.shared, horizontalSizeClass: horizontalSizeClass) .frame(maxWidth: .infinity, maxHeight: .infinity) - .ignoresSafeArea(.all, edges: uiState.hasTopBar() ? .horizontal : .all) + .ignoresSafeArea() // Bottom navigation at bottom NativeBottomNavigation(onTabSelected: onTabSelected) @@ -140,7 +145,7 @@ struct WebViewLayoutContainer: View { // No bottom nav - WebView fills entire screen WebView(shared: SharedWebView.shared, horizontalSizeClass: horizontalSizeClass) .frame(maxWidth: .infinity, maxHeight: .infinity) - .ignoresSafeArea(.all, edges: uiState.hasTopBar() ? [.horizontal, .bottom] : .all) + .ignoresSafeArea() } } } @@ -246,6 +251,9 @@ struct WebView: UIViewRepresentable { let insets = windowScene?.windows.first?.safeAreaInsets ?? webView.window?.safeAreaInsets ?? .zero + // When top bar overlays the webview, add its height to the top inset + let topInset = insets.top + (NativeUIState.shared.hasTopBar() ? NativeUIState.topBarHeight : 0) + // Also get color scheme for CSS variable let isDarkMode = windowScene?.windows.first?.traitCollection.userInterfaceStyle == .dark let colorScheme = isDarkMode ? "dark" : "light" @@ -254,7 +262,7 @@ struct WebView: UIViewRepresentable { (function() { // Set CSS variables directly on documentElement for immediate availability if (document.documentElement) { - document.documentElement.style.setProperty('--inset-top', '\(insets.top)px'); + document.documentElement.style.setProperty('--inset-top', '\(topInset)px'); document.documentElement.style.setProperty('--inset-right', '\(insets.right)px'); document.documentElement.style.setProperty('--inset-bottom', '\(insets.bottom)px'); document.documentElement.style.setProperty('--inset-left', '\(insets.left)px'); @@ -615,7 +623,7 @@ struct WebView: UIViewRepresentable { } func updateUIView(_ uiView: WKWebView, context: Context) { - // No manual insets needed - safeAreaInset handles topbar automatically + // Top bar overlays via ZStack; --inset-top includes its height when present // Bottom nav uses its own safeAreaInset in WebViewLayoutContainer } } diff --git a/resources/xcode/NativePHP/NativeUI/NativeUIState.swift b/resources/xcode/NativePHP/NativeUI/NativeUIState.swift index ded40dab..a534a91c 100644 --- a/resources/xcode/NativePHP/NativeUI/NativeUIState.swift +++ b/resources/xcode/NativePHP/NativeUI/NativeUIState.swift @@ -10,6 +10,9 @@ class NativeUIState: ObservableObject { @Published var sideNavData: SideNavData? @Published var topBarData: TopBarData? + // Standard UINavigationBar height + padding below + static let topBarHeight: CGFloat = 52 + // Cache to prevent unnecessary updates private var lastJsonString: String? From 1a6004f4bd0460982ec1c9d10781bb8fd035c0e6 Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Mon, 30 Mar 2026 17:16:24 -0400 Subject: [PATCH 2/2] Move top bar overlay inside NativeSideNavigation The top bar was in an outer ZStack which caused it to render on top of the side navigation drawer. Moved it inside the NativeSideNavigation content closure so the drawer properly covers it when opened. Co-Authored-By: Claude Opus 4.6 (1M context) --- resources/xcode/NativePHP/ContentView.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/resources/xcode/NativePHP/ContentView.swift b/resources/xcode/NativePHP/ContentView.swift index f5a44433..9df11ca6 100644 --- a/resources/xcode/NativePHP/ContentView.swift +++ b/resources/xcode/NativePHP/ContentView.swift @@ -12,17 +12,17 @@ struct ContentView: View { @StateObject private var uiState = NativeUIState.shared var body: some View { - ZStack { - NativeSideNavigation(onNavigate: handleNavigation) { + NativeSideNavigation(onNavigate: handleNavigation) { + ZStack(alignment: .top) { WebViewLayoutContainer(onTabSelected: handleNavigation) .frame(maxWidth: .infinity, maxHeight: .infinity) - } - // Top bar overlay — sits on top of webview so web content extends edge-to-edge behind it - if uiState.hasTopBar() { - VStack(spacing: 0) { - NativeTopBar(onNavigate: handleNavigation) - Spacer() + // Top bar overlay — inside side nav so it doesn't cover the drawer + if uiState.hasTopBar() { + VStack(spacing: 0) { + NativeTopBar(onNavigate: handleNavigation) + Spacer() + } } } }