# iOS 添加到主屏幕：iOS PWA 安装

> PWA 安装在 iOS 与 iPadOS 上的工作方式——手动"添加到主屏幕"手势、主屏幕条目能做什么，以及 iOS 26 对 Web 应用模式的改变。

**一句话：** 在 iOS 与 iPadOS 上，安装 PWA 的唯一途径是用户自己的手势——点按分享，
再点按"添加到主屏幕"。没有可挂接的安装事件，也没有你能触发的提示。你的标记提供的是默认值
与素材——图标、默认名称、清单——而名称由用户在添加时编辑；在 iOS 26 上，图标如何启动则由
用户的"以 Web 应用打开"开关决定。

## 安装手势

自第一代 iPhone 起，用户就可以把任意网站添加到主屏幕。在 iOS 与 iPadOS 的 Safari 中，
流程是：点按**分享**按钮打开分享菜单，然后点按**"添加到主屏幕"**。该网站的图标随即出现在
主屏幕上，轻点一下就能回到站点。用户在添加时还可以修改应用的名称。

没有程序化的替代方案。`beforeinstallprompt`——其他平台在提示用户把站点安装到主屏幕之前
触发的那个事件——是非标准的，且被标注为可用性有限，因此这里没有任何东西可供拦截、延迟或
重新触发。

### 第三方浏览器同样可以安装

iOS 与 iPadOS 16.4 让第三方浏览器也能在分享菜单中提供"添加到主屏幕"。WebKit 明确记录了
该菜单项出现所需满足的条件：

- 应用持有 `com.apple.developer.web-browser` 托管授权；
- 分享菜单的 `activityItems` 数组中包含一个 `WKWebView`；
- 该 `WKWebView` 正在显示 HTTP 或 HTTPS URL 的文档；
- 若设备是 iPad，则它未被配置为共享 iPad。

## 是 Web 应用，还是书签

用户点按生成的图标后会发生什么，在 16.4 与 26 之间发生了实质变化。

**在 iOS 与 iPadOS 16.4 上**，只要网站的清单把 `display` 成员设为 `standalone` 或
`fullscreen`，它就会以 Web 应用方式打开——无论是哪个浏览器添加的——并在 App 切换器中
拥有独立于 Safari 的预览。如果既没有请求 Web 应用行为的清单，也没有把站点标记为
web-app-capable 的 meta 标签，该条目会保存为主屏幕书签；自 16.4 起，这类书签会在用户当前的
默认浏览器中打开。

**在 iOS 26 与 iPadOS 26 上**，WebKit 修改了这一行为：默认情况下，添加到主屏幕的每一个
网站都以 Web 应用方式打开。更想要浏览器书签的用户，可以在添加时关闭"以 Web 应用打开"——
即便站点本身被配置为 Web 应用。WebKit 的表述很直白：Safari 中现在对"可安装性"已没有任何
要求，用户可以把任意站点添加到主屏幕并以 Web 应用方式打开它。决定权从你的标记转移到了
拿着手机的那个人手里。

这并没有移除任何支持。WebKit 明确指出，清单带来的好处依然生效——在清单中声明图标，它们
就会被使用——只是让用户获得 Web 应用体验不再**需要**清单文件；正如 iOS 与 iPadOS 的主屏幕
Web 应用从来不像其他平台的 PWA 那样要求 Service Worker。

## 图标、名称与启动图

- **清单图标自 iOS 与 iPadOS 15.4 起即受支持。** 你也可以沿用在文档头中列出
  `apple-touch-icon` 链接这一长期做法——如果两者都提供，**`apple-touch-icon` 优先**于
  清单声明的图标。
- **不提供图标，系统会替你造一个。** 过去 iOS 会用站点截图生成图标；16.4 改为用站点名称的
  首字母加上取自站点的颜色生成一个字母图标。
- **为触摸图标设定尺寸。** Apple 的指南建议把 `apple-touch-icon.png` 放在根文档目录作为
  全站图标，或用 `sizes` 声明分页面图标；系统使用最贴近设备合适尺寸的图标，其次是大于该
  尺寸中最小的那个，再次是可用图标中最大的那个。
- **启动图。** 默认情况下，应用启动时显示的是它上次启动时的截图；
  `<link rel="apple-touch-startup-image" href="/launch.png">` 可覆盖它。这在 Web 应用离线时
  尤其有用。
- **启动图标标题。** 默认使用 `<title>` 标签；
  `<meta name="apple-mobile-web-app-title" content="AppTitle">` 可设置不同的名称。

```html
<!-- 全站图标，以及一个针对设备的覆盖项 -->
<link rel="apple-touch-icon" href="/touch-icon-iphone.png">
<link rel="apple-touch-icon" sizes="180x180" href="/touch-icon-iphone-retina.png">

<!-- Web 应用启动时显示的启动图 -->
<link rel="apple-touch-startup-image" href="/launch.png">

<!-- 主屏幕图标下方的名称 -->
<meta name="apple-mobile-web-app-title" content="AppTitle">
```

### 旧版 `apple-` 独立模式标签

Apple 的存档指南描述了清单出现之前的机制。理解它仍有价值，因为你会在旧代码和旧建议中
遇到它：

> 把 `apple-mobile-web-app-capable` meta 标签设为 `yes` 以开启独立模式。

在那个存档模型中，独立模式意味着不使用 Safari 来显示网页内容——顶部没有 URL 输入框、
底部没有按钮栏，只保留状态栏——并且 `apple-mobile-web-app-status-bar-style`
"除非你先按上述方式指定独立模式，否则不起作用"。WebKit 自己的历史记述把这个标签定位在
2008 年 8 月的 iPhone OS 2.1；Web 应用清单自 2013 年开始标准化，Safari 于 2018 年 3 月
随 iOS 11.4 采纳。

请把这层依赖关系当作**有据可查的历史行为**来读，而不是今天仍在决定独立模式的规则：在
iOS 26 与 iPadOS 26 上，决定主屏幕条目是否以 Web 应用打开的是"以 Web 应用打开"开关，
无论标记怎么写。

```html
<!-- 旧版独立模式，如 Apple 存档指南所述 -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<!-- "black" 是 Apple 存档指南所演示的取值 -->
```

## 检测显示上下文

你无法询问用户**能否**安装——因为不存在安装事件。你能询问的是页面**此刻**以何种方式显示，
而这正是真正有意义的分支。可用的检查有两种：

- `window.navigator.standalone` 是 Apple 指南所记载的只读布尔属性，用于判断网页是否正以
  独立模式显示。
- 标准的 `display-mode` 媒体特性用于测试 Web 应用是显示在普通浏览器标签页中，还是以独立
  应用、全屏等其他方式显示。它的值标识浏览器**实际采用**的模式，可能与清单请求的模式不同。

```js
// 优先使用标准媒体特性；回退到 Apple 的旧属性。
const standalone =
  ('standalone' in window.navigator && window.navigator.standalone === true) ||
  window.matchMedia('(display-mode: standalone)').matches;

if (standalone) {
  hideInstallHint();            // 正以独立模式显示——无需再教学
} else {
  // iOS 上不存在安装事件，唯一的"提示"就是你自己的文案。
  showShareMenuInstructions();  // "点按分享，然后添加到主屏幕"
}
```

请在用户访问过几次之后再显示该提示，而不是首次加载就弹出；并且绝不要在页面正以独立模式
显示时渲染它——在已启动的 Web 应用里出现安装横幅纯属噪音。

## 主屏幕 Web 应用的能力支持

| 能力 | iOS / iPadOS | 说明 |
|---|---|---|
| 添加到主屏幕 | 支持，经分享菜单 | Safari 自第一代 iPhone 起；第三方浏览器自 16.4 起。 |
| `beforeinstallprompt` | 不支持 | 非标准、可用性有限——没有程序化安装。 |
| 以 Web 应用方式打开 | 支持 | 16.4 上需清单 `display: standalone`/`fullscreen`；iOS 26 上每个添加的站点默认如此。 |
| App 切换器条目 | 支持 | Web 应用与 Safari 分开预览。 |
| Service Worker | 支持，但非必需 | 主屏幕 Web 应用从不要求它；原始 Web Push 需要，Declarative Web Push（18.4+）不需要。 |
| Web Push | 支持（16.4+），仅主屏幕 Web 应用 | 请求必须响应直接用户交互。18.4 起 Declarative Web Push 额外提供 `window.pushManager`。 |
| Badging API | 支持（16.4+），仅主屏幕 Web 应用 | 用户允许通知后，图标上才出现计数。 |
| 清单 `id` | 支持（16.4+） | 与用户所取名称组合，标识每一份安装。 |
| 清单 `icons` | 支持（15.4+） | 若同时存在，`apple-touch-icon` 优先。 |
| 屏幕唤醒锁 / 屏幕方向 / 用户激活 | 支持（16.4+） | WebKit 将其列为 16.4 面向 Web 应用开发者的新能力。 |
| 默认持久存储 | 不支持 | 除非源主动申请，存储都是尽力而为。 |

各浏览器的分特性数据，请查看 [/zh/compatibility/](/zh/compatibility/)。

## 可多次安装是特性，不是缺陷

iOS 从一开始就支持同一个 Web 应用安装多次，WebKit 把它视为有意为之：多账号、工作与个人
分离等等。自 16.4 起，清单的 `id` 成员——一个 URL 形态的 Web 应用唯一标识符——会与用户添加
时输入的名称组合起来，使每份副本都可区分。这正是"专注模式"能只静音"Shiny（个人）"而放行
"Shiny（工作）"的原因；当用户在多台设备上给同一站点取相同名称时，专注设置也因此得以同步。

显式声明 `id` 依然值得做：web.dev 的论点是，显式的 `id` 直接定义了标识符，使其不再依赖
`start_url` 或清单文件所在的位置。

## 实践检查清单

- [ ] 自行提供"点按分享，然后添加到主屏幕"的说明——没有任何浏览器 UI 会替你提示。
- [ ] 按 `(display-mode: standalone)` 分支（以 `navigator.standalone` 作为旧版回退），绝不要按 User-Agent 字符串。
- [ ] 在清单中声明图标，同时清楚一个随手留下的 `apple-touch-icon` 会覆盖它们。
- [ ] 设置清单 `id`，让多次安装可区分、专注设置可同步。
- [ ] 不要依赖 `apple-mobile-web-app-capable` 在 iOS 26 上决定 Web 应用模式——由用户的"以 Web 应用打开"开关决定。
- [ ] 即便主屏幕条目不要求 Service Worker，也要注册一个；原始 Web Push（16.4+）需要它。Declarative Web Push（18.4+）则通过 `window.pushManager` 订阅，无需 Service Worker。
- [ ] 把已存数据当作尽力而为：它在该源未超配额、设备仍有空间、且用户没有清除数据时才会保留；而且在开启跨站跟踪保护时，若某个源在最近七天的浏览器使用中没有点击或轻点，Safari 会主动删除它由脚本创建的数据。`navigator.storage.persist()` 是主动申请的入口，而 Safari 会依据交互历史自动作答，不向用户显示提示。
- [ ] 在真机上测试——上述行为都是主屏幕行为，不是标签页行为。

## 下一步去哪里

- [iOS 与 Safari 上的 PWA](/zh/reference/platforms/ios-safari/) —— 应用进入主屏幕之后，
  整个 WebKit 平台的全貌。
- [iOS Safari 推送](/zh/reference/notifications/ios-safari-push/) —— 依赖这一安装步骤的
  16.4 Web Push 配置。
- [角标](/zh/reference/installation/badging/) —— 主屏幕图标上的 `setAppBadge`/`clearAppBadge`。
- [可安装性标准](/zh/reference/installation/installability-criteria/) —— 其他平台在愿意提供
  安装之前要求什么。