ChengRang

iPhone Duo 适配记:Web 无招可破,原生要清旧账

开发者教程 约 12 分钟阅读

苹果给 iPhone Duo 开了个开发者专区。里面有六场技术讲座、三场 Group Lab 回放,SwiftUI、UIKit、相机三场论坛问答,另有 Human Interface Guidelines 和各地的线下 workshop。东西不少,讲的却全是原生 App。专区页面和配套文档我从头翻到尾,Safari 两个字一次也没出现。

这也不奇怪。Safari 按视口画页面,不按机器画,中间也没有重新编译这一步。网页本来就随窗口变形,好比独孤九剑,无招可破。现成的响应式 CSS 拿到这台机器上,照样能跑。

我不想只凭推想,就写了个检测页,在 iPhone Duo 模拟器里打开,合上看一次,展开看一次,把视口、屏幕、安全区、视口单位和折叠屏接口都读了出来。另外拿自家的 ChengRang 和 WorkBuddy 两个站对照了布局,又装了一款没适配的 App 看原生那边的样子。结果和原先的推想有出入,下面按实测来写。

一、Web:模拟器里读到的数

外屏合上

图 1:外屏合上。Safari 的栏竖排在右侧,网页视口 382 × 580。

外屏合上时,Safari 把状态栏和后退、书签、新建、标签页几个按钮竖着排在右边,网页在它左边。视口是 382 × 580,safe-area 上右下左四个值全是 0。摄像头和状态栏都在网页区域之外,网页压根碰不到。

内屏展开横放

图 2:内屏展开横放。Safari 的栏回到顶部,视口 951 × 589。橙色的几格,是检测页标出来的异常。

内屏展开横放时,Safari 的栏回到顶部横排,视口变成 951 × 589,落在 769 到 1024 的平板区间。safe-area 上右下左是 2、0、34、0:底部那 34 留给 Home 指示条,左右两边仍然都是 0。

往下一滚,Safari 顶栏缩成一枚小小的地址胶囊,视口跟着长到 951 × 618,safe-area 顶部从 2 一路涨到 31。网页这时铺到了状态栏底下,安全区是随着工具栏伸缩现算的,并非定数。变化记录里看得清楚:高度从 540、565、614 一步步涨到 618,顶部安全区从 0、27 涨到 31,前后不过一百多毫秒。

有几个数要单拎出来说:

两个站的布局,外屏都切到了手机版,没出错。内屏横放时,ChengRang 换成了带侧栏的桌面布局,WorkBuddy 却还是手机上那个汉堡菜单。

顺带一提,Safari 自己就是这次竖向栏的现成样板。原生 App 用 27.1 SDK 以后,栏会怎么竖起来、挂在哪里,看图 1 就明白了。

二、Web:要补的四处

其一,视口会在中途变。 手机一折一开,App 就在内外两块屏之间迁移,接着重新算尺寸。网页也一样,看到一半,宽度说变就变。如果首屏读一次 window.innerWidth 存下来一直用,到这一刻就全错了。凡是和尺寸有关的逻辑,都改用 ResizeObserver;退一步,也要监听 resize 事件,尺寸一变就重新读。

其二,100vh 换成 100dvh。 上面量得明白:工具栏展开时,100vh 比真实可见高度多出 18 到 29 像素,满屏的弹层、底部按钮就会被压在地址栏下面。这一条在普通 iPhone 上早就该改,根子在 Safari 工具栏会伸缩,和折叠屏关系不大,这次顺手改掉。

其三,看一眼桌面导航的断点。 内屏横放时,自家站点已经换成带侧栏的桌面布局;WorkBuddy 还是手机上那个汉堡菜单,两边空出一大片。这谈不上出错,页面照样能用,只是这么大一块屏幕还用手机导航,有点可惜。苹果要求原生 App 在内屏上看起来像 iPad 布局的自然延伸,网页也是这个道理。桌面导航的断点如果设得偏高,就往下调一调,让平板区间也能用上大屏布局。

其四,别拿 screen.width 判断设备。 图 2 里展开以后,视口 951 宽,screen 报的还是外屏的 466 × 678。哪个页面要是靠 screen.width 分辨手机和平板,到了内屏就会把一块平板大小的屏当成手机。布局一律看视口,也就是媒体查询和 innerWidth,screen 那组数别碰。

三、Web:原先担心、实测用不上的

我原先推想,内屏右边要给状态栏和摄像头让位,safe-area 的左右值会不一样,所以建议把 padding-left 和 padding-right 拆成两行写。实测下来,至少在 Safari 里这一条用不上:外屏四个值全是 0,内屏无论工具栏展开还是缩起,左右也都是 0。右边那一条被 Safari 自己的竖排栏占了,网页根本够不着。拆开写也没坏处,只是别指望它解决什么问题。

上下两边倒是会变。内屏顶部从 2 到 31,底部是 34,全看工具栏此刻是展开还是缩起。所以贴边的固定元素,照旧老老实实写 env(safe-area-inset-top) 和 env(safe-area-inset-bottom),千万别把 34、31 这些数抄进样式表里。添加到主屏幕的 Web App 没有 Safari 的栏,情况可能不同,我还没测。

折缝也一样。网页拿不到它的位置,完全展开时屏幕是平的,内容压在折缝上也看不出什么;半折着看网页会怎样,这次没试。

还有一件事。专门用来识别折叠屏的两套 Web 标准,Viewport Segments 和 Device Posture,Chrome 那边早就能用了:Device Posture 从 Chrome 132、Edge 132 开始支持,时间是 2025 年 1 月;Viewport Segments 也已经在 Chrome 上线。WebKit 这边,两个 standards-positions issue 都是 2024 年 3 月开的,到现在还是 open,也没挂表明立场的标签。今年 9 月 20 日,有人在两个 issue 下各留了一言,说 27.1 模拟器里已经能用 flag 打开这两项功能;第二天,WebKit 给 Device Posture 那个 issue 换了负责人。这些都算不上官方表态。眼下别指望它们,等下一版 Safari 再说。

四、原生:先看自己在哪一档

苹果给原生开发者的指南,题目叫《三步让您的 App 在 iPhone Duo 上出色呈现》。要不要动手,先看你的 App 是用哪一版 SDK 编译的:

苹果特意提醒:切到 27.1 SDK 之前,先确认你的内容受得住栏变成竖排。这里牵涉一批新 API,后面再说。

没适配的 App 长什么样

光看文字不够直观。我在模拟器里装了一款三国题材的小游戏,合上、展开横放、展开竖放,各截了一张。它是用哪版 SDK 编译的,我不知道,下面只说看到的样子。

App 在外屏

图 3:外屏合上。画面铺满整块屏,摄像头和状态栏压在背景的天空上。

外屏合上时,看不出毛病。背景一直画到摄像头和状态栏底下,标题、按钮都避开了右上角,没被挡住。这块屏的比例和普通 iPhone 相差无几,游戏原本就是照着手机竖屏画的,自然严丝合缝。

App 在内屏横放

图 4:内屏展开横放。App 缩成一个竖条窗口,左右两大片全黑。

展开横放时,毛病就出来了。App 没有铺开,缩成一个竖条窗口,孤零零立在屏幕正中,左右两边各空出一大片黑,整块内屏用了不到一半。这和苹果描述的旧 SDK 编译的 App 一模一样:"悬在屏幕正中,四周一圈留白"。只支持竖屏的 App 到了横放的内屏,也会是这副样子。单看截图,分不出是哪一个缘故。

App 在内屏竖放

图 5:内屏展开竖放。App 铺满了,背景图却缩在中间,标题和按钮跟它对不上。

展开竖放时,App 倒是铺满了,毛病换了一种。背景图没有跟着放大,还是原来的大小摆在中间,四周露出一圈灰色渐变。标题、按钮却是照着整块屏幕排的:标题一半压在图上、一半落在灰底上;"开始游戏"骑在图的下沿;"继续游戏"掉到图外面;底下四个按钮横跨整屏,比背景图还宽出一截。

问题出在两套坐标对不上。背景图照着固定尺寸画,按钮照着屏幕排。在手机上,图和屏幕一样大,两套坐标恰好重合,谁也看不出毛病。屏幕一换比例,两边就各走各的了。这正是后面第二步要改的写法:尺寸写死、素材只照一种比例做。

五、第一步:先让 skill 扫一遍

在 Xcode 里对编码助手说一句 "get my app ready for iPhone Duo",它就会调用一个叫 app resizability 的 skill,把整个工程从头到尾查一遍,把第二步列出的问题写法一处处找出来,说明问题出在哪、该怎么改。如果用的是别家的编码 agent,在终端执行 xcrun agent skills export,就能把这个 skill 导出来用。

整个专区里,我最留意的就是这一处。苹果把 agent skill 做进了 Xcode 命令行,还特意开了口子给第三方 agent 用。它显然清楚,如今很多开发者写代码,是一边跟 agent 说话一边写的。

不过苹果自己也承认,这个 skill 能查出大部分问题,但查不全。下面这份清单,最后还得自己过一遍。

六、第二步:三类旧写法要改

第一类,别再用 UIScreen.main 定尺寸。

这一类最要紧的是读尺寸的时机。折叠会在一次会话的中途改变 App 的大小,只在启动时或 viewIsAppearing 里读一次是不够的。和尺寸有关的布局放进 layoutSubviews 或 viewDidLayoutSubviews;只在尺寸变化时才需要跑一次的逻辑,交给 viewWillTransition(to:with:)。

第二类,别再拿设备方向推布局。 UIDevice.current.orientation、statusBarOrientation、interfaceOrientation(包括 windowScene.effectiveGeometry.interfaceOrientation),都不能用来判断 App 的形状。设备朝哪个方向,和 App 是宽是窄,本来就是两回事。想知道自己是横是竖,直接比宽高:view controller 里看 view.bounds,自定义 view 里看 superview.bounds,SwiftUI 里看 GeometryReader 给的尺寸。

第三类,别再用 idiom 或 size class 判断设备。 除了 iPhone Duo,还有可以随意缩放的 iPhone Mirroring,一个 iPhone App 现在可能出现在任意一种 size class 组合里。苹果对内屏的要求只有一句:看起来应该像这个 App 的 iPad 布局自然延伸出来的样子。具体有四点:

七、删完旧写法,还有新 API

用系统控件搭的界面,多半能自动适配:栏由系统自动转成竖排,系统自带的视图和容器也会自己避开折缝和摄像头。只有自定义视图,或者想精细控制栏的表现,才需要下面这批 API。它们都是从 iOS 27.1 开始提供的。

竖向栏。 外屏上的 sheet,栏默认是竖排,可以用 toolbarVerticalBehavior(_:) 或 preferredVerticalBarBehavior 关掉。内屏上的 sheet,放在中间或前导位置时栏是横排,放在尾部位置时变成竖排,位置由 presentationPlacement(_:) 或 preferredPlacement 指定。想知道竖向栏会放在哪一边,用 toolbarVerticalEdge 或 verticalBarEdge 查;如果当前场景下系统根本不放竖向栏,verticalBarEdge 返回 unspecified。

栏里的按钮也可以单独控制:axisBehavior 决定它进不进竖排;visibilityPriority 决定空间不够时哪个按钮先收进溢出菜单;"完成"这类主操作,可以用 topBarPinnedTrailing 或 pinnedTrailingGroup 固定在尾部。

显示规则要记清楚:竖排时只显示图标;横排时图标和标题都能用,优先图标;收进溢出菜单时,图标和标题一起显示。只有标题、没有图标的按钮不会进竖排,用自定义 view 做的按钮也不会。如果你的工具栏里有一排纯文字按钮,就得补上图标。图 1 里 Safari 竖排的那几个按钮,就是只有图标、没有文字。

排列视图。 SwiftUI 的 ArrangementView 和 UIKit 的 UIArrangementViewController,用来放一主一次两个视图,并随设备姿态自动重新排列。有 .split 和 .overlay 两种样式。别把它放进 navigation split view、list、scroll view 这类容器里,否则部分视图可能会点不到。

保留区域。 GeometryProxy.reservedRegions(...) 和 UIView.reservedRegions(...) 会告诉你,折缝和摄像头各占了哪块地方。折缝属于 division,会把一个大视图分成几块;摄像头属于 occlusion,会挡住内容。内屏的前置摄像头只在启用时才挡,外屏的则一直挡着。半折时,折缝区域是 active;完全展开时,是 inactive。

整份文档翻完,我没找到直接读取设备姿态或折叠角度的 API。想知道现在是不是半折,只能看保留区域是不是 active,间接推断。看苹果的意思,大概是不希望你把"现在是什么形态"写成固定的分支,而是按手头实际有多少空间来布局。

八、第三步:测试

测试工具有三个:Device Hub 里可以缩放的 iOS 模拟器;macOS 27 上的 iPhone Mirroring,把窗口往两个方向都拉到极限;Xcode 27.1 里的 iPhone Duo 模拟器。苹果明确说过,最后这个最能还原完整的原生体验。文中五张截图,用的就是这个模拟器。

九、上架

这一节在指南里算可选,但有个日期要记住:从 2027 年 4 月起,所有提交的 App 都必须附上 iPhone Duo 截图。

现在提交 iPhone Duo 截图,产品页上就会出现一个徽章,告诉用户这个 App 已经针对 iPhone Duo 做了优化。按原文的说法,触发徽章的条件是提交了截图。所以在适配好之前,截图先别急着交。App Store Connect 里有个新的预览工具,可以先看看素材在 iPhone Duo 产品页上的效果。

想争取编辑推荐,可以在 App Store Connect 里提交 featuring 提名,在 Helpful Details 里写明你的 App 已针对 iPhone Duo 做了优化,包括支持所有设备姿态。这要你主动去争取,没有硬性门槛。

尾声

原生这边分两部分。一部分是清旧账:有些写法一直没出事,全靠设备只有那么几种尺寸,现在要一处处找出来删掉。Xcode 里的 skill 能帮你扫一遍,但扫不干净,剩下的要自己对着清单过。另一部分是学新 API:竖向栏、排列视图、保留区域。只打算升到 27 SDK,可以先不碰;想要铺满全屏的完整体验,界面里又有自定义视图,这一关就绕不过去。

Web 这边,模拟器里看下来比预想的还省事。右边那条区域 Safari 自己占了,左右的 safe-area 用不着操心,上下照旧交给 env()。真要做的就四件事:尺寸别只读一次,100vh 换成 100dvh,桌面导航的断点往下调一调,别拿 screen.width 认设备。一个下午差不多就能改完。真机到手,我再验一遍。

iPhone Duo 10 月 23 日开卖,截图要到明年 4 月才强制提交。

← 返回目录