用 React Native、Flutter 和 Web 開發 iPhone Duo
從 React Native、Flutter、網頁和 WebView 的角度看摺疊螢幕:每種技術棧免費得到什麼、哪些橋接庫能暴露轉軸和摺痕,以及哪些仍然需要 Xcode。
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 隨後。這條貼文展示了它在模擬器上的效果。
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 事件。

合上時的視口與外螢幕一致,約 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 專案一樣有效。
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 日的日程。
繼續閱讀

iPhone Duo App 創意:開發者正在用摺疊螢幕做的 6 件事
300 多個 iPhone Duo 示範、概念和原型,歸納為反覆出現的六種模式,從半折的筆記型電腦形態到把轉軸當控制器。如果你正在考慮做點什麼,可以從這裡開始。

iPhone Duo 能執行 iPad App 嗎?iPhone Duo 與 iPad mini
iPhone Duo 的內螢幕幾乎有 iPad mini 那麼大,但它執行的是 iOS,不是 iPadOS。這對僅限 iPad 的 App 意味著什麼、為什麼很多 Duo App 看起來會像它們的 iPad 版,以及 Duo 從 iPad 借來了什麼。

iPhone Duo 遊戲:即將到來的作品,以及摺疊螢幕給遊戲帶來了什麼
哪些遊戲已在 iPhone Duo 上亮相、未更新的遊戲在展開的螢幕上是什麼樣子,以及開發者正在用轉軸、半折和兩塊螢幕做出哪些新玩法。