# WebView 上下边距 / 通屏显示 涉及文件(这几处是**一个整体**,只改其中一处会出问题): - `XuXuCheck/Components/XuXuWebView.swift` — `topSafeInset` 常量、`.padding(.top,)`、两层 `.ignoresSafeArea(...)` - `XuXuCheck/Components/WebViewRepresentable.swift` — `contentInsetAdjustmentBehavior = .never` 所有网页(记录 / 成员 / 我的 / 产品介绍 / 检测结果,以及首页四个入口)都走同一个 `XuXuWebView`, 改这里一处,全局生效。 ## 现象 三个阶段,顶部和底部各修了一半: 1. 改之前 —— 每个网页顶部露出一大条蓝色空白,内容被压得很低; 同时屏幕最下方 34pt 是一条纯白带。 2. 只通屏顶部之后 —— 标题被灵动岛压住,网页左上角的返回键落进系统区域,点不动。 3. 现在 —— 顶部留 20pt,底部铺到物理底边。 ## 判断逻辑(可复用) 同一个「留白」现象,可能是两种**完全相反**的成因,修法也相反。所以第一步不是改代码, 而是**确定这块区域显示的是谁的颜色** —— 用 `docs/tools/px.py` 量出来,别靠眼睛: | 量到的颜色 | 含义 | 该怎么做 | | --- | --- | --- | | 本页蓝色底 `#395CFF` | 容器铺到了,webview 没铺到,露出 App 自己的底色 | 让 webview 铺满这一边 | | 父视图的白 / 浅灰 `#FFFFFF` | 连容器都没铺到,露出的是 `XuXuWebView` 外面 | 给容器的 `ignoresSafeArea` edges 补上这一边 | | 网页自己的底色 `#E8F3FE` | 网页在该边自己留了空 | 是网页的事,客户端别动 | | 系统材质(模糊 / 纯黑) | 系统安全区自己画的 | 这块本来就该留给系统,不要盖 | 本文件里的顶部和底部就是这两种情况的活标本: - **顶部** —— 所有层都铺到了,但网页自带 ~49pt 让位,系统安全区又整份叠上去 = 双份,所以要**收**。 - **底部** —— 两层都停在安全区上,没人去画那 34pt,父视图白底漏出来,所以要**铺满**。 另一个容易搞错的是 `ignoresSafeArea` 和 `padding` 的顺序:想「铺满但内容内缩」, 必须 `padding` 在前、`ignoresSafeArea` 在后;反过来说,看到别人写 `.ignoresSafeArea(...).padding(...)` 时,那是给满屏视图加外衬距,底部会溢出容器。 ## 顶部 原来蓝色底铺到了物理顶端,而 `VStack` / `WebViewRepresentable` 还遵守顶部安全区, 网页从安全区下方开始渲染 —— 顶部就空出约 62pt 的蓝色带。 这 62pt 本身该留,问题是**网页自己也留了一份**。从改前的截屏反推(iPhone 16 Pro,单位 pt): | 量 | 值 | 来源 | | --- | --- | --- | | webview 起点 | ~62 | 设备顶部安全区 | | 网页标题中心 | ~111 | 截屏量得 | | → 网页自身头部下移量 | ~49 | 111 − 62 | | 灵动岛黑胶囊纵向范围 | 20 ~ 47 | 截屏量得 | 也就是说网页头部是按「浏览器里已经让开状态栏」设计的,自带约 49pt 下移,真正多余的只有那 62pt。 但直接把 webview 铺到 y=0 也不行:标题中心会落到 ~49pt,正好嵌进灵动岛(20~47pt)下沿; 返回键同在这一行,也就贴进了屏幕左上角那块**系统手势保留区**(iOS 留给下拉通知中心/控制中心的地盘), 那里的点击会被系统吃掉 —— 这就是「返回点不动」的原因,不是按钮坏了。 现在留一小条白、整体下移 `topSafeInset`(当前 20pt):`20 + 49 ≈ 69pt`, 实测标题中心在 ~70pt,离开灵动岛下沿 23pt,也离开了手势区。 ## 底部 底部那条白带的原因和顶部相反 —— 是**两层都没铺到底**: ```swift Color(hex: 0x395cff).ignoresSafeArea(.keyboard, edges: .bottom) .ignoresSafeArea(.container, edges: [.top, .horizontal]) // ← 原先少了 .bottom VStack { ... }.ignoresSafeArea(.container, edges: .top) // ← 原先只有 .top ``` 两层都停在底部安全区边界上,于是最下面 34pt 谁也不画,露出的是 `XuXuWebView` 背后**父视图的白底**。 实测(同一张截屏,屏高 874pt): | 位置 | 颜色 | | --- | --- | | 网页正文底色 | `#E8F3FE` | | y = 840 ~ 874pt | `#FFFFFF` 纯白,正好 34pt = 底部安全区 | 修法:两层的 edges 都补上 `.bottom`。 ## 现在的做法 ```swift // XuXuWebView.swift private let topSafeInset: CGFloat = 20 ... Color(hex: 0x395cff) .ignoresSafeArea(.keyboard, edges: .bottom) .ignoresSafeArea(.container, edges: [.top, .horizontal, .bottom]) VStack { WebViewRepresentable(currentUrl: currentUrl) ... } .padding(.top, topSafeInset) .ignoresSafeArea(.container, edges: [.top, .bottom]) ``` **顺序不能反**:先 `.padding(.top,)` 把内容推下去,再 `.ignoresSafeArea(...)` 让容器重新铺到物理顶端, 被推出来的那 20pt 就露出 ZStack 里的蓝色底。反过来写(`.ignoresSafeArea` 在前、`.padding` 在后) 是给一个已经满屏的视图再加外衬距,底部会被挤出容器。 **`.never` 不能删**:默认的 `.automatic` 在 webView 铺到状态栏下面之后会再叠一份安全区 inset, 和 `.padding(.top,)` 双重叠加,就又回到「一大条留白」的原始状态。这三处是一套,注意成对维护。 ## 调参 只改 `XuXuWebView.swift` 里的 `topSafeInset`: | topSafeInset | 效果 | | --- | --- | | `8` ~ `12` | 顶部最窄,逼近灵动岛;小屏/无刘海机型可能不够 | | **`20`(当前)** | 标题与返回键落在灵动岛下方,顶部留一条窄蓝边 | | `30` ~ `38` | 更稳妥地躲开左上角手势区;顶部蓝边更明显 | | `62`(原值附近) | 回到改动前的观感 | 判断依据:以**头部最挤的那个页面**为准。底部没有可调参数,它就是物理底边。 ## 已知取舍 底部铺到物理底边之后,网页内容在滚到最末时可以压到 home indicator 底下(home indicator 是半透明浮层, 文字仍看得见,但会有一条横杠压在最后一行上)。若这点不能接受,可以给 `WebViewRepresentable` 的 `scrollView.contentInset.bottom` 设成安全区高度 —— 背景仍通到物理底, 只是内容提前 34pt 结束。**注意**:`contentInset` 区域显示的是 scrollView 自己的背景色 (通常会被 WebKit 同步成页面底色,但不保证),设之前先在真机上确认它在你家页面上不是白的, 否则会重新出现一条白带。 ## 验证 ```bash xcodebuild -workspace XuXuCheck.xcworkspace -scheme XuXuCheck -configuration Debug \ -destination 'id=' -allowProvisioningUpdates build ``` 真机逐页看:标题是否完整露出、左上角返回能否点、顶部蓝边宽窄、**页面滚到底时底部是否还有白带**、 最后一行是否被 home indicator 压住。 ## 附:怎么量截屏像素 判断「某某区域到底是什么颜色、多高」不要靠眼睛估计,直接读像素。 本机 `swift` 解释器构建模块会报错(`could not build module '_DarwinFoundation1'`),用下面这条纯标准库的路子: ```bash sips -s format bmp /tmp/shot.jpg --out /tmp/shot.bmp # sips 输出 24bpp、自下而上排列的 BMP python3 px.py /tmp/shot.bmp # 见 docs/tools/px.py ``` `px.py` 会沿最左边一列扫描并打印所有颜色跳变点(最左列只经过背景,不经过文字), 再用 `pt = 像素 / 3`(iPhone 16 Pro 是 3x)换算成点。`webview-safe-area.md` 里的数字都是这么来的。 ## 相关 - 只影响网页;`CameraView`、`ScanView` 等原生界面不受影响。 - 顶部 inset 与相机启动白屏无关,见项目根目录 `CLAUDE.md` 的「Notes and quirks」。