从桌面工程复制而来,作为本仓库的 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。
7.4 KiB
WebView 上下边距 / 通屏显示
涉及文件(这几处是一个整体,只改其中一处会出问题):
XuXuCheck/Components/XuXuWebView.swift—topSafeInset常量、.padding(.top,)、两层.ignoresSafeArea(...)XuXuCheck/Components/WebViewRepresentable.swift—contentInsetAdjustmentBehavior = .never
所有网页(记录 / 成员 / 我的 / 产品介绍 / 检测结果,以及首页四个入口)都走同一个 XuXuWebView,
改这里一处,全局生效。
现象
三个阶段,顶部和底部各修了一半:
- 改之前 —— 每个网页顶部露出一大条蓝色空白,内容被压得很低; 同时屏幕最下方 34pt 是一条纯白带。
- 只通屏顶部之后 —— 标题被灵动岛压住,网页左上角的返回键落进系统区域,点不动。
- 现在 —— 顶部留 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」。