# iOS 与 Safari 上的 PWA

> PWA 在 iOS 与 iPadOS 上如何安装与运行：添加到主屏幕、Open as Web App 开关、主屏幕 Web 应用的 Web Push 与角标，以及要面对的 WebKit 存储限制。

在 iOS 与 iPadOS 上，PWA 就是主屏幕 Web 应用：用户从共享面板把网站添加到主屏幕，之后它在浏览器之外打开、在应用切换器里有自己的条目，并且只要网站实现了，就能使用 Service Worker、Web Push 和角标。这里没有可调用的安装步骤，平台层面的工作是教会用户这个手势，并针对 WebKit 每个版本实际提供的能力做设计。

## 工作原理

三个系统版本决定了网站看到的行为：iOS 16.4（2023-03）让第三方浏览器和 manifest 进入流程，iOS 18.4（2025-03）加入了声明式 Web Push，iOS 26（2025-09）把"以 Web 应用打开"变成了每个被添加网站的默认值。

### 添加到主屏幕的手势

Apple 的 iPhone 使用手册 [Turn a website into an app in Safari on iPhone](https://support.apple.com/guide/iphone/open-as-web-app-iphea86e5236/ios) 记载了 Safari 中的路径（英文界面）：点按共享按钮，向下滚动操作列表，点按 **Add to Home Screen**，保持 **Open as Web App** 开启，再点按 **Add**。如果共享面板里没有 **Add to Home Screen**，用户可以通过列表底部的 **Edit Actions** 把它加回来。图标只出现在添加它的那台设备上。

自 iOS 与 iPadOS 16.4 起，第三方浏览器也能提供同样的操作，条件见 [WebKit 公告](https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/)：应用持有 `com.apple.developer.web-browser` 受管理权限、共享面板的 `activityItems` 中包含一个正在显示 `http:` 或 `https:` 文档的 `WKWebView`、且设备不是共享 iPad。

### 由什么决定是 Web 应用还是书签

在 iOS 16.4 到 18 上由 manifest 决定：manifest 把 `display` 设为 `standalone` 或 `fullscreen` 的网站，无论由哪个浏览器添加，都以 Web 应用打开；没有这种 manifest（也没有旧式 web-app-capable meta 标签）的网站成为主屏幕书签，在用户的默认浏览器中打开。

iOS 26 与 iPadOS 26 把决定权交给了用户。WebKit 的 [Safari 26.0 说明](https://webkit.org/blog/17333/webkit-features-in-safari-26-0/)写明：默认情况下每个添加到主屏幕的网站都以 Web 应用打开；用户可以在添加时关闭"Open as Web App"改为书签；Safari 中"可安装性"的要求为零。manifest 仍然塑造结果：其中声明的图标会被使用，`display`、`name` 与 `start_url` 也生效。

### 图标与身份

manifest 图标自 iOS 15.4 起被读取，但同时存在 `apple-touch-icon` 链接时以后者优先。完全没有图标时，iOS 16.4 及之后会用网站名称首字母配上取自网站的颜色生成一个字母图标。manifest 的 `id` 成员（16.4+）与用户添加时输入的名称组合，用来区分同一网站的多个安装，这也是专注模式能静音其中一份而不影响另一份的原因。

### 只在已安装上下文中可用的 API

Web Push、Notifications API 与 Badging API 自 iOS 16.4 起在主屏幕 Web 应用中可用；iOS 上的 Safari 标签页一个都没有。通知权限必须在用户手势（例如点按订阅按钮）的响应中请求；`setAppBadge()` 在权限授予前就会改变存储的计数，待用户允许通知后显示在图标上。声明式 Web Push（iOS 与 iPadOS 18.4、macOS 15.5）增加了 `window.pushManager`，无需 Service Worker 即可显示通知；原有的推送流程仍通过 Service Worker 注册对象订阅。

### 存储

除非源调用 `navigator.storage.persist()`，浏览器存储的数据都是尽力而为；Safari 根据用户的交互历史自动回应该调用，不显示任何提示。开启跨站跟踪预防时，若某个源在最近七天的浏览器使用中没有任何点按或点击，Safari 会删除该源由脚本创建的数据，服务器设置的 Cookie 得以保留（MDN，[Storage quotas and eviction criteria](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria)）。自 iOS 17 起，主屏幕 Web 应用享有浏览器应用的配额，每个源约为磁盘的 60%，而不是内嵌 `WKWebView` 的 15%。

## 示例

两段代码在 Safari 标签页和主屏幕 Web 应用中都能运行，不同的只是分支。

### 优先使用声明式管理器订阅推送

页面拿到哪个 `PushManager` 取决于可用的推送类型。声明式的挂在 `window` 上，不需要 Service Worker；原有的挂在注册对象上，必须等待一个激活的 Worker。

```js
subscribeButton.addEventListener('click', async () => {
  if (!('PushManager' in window) || !('Notification' in window)) {
    // iOS 上的 Safari 标签页，或任何没有推送的浏览器：提供应用内收件箱。
    showInAppInbox();
    return;
  }

  let pushManager;
  if ('pushManager' in window) {
    pushManager = window.pushManager; // 声明式 Web Push，iOS 18.4+
  } else if ('serviceWorker' in navigator) {
    await navigator.serviceWorker.register('/sw.js');
    pushManager = (await navigator.serviceWorker.ready).pushManager;
  } else {
    showInAppInbox();
    return;
  }

  if ((await Notification.requestPermission()) !== 'granted') return;

  await pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: VAPID_PUBLIC_KEY,
  });
});
```

与 Chrome 和 Edge 一样，WebKit 会拒绝没有 `userVisibleOnly: true` 的订阅。声明式路径还要求服务端发送声明式 JSON 载荷，见 [iOS Safari 推送](/zh/reference/notifications/ios-safari-push/)。

### 只在浏览器标签页中显示安装提示

`display-mode` 媒体特性报告的是当前的呈现方式，而不是页面如何被启动，所以应该检测安装提示唯一该出现的那种模式，而不是枚举它不该出现的模式。

```js
const inBrowserTab =
  typeof window.matchMedia === 'function' &&
  window.matchMedia('(display-mode: browser)').matches;

if (inBrowserTab) {
  // iOS 上没有安装事件：自己解释"共享 > Add to Home Screen"。
  showAddToHomeScreenHint();
} else {
  // 主屏幕 Web 应用、全屏，或无法回答的引擎：不显示提示。
  hideInstallHint();
}
```

通过 Fullscreen API 进入全屏的页面也不再匹配 `browser`，这正是想要的结果：盖在全屏视频上的浮层比漏掉一次提示更糟。

## 实测行为

MDN 对 `beforeinstallprompt` 未列出任何 Safari 或 iOS 版 Safari 的版本号，因此唯一的安装界面就是共享面板，而它的标签在 iOS 26 发生了变化。

:::observed
iOS 26 上的 Safari（英文界面）：共享面板中的操作标签为 "Add to Home Screen"；下一屏有一个默认开启、标签为 "Open as Web App" 的开关和一个 "Add" 按钮。关闭开关后得到的是在默认浏览器中打开的主屏幕书签；开启时图标打开的网站没有 Safari 界面。见 Apple 的 [Turn a website into an app in Safari on iPhone](https://support.apple.com/guide/iphone/open-as-web-app-iphea86e5236/ios) 与 WebKit 的 Safari 26.0 功能说明。
:::

因为开关是每次添加时单独决定的，两个用户添加同一个 URL 可能得到不同的行为；运行时检测 `display-mode`，不要假设 manifest 一定生效。

## 另请参阅

- [iOS 添加到主屏幕](/zh/reference/installation/ios-add-to-home-screen/)
- [iOS Safari 推送](/zh/reference/notifications/ios-safari-push/)
- [角标](/zh/reference/installation/badging/)
- [存储持久化](/zh/reference/storage/persistence/)
- [macOS 上的 Safari PWA（Dock 应用）](/zh/reference/platforms/macos-safari/)
- [Web Push for Web Apps on iOS and iPadOS](https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/)（webkit.org）
- [Meet Declarative Web Push](https://webkit.org/blog/16535/meet-declarative-web-push/)（webkit.org）
- [WebKit Features in Safari 26.0](https://webkit.org/blog/17333/webkit-features-in-safari-26-0/)（webkit.org）
- [Turn a website into an app in Safari on iPhone](https://support.apple.com/guide/iphone/open-as-web-app-iphea86e5236/ios)（support.apple.com）