d6aeede2bc
从桌面工程复制而来,作为本仓库的 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。
154 lines
7.4 KiB
Markdown
154 lines
7.4 KiB
Markdown
# 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=<device-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」。
|