iPhone Duo의 Expo
출처: https://expo.dev/blog/supporting-iphone-duo (Expo SDK 58). 디자인 규칙은 apple-design-duo에 있습니다. 이 스킬은 Expo 및 React Native 구현 계층입니다. CI 파이프라인 배경: https://docs.expo.dev/tutorial/cicd/introduction/ 및 expo:eas-workflows 스킬을 참조하세요.
요구 사항
- Expo SDK 58 (iOS 27 대상) 및 Xcode 27.1 (iOS 27.1 런타임 및 iPhone Duo 시뮬레이터).
- EAS Build 이미지:
sdk-58또는latest. - Xcode 27은 Simulator.app을 Device Hub로 대체합니다 (Xcode > Open Developer Tool > Device Hub). 접히고 회전하며 힌지 각도를 설정할 수 있는 3D Duo가 있습니다.
정신적 모델
접힘 = 컴팩트 너비, 펼침 = 일반 너비. 레이아웃이 넓어지도록 하고, 디자인을 전환하지 마십시오. 양쪽에서 동일한 기능과 상태를 유지합니다. 책과 같은 자세에서는 iOS가 상태 표시줄, 탭 표시줄 및 툴바를 한쪽 가장자리의 수직 레일로 이동합니다.
네 가지 규칙
- iOS가 네비게이션을 그리도록 하십시오. Expo Router의
Stack및NativeTabs를 사용하십시오. 사용자 정의 JS 헤더와<TabList>는 레일을 인식하지 못하며 (SDK 58은 최소한의 호환성만 추가),<TabList>는 레일에 들어갈 수 없습니다. - 모든 화면에
SafeAreaView(react-native-safe-area-context) 를 사용하고 수평 인셋을 포함하십시오. - 모든 자세를 테스트하십시오. 접기, 펴기, 회전, Split View.
- 필요한 곳에서만 조정하십시오:
ArrangementView,preferredPlacement, 예약 영역, 힌지 API 또는 레일 비활성화.
세이프 에어리어 (대부분의 버그가 발생하는 곳)
- 레일은 왼쪽이나 오른쪽에 84pt 인셋이 필요합니다.
- 스크롤 콘텐츠를
SafeAreaView edges={['left','right']}로 감싸고,ScrollView를 감싸지 마십시오. 그래야 배경이 가장자리까지 이어집니다. ScrollView에서contentInsetAdjustmentBehavior="automatic"을 설정하십시오 (RN 패치 #59194는 이 설정만으로 충분하도록 해야 합니다). - 수평 캐러셀: 자체적으로 끝 패딩을 추가합니다.
- 플로팅 버튼 및 토스트:
right: insets.right + 16. RTL에서는left와right를 모두 읽습니다. NativeTabs내부에서insets.bottom은 탭 표시줄이 하단에 있을 때만 포함됩니다. 고정 탭 표시줄 높이를 추가하지 마십시오.- 루트 뷰가 아닌 콘텐츠 컨테이너에 인셋으로 패딩을 주십시오.
- 렌더링 시
useWindowDimensions를 읽으십시오. 왼쪽 Split View 창은 469pt 너비입니다.
Expo Router
- 레일의 헤더 버튼은 한 열로 쌓이며, 뒤로 또는 닫기가 먼저입니다. 텍스트 전용 항목은 수평을 유지합니다. 사용자 정의 뷰는 제외됩니다.
- Dynamic Island의 Live Activity는 툴바를 오버플로로 축소할 수 있습니다. 이에 대응하지 마십시오.
<SplitView>는 시스템 분할 뷰를 감쌉니다: 접힘 시 한 열, 펼침 시 두 열, 접힘 인식. SDK 58에서 실험적입니다.NativeTabspreferredPlacement:'sidebar' | 'tabBar' | 'automatic'. 가로 방향에서 열린 디스플레이에 적용됩니다.
추가 API
| 필요 사항 | API |
|---|---|
| 두 개의 창 콘텐츠 (iOS 27.1 전용, 이전 버전은 컨테이너 없이 자식을 렌더링) | @expo/ui/swift-ui의 ArrangementView, Host 내부에서 ArrangementView.Primary 및 .Secondary 사용. arrangementViewStyle('split' | 'overlay'), splitArrangementLayoutSize, overlayArrangementEdge로 스타일 지정. @expo/ui/swift-ui/modifiers의 modifier. |
| 접힘 및 가려짐 프레임 | npx expo install react-native-reserved-regions, 이후 useReservedRegions()는 포인트 단위로 { kind: 'division' | 'occlusion', frame }을 반환. 사용자 정의 게임 컨트롤 및 균일 열 그리드에 적합. 접힘 부분에서 콘텐츠를 분할하는 데 사용하지 말고 ArrangementView를 사용하십시오. |
| 힌지 각도 및 상태 | npx expo install expo-hinge. useHinge(), getHinge(), addHingeListener(). 상태: closed, partiallyOpen, fullyOpen, unknown. SwiftUI modifier onHingeChange는 useWorkletCallback을 통해 UI 스레드에서 실행 가능. Reanimated 및 Android 11+ 폴더블에서 작동. |
| 레일 가장자리 | useVerticalBarEdgeIOS()는 'leading', 'trailing' 또는 null을 반환. Duo 감지기가 아닙니다 (열린 세로 방향은 null 반환). |
| 레일 비활성화 | <StatusBar verticalBarBehaviorIOS="disabled" /> (preferredVerticalBarBehavior에 매핑). 비디오 플레이어나 계산기 스타일 화면에만 사용하고, 화면별로 선택을 안정적으로 유지하십시오. |
방향
iOS 27에서 방향 고정은 크기 조정 가능한 컨텍스트에서 선호도입니다. lockAsync는 거부될 수 있으며 requireFullScreen은 더 이상 옵트아웃하지 않습니다. Duo에서 세로 또는 가로로 고정하는 것은 앱이 자세 변경을 통해 화면을 채울 때만 작동합니다.
알려진 제한 사항
- Duo 시뮬레이터에서는 StandBy를 사용할 수 없으며, Xcode 27.1 시뮬레이터에서는 대부분의 앱 확장을 사용할 수 없습니다. 위젯 및 확장은 기기에서 테스트하십시오.
expo-app-intents(알파, SDK 58에도 있음)는 Siri, Shortcuts 및 Spotlight에 작업을 노출합니다. 일부 스키마는 해당 도메인보다 최신 iOS가 필요합니다 (.mail.openDraft는 iOS 27 필요). 레이아웃과 관련이 없습니다.
테스트 체크리스트
- 접힘 상태: 레일 아래에 중요한 것이 없고, 버튼이 응답하는지 확인.
- 펼침 상태: 레이아웃이 디자인을 전환하지 않고, 레일 아래에 아무것도 없으며, 세로 막대가 상단과 하단으로 돌아오는지 확인.
- 부분적으로 열림: 접힘 부분에 필수적인 것이 없고, 시트와 팝오버가 한쪽에 위치하는지 확인.
- Split View: 콘텐츠가 레일 (왼쪽 창의 왼쪽 가장자리)에서 깨끗한지 확인.
- 화면 중간에서 접기: 스크롤 위치, 상태 및 작업이 유지되는지 확인.
- 표준 iPhone: 아무것도 변경되지 않는지 확인.
감사 워크플로우
- SDK 58 확인 및 재빌드.
- 사용자 정의 헤더 또는 탭 표시줄 (
headerShown: false및 수동 빌드된 바,TabList) 검색하여Stack및NativeTabs로 이동. - 하드코딩된
top,bottom,left,right오프셋, 고정 탭 표시줄 높이 및 루트 수준 패딩 검색. 인셋으로 대체. - 세이프 에어리어 목록에 따라 캐러셀, FAB 및 토스트 확인.
- 두 개의 창 화면 선택:
SplitView또는ArrangementView. - 사용자 정의 레이아웃이 실제로 필요한 경우에만 화면별로 예약 영역 또는 힌지를 결정.
- Device Hub에서 확인한 후 CI에서 확인 (아래 참조).
CI: Duo 시뮬레이터에서 실행
expo:eas-workflows 스킬을 사용하십시오 (먼저 라이브 스키마를 가져오고, 작업 매개변수를 추측하지 마십시오). CI/CD 튜토리얼의 적절한 형태: sdk-58 또는 latest 이미지에서 PR 트리거 시뮬레이터 빌드, 선택적 Maestro E2E, 그런 다음 핑거프린트 기반으로 네이티브 빌드와 프로덕션용 EAS Update 중 선택. npx -y eas-cli@latest workflow:validate .eas/workflows/<file>.yml --non-interactive로 검증. 워크플로우는 유료 EAS 분을 사용합니다.
일반적인 실수
| 실수 | 수정 |
|---|---|
| 인셋으로 ScrollView 패딩 처리 | 주변에 SafeAreaView, contentInsetAdjustmentBehavior="automatic" |
| 패딩에 고정 탭 표시줄 높이 사용 | 세이프 에어리어의 insets.bottom 사용 |
Duo 감지를 위해 useVerticalBarEdgeIOS() 사용 | 레일 가장자리일 뿐이며, 열린 세로 방향에서는 null |
| 예약 영역으로 접힘 부분에서 콘텐츠 분할 | ArrangementView 또는 SplitView |
| 수동 빌드된 JS 헤더 또는 탭 표시줄 | Stack 및 NativeTabs |
right: 16에 플로팅 버튼 | right: insets.right + 16 |
lockAsync가 항상 성공한다고 가정 | 선호도로 처리하고 화면 채우기 |
| 표준 iPhone에서만 테스트 | Device Hub, 모든 자세 |