fix(app): remove release-notes WebView bottom safe-area gap

Extend the sheet WebView under the home indicator, keep scroll insets cleared after navigation, and neutralize remote page safe-area bottom padding in-app.
This commit is contained in:
Rocky
2026-08-08 10:53:46 +08:00
parent 9da32b81e9
commit bfe2cd2001
2 changed files with 54 additions and 9 deletions
@@ -1,7 +1,7 @@
// RemoteWebView.swift
// OSGKeyboard · Main App
//
// In-app WKWebView for remote HTTPS pages (e.g. GitHub Issues).
// In-app WKWebView for remote HTTPS pages (e.g. GitHub Issues, release notes).
import SwiftUI
import WebKit
@@ -9,9 +9,12 @@ import WebKit
struct RemoteWebView: UIViewRepresentable {
let url: URL
@Binding var isLoading: Bool
/// When true, strip page `env(safe-area-inset-*)` bottom padding after load
/// (sheets already lay out above the home indicator).
var neutralizeSafeAreaPadding: Bool = false
func makeCoordinator() -> Coordinator {
Coordinator(isLoading: $isLoading)
Coordinator(isLoading: $isLoading, neutralizeSafeAreaPadding: neutralizeSafeAreaPadding)
}
func makeUIView(context: Context) -> WKWebView {
@@ -19,11 +22,7 @@ struct RemoteWebView: UIViewRepresentable {
webView.isOpaque = false
webView.backgroundColor = .clear
webView.scrollView.backgroundColor = .clear
// Sheet / NavigationStack already lays out inside the safe area.
// Automatic adjustment would add a second bottom inset and leave a dead band.
webView.scrollView.contentInsetAdjustmentBehavior = .never
webView.scrollView.contentInset = .zero
webView.scrollView.scrollIndicatorInsets = .zero
Self.applyScrollInsets(webView)
webView.navigationDelegate = context.coordinator
context.coordinator.loadedURL = url
webView.load(URLRequest(url: url))
@@ -31,33 +30,66 @@ struct RemoteWebView: UIViewRepresentable {
}
func updateUIView(_ uiView: WKWebView, context: Context) {
context.coordinator.neutralizeSafeAreaPadding = neutralizeSafeAreaPadding
Self.applyScrollInsets(uiView)
guard context.coordinator.loadedURL != url else { return }
context.coordinator.loadedURL = url
uiView.load(URLRequest(url: url))
}
/// Kill automatic safe-area insets that leave a dead band under the page.
static func applyScrollInsets(_ webView: WKWebView) {
let scroll = webView.scrollView
scroll.contentInsetAdjustmentBehavior = .never
scroll.contentInset = .zero
scroll.scrollIndicatorInsets = .zero
scroll.automaticallyAdjustsScrollIndicatorInsets = false
}
final class Coordinator: NSObject, WKNavigationDelegate {
@Binding var isLoading: Bool
var loadedURL: URL?
var neutralizeSafeAreaPadding: Bool
init(isLoading: Binding<Bool>) {
init(isLoading: Binding<Bool>, neutralizeSafeAreaPadding: Bool) {
_isLoading = isLoading
self.neutralizeSafeAreaPadding = neutralizeSafeAreaPadding
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
isLoading = true
RemoteWebView.applyScrollInsets(webView)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
isLoading = false
RemoteWebView.applyScrollInsets(webView)
guard neutralizeSafeAreaPadding else { return }
// Remote HTML may still use env(safe-area-inset-bottom); neutralize in-app.
webView.evaluateJavaScript(
"""
(function () {
var s = document.getElementById('osg-webview-safe-area-fix');
if (!s) {
s = document.createElement('style');
s.id = 'osg-webview-safe-area-fix';
document.head.appendChild(s);
}
s.textContent = 'html{height:100%;} body{min-height:100%;padding-bottom:24px!important;}';
})();
""",
completionHandler: nil
)
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
isLoading = false
RemoteWebView.applyScrollInsets(webView)
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
isLoading = false
RemoteWebView.applyScrollInsets(webView)
}
}
}
+14 -1
View File
@@ -36,7 +36,16 @@ struct ReleaseNotesSheet: View {
colorScheme: resolvedColorScheme
) {
ZStack {
RemoteWebView(url: url, isLoading: $isLoading)
// Fill the sheet body edge-to-edge (including home-indicator
// band). WKWebView otherwise sits above a blank safe-area strip.
RemoteWebView(
url: url,
isLoading: $isLoading,
neutralizeSafeAreaPadding: true
)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea(edges: .bottom)
if isLoading {
ProgressView()
.tint(palette.accent)
@@ -52,6 +61,7 @@ struct ReleaseNotesSheet: View {
)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(palette.background.ignoresSafeArea())
.navigationTitle(AppL10n.string("releaseNotes.title", language: language))
.navigationBarTitleDisplayMode(.inline)
@@ -63,6 +73,9 @@ struct ReleaseNotesSheet: View {
}
}
}
// Force full-height sheet so the web view isn't clipped under a mid detent.
.presentationDetents([.large])
.presentationDragIndicator(.visible)
// Sheet can drop WindowGroup environment; re-assert language + appearance.
.environment(\.locale, language.swiftUILocale)
.preferredColorScheme(appearance.colorScheme)