跳转到内容

平台 · 平台

iOS 与 Safari 上的 PWA

发布于 更新于

在 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 记载了 Safari 中的路径(英文界面):点按共享按钮,向下滚动操作列表,点按 Add to Home Screen,保持 Open as Web App 开启,再点按 Add。如果共享面板里没有 Add to Home Screen,用户可以通过列表底部的 Edit Actions 把它加回来。图标只出现在添加它的那台设备上。

自 iOS 与 iPadOS 16.4 起,第三方浏览器也能提供同样的操作,条件见 WebKit 公告:应用持有 com.apple.developer.web-browser 受管理权限、共享面板的 activityItems 中包含一个正在显示 http: 或 https: 文档的 WKWebView、且设备不是共享 iPad。

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

iOS 26 与 iPadOS 26 把决定权交给了用户。WebKit 的 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+)与用户添加时输入的名称组合,用来区分同一网站的多个安装,这也是专注模式能静音其中一份而不影响另一份的原因。

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)。自 iOS 17 起,主屏幕 Web 应用享有浏览器应用的配额,每个源约为磁盘的 60%,而不是内嵌 WKWebView 的 15%。

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

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

Section titled “优先使用声明式管理器订阅推送”

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

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 推送。

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

Section titled “只在浏览器标签页中显示安装提示”

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

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 发生了变化。

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

规范

规范状态
无。