Files
ios/Xuxucheck/docs/webview-safe-area.md
T
admin d6aeede2bc 导入 XuXuCheck 工程(iOS 干化学尿液试纸分析 App)
从桌面工程复制而来,作为本仓库的 Xuxucheck/ 子目录。

为控制仓库体积做了两处删减:
- 移除 Frameworks/opencv2.xcframework 下的两个 visionOS 切片
  (xros-arm64、xros-arm64-simulator,合计约 106 MB),并同步删掉该
  xcframework 内 Info.plist 里对应的两条 AvailableLibraries 记录,
  只保留 ios-arm64 与 ios-arm64_x86_64-simulator。
- 移除全部 .DS_Store 与 xcuserdata/。

已用 xcodebuild 从本目录完整编译验证:BUILD SUCCEEDED,
ProcessXCFramework 以 --platform ios 成功选中框架,产物含 3699 个 cv:: 符号
(OpenCV 确实链入),嵌入 framework 3 个,与移除无用 pod 之后的状态一致。

仓库约 206 MB,其中 OpenCV 203 MB、其余源码与依赖约 3 MB。
2026-09-23 14:17:31 +08:00

7.4 KiB
Raw Blame History

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,正好嵌进灵动岛(2047pt)下沿; 返回键同在这一行,也就贴进了屏幕左上角那块系统手势保留区(iOS 留给下拉通知中心/控制中心的地盘), 那里的点击会被系统吃掉 —— 这就是「返回点不动」的原因,不是按钮坏了。

现在留一小条白、整体下移 topSafeInset(当前 20pt):20 + 49 ≈ 69pt, 实测标题中心在 ~70pt,离开灵动岛下沿 23pt,也离开了手势区。

底部

底部那条白带的原因和顶部相反 —— 是两层都没铺到底:

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。

现在的做法

// 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 同步成页面底色,但不保证),设之前先在真机上确认它在你家页面上不是白的, 否则会重新出现一条白带。

验证

xcodebuild -workspace XuXuCheck.xcworkspace -scheme XuXuCheck -configuration Debug \
           -destination 'id=<device-id>' -allowProvisioningUpdates build

真机逐页看:标题是否完整露出、左上角返回能否点、顶部蓝边宽窄、页面滚到底时底部是否还有白带、 最后一行是否被 home indicator 压住。

附:怎么量截屏像素

判断「某某区域到底是什么颜色、多高」不要靠眼睛估计,直接读像素。 本机 swift 解释器构建模块会报错(could not build module '_DarwinFoundation1'),用下面这条纯标准库的路子:

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」。