
SwiftUI 如何适配 iPhoneDuo?回顾设计与开发遇到的那些坑
前些日子忙活适配iPhoneDuo了。主力虽然是vibecoding,但是遇到的坑简直不要太多。主要是“洪绘敲木鱼”这个应用,因为本身软件是SwiftUI编写的,所以原生sdk适配起来也不难。
主要需要适配的就是iPhoneDuo的多种屏幕形态情况。
iPhoneDuo在内屏下分成了三个角度,在半折叠下分成了两个角度,在展开情况下分成了两个角度。
于是,你需要适配7个不同的情况(我的天呐)
先来看看我适配的情况吧。可以为大家设计与开发作为一个参考。
外屏设计情况
外屏涉及到了三个尺寸,非常的复杂,因为是异形屏,所以所有尺寸都需要单独适配。

主要的难点就是在于将摄像头所在的纵向安全区进行信息填充,避免该区域浪费。
内屏设计情况
还好内屏是一个屏下摄像头,不然就得适配四种了。得益于这个屏下摄像头,我们可以只需要两种布局即可解决,类似iPad适配。但是与iPad不同的是,我们需要尽可能利用可折叠的能力,以及避免半折叠中间区域无法点击的情况。
这里推荐使用分栏设计,并且根据开合情况进行比例适配。
在半折叠横屏的时候,我做了分栏设计。左右各占一半。

但是在纵向的时候,就需要类似笔记本的操作感觉了。底部是操作区域,并且尽可能避免用户点击处于直立的上半部分屏幕。另外,最好保证在折叠时和展开时,内屏的内容与外屏右侧一致,这样可以保证体验的一致性。所以我选择了左侧选择音效,右侧和外屏一样的操作样式。
对于洪绘敲木鱼来说,敲击是最基本的操作,所以为了保证体验,在底部增加了一个额外的敲击按钮。

在全部展开时,适配就可以参考iPad了。值得一提的是,如果你使用了分栏布局,在横向的时候,分栏左侧为了视觉效果更加紧凑,应该减小横向比例,可以减少到三分之一或者根据设计情况来。

开发过程的坑
最开始全折叠情况vibecoding自然去识别摄像头方向或者屏幕方向了,这都错了。苹果提供了官方的sdk,用来识别屏幕情况,所以一定要用。
苹果在 iPhone Duo 的适配文档里写得很直接:不要根据设备方向、摄像头位置或者屏幕尺寸去猜当前姿态。方向描述的是画面怎么转,形态描述的是设备怎么折,这两件事在 Duo 上经常对不上。合上、展开、半折、立在桌上,横竖都能成立。
官方给的接口是 onHingeChange(SwiftUI)和 UIHingeInteraction(UIKit)。回调报三档状态:closed、partiallyOpen、fullyOpen,同时带一个连续角度。DeviceHinge 的文档原话是,角度是铰链当前的角度,状态由系统根据当前角度和设备方向算出来。也就是说三档不用你自己拿角度去切:closed 是合上,partiallyOpen 是中间那段,fullyOpen 是开到设备允许的最大角度。判断的第一步是看上下文在不在,普通 iPhone 上没有铰链,context.hinge 是 nil。官方特意提醒过,不要按系统版本或者机型去推断设备形态。
1 | enum DuoPosture { |
还有一条容易被忽略的规定:铰链数据只用来驱动交互,不要拿它算布局。布局交给尺寸类、reservedRegions 和 arrangement。角度告诉你设备正在怎么动,不告诉你内容该放哪。半折时两块屏各占多少,直接读 reservedRegions(kind: .division),不要自己拿角度反推屏幕尺寸。
1 | @available(iOS 27.1, *) |
横屏时折痕是竖的,读 midX;竖屏时折痕是横的,读 midY。拿到的比例直接交给分屏容器,两块屏的大小就有了,不需要再碰角度。
合盖横屏时,外屏前摄是在左上角还是右上角,同样不用猜。苹果把外屏前摄作为 occlusion 保留区一直报出来,读它的 frame 就行。ReservedRegion.frame 用的是当前 view 的坐标,(0, 0) 是左上角,而且 frame 已经包含 margins,不用自己再加。
1 | @available(iOS 27.1, *) |
注意,系统有时会把状态栏、工具栏、灵动岛一起报进 occlusion,整条竖栏的 midY 会落在中间,不能直接当摄像头位置。木鱼里这块没有只读一次就完事,DuoLayout.swift 的 DuoClosedMetrics.resolve 先取所有 occlusion frame,再按“窄且贴边”筛出摄像头那一列:
1 | let column = proxy.reservedRegions(kind: .occlusion) |
左右判断就是这一列的中线:
1 | let edge: HorizontalEdge |
读不到保留区时,先看左右安全区哪边更宽,再退到 traitCollection.verticalBarEdge,最后兜底右侧。
上下要麻烦一点。系统可能把整条竖栏报上来,这时 midY 是屏幕中心,不能当摄像头位置。所以只有高度小于屏幕一半的区域,才按摄像头本体读:
1 | let camera = column.flatMap { |
兜底规则是:摄像头列在右侧,竖屏在上、横屏在下;列在左侧,一律按上处理。合盖横屏时摄像头到底在左上还是右上,还是保留区说了算,代码不猜方向。
还有一道跟摄像头无关但必须加的门:usesDuoSideLayout 要求铰链是 .closed,并且 windowLayout.needsSideDock 为真。折叠到 55° 左右铰链就会先报 closed,这时界面还在内屏上;只看铰链会把切换栏贴到内屏最右边。这个坑是实测出来的。
另外,UIScreen.main 在双屏设备上没有明确含义,官方也说这个用法以后会废弃。尺寸从窗口场景或者 GeometryProxy 拿,不要缓存一块屏的宽度去推另一块屏。
苹果没有公布三档状态各自对应的角度区间,也不保证角度更新的频率和精度。所以七种姿态只能建在这三档之上:先认 status,再按 angle.degrees 细分,阈值是你自己定的产品规则,不是官方保证。判断入口仍然只能有一个,别在 view 里到处写 if。
设计屏幕截图
在适配完成后需要上传到App Store。我们可以看到iPhone Duo的尺寸推荐为:
App 预览支持的尺寸包括 1920 × 886px 和 886 × 1920px。App 截屏支持的尺寸包括 2034 × 1398px、1398 × 2034px、2853 × 2007px 和 2007 × 2853px。

我这里建议用2853 × 2007px 和 2007 × 2853px。最开始我用了最前面的值,才发现是外屏小尺寸。。。
我认为体验最好的预览尺寸是内屏,所以我建议用 2853 × 2007px 横屏,当然也可以用竖屏形式,具体由你自己去把握。
我就直接续用之前的风格和素材了,调整尺寸和位置做了一版。

总结
总的来说适配起来主要是需要适配的屏幕情况太多了,在早期应用适配的时候,我们只需要考虑纵向的高度以及旋转,在iPad时期我们可能需要稍微考虑下横屏的分栏和信息密度。但是在iPhoneduo足有7种屏幕形态需要单独适配,这非常考验设计团队和开发的经验和能力。
iPhoneDuo适配难度并不算简单,特别如果是大厂应用有大量界面、活动营销页面需要设计和适配,所以iPhoneDuo用户也不用着急,适配肯定会慢慢跟上的。


























