开发布局iPhone Duo

用 React Native、Flutter 和 Web 开发 iPhone Duo

从 React Native、Flutter、网页和 WebView 的角度看折叠屏:每种技术栈免费得到什么、哪些桥接库能暴露铰链和折痕,以及哪些仍然需要 Xcode。

2026年10月4日阅读约 8 分钟17 个来源

Apple 的讲座以及本站其他指南,说的都是 SwiftUI 和 UIKit。而 App Store 上的大多数 App 两者都不用。如果你的 App 是用 React Native、Flutter 写的,或者是一个网页,又或者是套在原生外壳里的网页,问题都一样:Duo 的哪些行为会自动生效,哪些需要桥接。

简短的回答是:窗口是免费的,折痕不是。窗口归 iOS 管,所以每种技术栈都能看到它调整尺寸、看到安全区域变化。而折痕的位置、铰链角度、侧边栏和 arrangement 都是 UIKit 和 SwiftUI 的 API,自己绘制界面的框架只能通过原生代码拿到它们。本文根据搭建这些桥接库的开发者的说法,介绍每种技术栈目前有什么。

要点

  • 无论用什么技术栈,都要用 Xcode 27.1 构建。如果链接的是 27.0,App 会得到一个带边框的窗口、横向的栏,而且拿不到任何折痕信息。
  • 窗口尺寸和安全区域内边距会通过 UIKit 传给每个框架。每条内边距都要单独读取:侧边栏会让它们不对称。
  • 折痕、铰链和竖向栏的边缘需要桥接。React Native 和 Flutter 各有社区包,全都在模拟器上测试过,还没有一个在真机上测过。
  • 用 JavaScript、Dart 或 HTML 画的栏永远不会移到侧边,只有 UIKit 管的栏才会。
  • Safari 无法告诉网页折痕在哪里。网页看到的只是一次尺寸变化。

每种技术栈都有的,以及谁都没有的

链接 iOS 27 的 App 会变成可调整尺寸的,Apple 的工程师说过这无法关闭。用 27.1 SDK 构建,它还会拿到整块屏幕以及侧边栏对应的内边距。这些都是窗口层面的行为,所以任何读取窗口的框架都能拿到:React Native 里的 useWindowDimensions 和 safe-area context,Flutter 里的 MediaQuery.sizeOf 和 MediaQuery.paddingOf,浏览器里的 resize 事件。Flutter 的 paddingOf 会分别报告左右两侧,这一点在这里很重要,因为合上并竖拿时,模拟器在尾侧边缘报告 84 点,另一侧则是 0。

没有原生代码,任何框架都拿不到的东西:

  • 保留区域,也就是折痕和屏下摄像头的 frame。
  • 铰链的状态和角度。
  • 竖向栏占据了哪一侧边缘。
  • ArrangementView 以及用于外屏的相机配件。
  • 会移动的栏。系统只会移动 UIKit 管理的容器里的导航、工具栏和标签项。由 JavaScript 或 Dart 组件绘制的标签栏,对系统来说只是一堆像素,会一直待在底部。如果你的导航库包装的是真正的 UINavigationController,先在 Duo 模拟器上看看它的标题栏表现如何,再下结论。
React Native Flutter Safari 中的网页 WKWebView 中的网页
在两块屏幕之间调整尺寸 是 是 是 是
按边读取安全区域 safe-area context MediaQuery.paddingOf env(safe-area-inset-*) 配合 viewport-fit=cover 同左
折痕在哪里 需要桥接 需要桥接 不能 原生外壳可以传进来
铰链状态和角度 需要桥接 需要桥接 不能 原生外壳可以传进来
栏移到侧边 仅限 UIKit 管理的栏 否 否 否

React Native

有两个库填补了这个空缺,它们的分工和 Apple 自己的 API 一样:一个读取状态,另一个排布视图。

Gautham Vijayan 的 react-native-nitro-hinge 负责状态。它的 useHinge() hook,以及一个可以在 StyleSheet.create() 中使用的同步 hinge 对象,提供宽度和高度类别、窗口尺寸、按边单独给出的 safeInsets、一个 foldFeatures 数组、一个可为空的 hingeAngle、字体缩放比例和一个 deviceInfo 记录。宽度类别用的是这个库自己的词汇 compact、medium 和 expanded,而不是 UIKit 的两种。辅助函数回答常见问题:isCompactWidth()、hasActiveFold()、isBookPosture()、isTabletopPosture()、contentWidth()。README 在 Xcode 27.1 模拟器上验证了六种 Duo 姿态:合上的手机是紧凑宽度,竖拿时栏在右侧,横拿时在左侧;展开的手机竖拿是 medium,横拿是 expanded。它刻意没有提供 isDuo() 检查。这个库的原则是“问窗口,别问设备”,这也是 Apple 的原则。

要求:React Native 0.75 或更新版本、Nitro Modules;要检测折痕,还需要 iOS 27.1,并在 podspec 中设置 HINGE_IOS_27_1_SDK=1,这样它在旧 SDK 上仍能编译并回退。Android 通过 androidx.window 获得同样结构的数据。目前还不支持 Expo,计划推出一个 config plugin。创意墙上的这个创意有各种姿态的截图。

Thiago Brezinski 的 react-native-arrangement-view 负责布局。带有 ArrangementView.Primary 和 ArrangementView.Secondary 子组件的 <ArrangementView arrangement="split">,在 iOS 27.1 上会交给 SwiftUI 自己的 ArrangementView 处理,所以分割线会像原生版本一样移到折痕处。arrangement="overlay" 让主窗格浮在次窗格上方。axes 把分割限制在一个方向。在任一窗格内,useHingeChange() 返回 { available, angle, status },角度以弧度表示,状态为 unknown、closed、partiallyOpen 或 fullyOpen 之一。在 Android 上,它使用 Jetpack WindowManager 检测折痕,可以配置 hingeGap 和 hingePolicy。低于 iOS 27.1 时,split 模式只显示主窗格。它不会自己添加安全区域内边距,需要开发构建而不是 Expo Go;Thiago 的公告称 0.1.0 版本还不能用于生产环境,先支持 iPhone Duo,Android 随后。这条帖子展示了它在模拟器上的效果。

Thiago Brezinski@thiagobrez

Announcing react-native-arrangement-view 0.1.0 ✨ Easily adapt your React Native app to foldables, arrange views between screens, and fetch hinge state Not prod ready yet, first on iPhone Duo , Android coming next release 🔜 Contributions welcome!… Show more

Expo。 Krystof Woldrich 发帖说,iPhone Duo 即将登陆 Expo 的远程 iOS 模拟器 EAS Simulators,以及用于本地串流的 expo-device-hub 和 serve-sim;Expo 官方账号补充说,编程智能体将无需 Xcode 或 Mac 就能测试 Duo。截至 9 月,EAS Simulator 处于有限开放的预览阶段,可以通过命令行、REST API、浏览器预览或智能体来操作。在 Duo 出现在那里之前,路径和任何原生 App 一样:在装有 Xcode 27.1 的 Mac 上做开发构建。Timeboxd 走的就是这条路。这是一款用 Expo 开发的手表收藏 App,开发者在模拟器里用 Expo 58 beta 跑了一个测试版本:手机展开后,网格从每行两块表变成六块。

Flutter

Flutter 会正确地调整尺寸和报告安全区域,因为两者都来自窗口。它不报告的是折痕。在 Android 折叠屏上用来传递铰链信息的 MediaQuery.displayFeatures,在 iPhone Duo 上是一个空列表。Duo 发布当天,Flutter 仓库里就有人提交了一份提案,打算用 iOS 27.1 的保留区域 API 来填充它:把 .division 区域作为一个带边界和平放或半开姿态的 fold 特性,把屏下摄像头作为一个 cutout。它目前处于开放状态,标记为 P2,有一个相关的拉取请求,截至撰写本文时尚未发布。在那之前,TwoPane 以及所有读取 displayFeatures 的组件都看不到铰链。

Flutter 自己绘制像素,还带来另外两个后果。你用 AppBar、BottomNavigationBar 或 NavigationBar 做的任何东西都不会移到侧边栏,所以如果想在合上的手机上有竖向栏,就得自己画。另外,SystemChrome.setPreferredOrientations 的表现和 Apple 描述的一样:锁定在外屏上有效,内屏仍然可以旋转。

有四个包填补了这个空缺,全都只在模拟器上测试过:

包 为 Flutter 提供什么 说明
medialyra.com 的 foldable hingeAngleStream 和 hingeStatusStream、保留区域、尺寸类别、一个 FoldAwareBuilder 以及 DuoMediaQuery 辅助工具 仅限 iOS。在运行时解析 API,所以在旧 SDK 上也能编译。除非你开启桥接,否则不会写入 displayFeatures
Code Growers 的 dual_screen_hinge 铰链角度和姿态流、iOS 27.1 上的 reservedRegions,以及 Android 的后屏和双屏会话 把更新合并到大约每帧一次。它的 README 建议用 displayFeatures 做基本布局,用它来获取连续角度
szymdzie 的 iphone_duo_ui_pro 一个包含尺寸类别、区域、铰链和竖向栏边缘的 DuoEnvironment,以及基于它的组件:FoldAwareTwoPane、FoldAlignedGrid、一个会绘制竖向栏的 DuoAdaptiveScaffold,还有把折痕重新发布为标准 DisplayFeature 的 DuoDisplayFeatures 需要 UIScene 生命周期;在每台构建机器都装上 Xcode 27.1 之前,Podfile 中需要设置 DUO_SDK_27_1 标志
shreeman.dev 的 nitro_fold_duo 折痕和摄像头的几何信息、铰链状态和角度、竖向栏边缘和圆角内边距,通过 Nitro FFI 桥接而不是 method channel 0.0.1 版本。在 iOS 27.1 以下报告 isSupported == false

选一个,并遵守原生开发的规则:根据尺寸和尺寸类别决定布局,只用区域来把控件挪离折痕。如果你已经有为 Android 写的、能识别 displayFeatures 的代码,iphone_duo_ui_pro 的 DuoDisplayFeatures 是最务实的桥接,因为它能让那些代码原封不动地工作。

这件事不一定要花很久。LeanCode 说,他们只用一天就让自家的 Flutter Tech Summit 大会 App 适配了 iPhone Duo:展开后,日程就在你选中的演讲旁边。

Web

iPhone Duo 上的 Safari 没有实现针对折叠屏的两项 Web 标准。CSS 视口分段(Viewport Segments)和设备姿态 API(Device Posture API)已在 Chrome 和 Edge 138 中推出,而 WebKit 两者都没有采纳,相关的标准立场议题仍未关闭。所以网页无法询问折痕在哪里,也无法知道手机是否半开。它能得到的,是一个在会话中途改变尺寸、但没有旋转的视口,以及尺寸改变时的一个 resize 事件。

以分屏浏览展开的 iPhone Duo:左边是一个介绍编织工作坊的 Safari 网页,右边是 Siri App 里的对话
分屏浏览中,Safari 在展开手机的左半边。对网页来说,折叠、展开或分屏都只是一次尺寸变化。图片:Apple

合上时的视口与外屏一致,约 466 × 678 CSS 像素,设备像素比为 3。对于展开的手机,第三方设备数据库列出的是约 890 × 626,而 Marker.io 和 Keishin Nishiura 都说这个数字是估算的,没有得到 Apple 的确认。自己测一下:在 Duo 模拟器里运行 Safari,在 Web 检查器里读取 innerWidth。不管具体是多少,展开后的页面都又宽又接近正方形,宽高比约为 1.4,而普通 iPhone 是 2.2,而且系统的侧边栏会占据其中一侧边缘。

该怎么应对,综合 Ingenious Techlab 和 Nishiura 的文章:

  • 用容器查询而不是窗口宽度来布局,并检查那些假定手机是长条形的断点。
  • 用 100dvh,不要用 100vh。
  • 添加 viewport-fit=cover,分别读取 env(safe-area-inset-left) 和 env(safe-area-inset-right)。它们是不同的。
  • 不要嗅探 user agent。Duo 报告自己是 iPhone。
  • 在宽视口上,把需要点中的控件放在中间那条带状区域之外,因为你无法知道它是否正好落在折痕上。
  • 测试时,在响应式设计模式里于合上和展开的宽度之间来回拖动,不要重新加载,因为折叠对你的页面做的就是这件事。

WKWebView 里的网页(Capacitor App 或 App 内浏览器就是这种情况)看到的和 Safari 完全一样,外加原生外壳告诉它的任何信息。velhobit 在模拟器上做的实验就是这样:SwiftUI 读取分隔区域、它是否生效、它的 frame 和中心点,然后把这些信息传进页面,页面再围绕它们布局。如果你的产品是原生外壳里的一个 Web 视图,那么折痕就住在那个外壳里。

游戏引擎及其他

截至撰写本文时,我们没有找到 Unity、Godot 或 JetBrains(针对 Compose Multiplatform)发布的 Duo 专属指南。这三者都渲染到同一个视图里,所以它们能拿到尺寸变化,其他什么也拿不到。像它们的 Android 指南对待折叠那样,把折叠当作一次可能在一帧中途发生的窗口尺寸变化,并保证游戏在两种尺寸下都能玩。任何需要铰链角度的功能都需要原生插件,而铰链指南里的检测器可以移植到任何语言。

像测试原生 App 一样测试

这些都不能让你免去 Xcode 27.1 和 iPhone Duo 模拟器。无论用什么构建 App,测试流程都是模拟器检查清单:每种姿态、做事做到一半时折叠、分屏浏览的两侧、每条安全区域边缘单独检查。有两个工具对任何技术栈都有帮助。Artem Novichkov 的 hinge 命令可以从脚本设置模拟器的角度,让测试运行时能折叠手机。Riley Brown 给编程智能体的提示词(安装 Xcode 27.1 测试版、设置 iPhone Duo 模拟器、构建并启动 App),对 React Native 或 Flutter 项目和对 Swift 项目一样有效。

Riley Brown@rileybrown

Wow… you can now vibe code apps for the iPhone Duo with GPT-6 Sol and Opus 5.5! When building with Codex or Claude Code, simply ask: “Install Xcode 27.1 beta, set up the iPhone Duo simulator, then build and launch my app so I can see how it looks.”

最后一步是任何框架都无法跳过的。本文提到的每个包,在 README 里都说了同样的话:在模拟器上测试过,没在手机上测试过。把真机测试排进 10 月 23 日的日程。

继续阅读

App · 规格阅读约 3 分钟

iPhone Duo 能运行 iPad App 吗?iPhone Duo 与 iPad mini

iPhone Duo 的内屏几乎有 iPad mini 那么大,但它运行的是 iOS,不是 iPadOS。这对仅限 iPad 的 App 意味着什么、为什么很多 Duo App 看起来会像它们的 iPad 版,以及 Duo 从 iPad 借来了什么。

10月6日阅读指南