iPhone Duo, первый складной iPhone от Apple, поступает в продажу 23 октября и представляет собой первый по-настоящему новый форм-фактор iPhone за последние годы. У него есть компактный внешний дисплей для использования в сложенном виде и большой внутренний дисплей, который открывается как книга, с шарниром, который люди держат под любым углом, ставят на стол или складывают плашмя.
Expo SDK 58 создан для iOS 27 и iPhone Duo с первого дня, и с помощью всего нескольких изменений ваше приложение будет чувствовать себя как дома на обоих дисплеях и в любом положении.
Что изменилось?
iPhone Duo добавляет в iOS три основных концепции макета:
- Вертикальная навигация в большинстве положений устройства. iOS перемещает системную строку состояния, панель вкладок и элементы управления панели инструментов в вертикальный рельс на одном из краев, применяя это, когда устройство находится в книжной ориентации. В других положениях iOS сохраняет горизонтальные панели.
- Новые размеры и ориентации. iPhone Duo можно открывать, частично открывать и поворачивать, что требует динамического изменения размера вашего приложения. Пользователи также могут разместить ваше приложение рядом с другим в режиме Split View.
- Разделенные макеты для организации контента в стиле iPad в развернутом виде. вы можете выбрать адаптацию панели навигации к макету боковой панели с помощью preferredPlacement или разделить контент на два окна с помощью <ArrangementView>.
Куда деваются системные панели: рельс на правом крае в сложенном виде, на внешнем крае каждого окна в Split View и снова горизонтальные панели на открытом дисплее в портретной ориентации.
Хорошая новость заключается в том, что если вы создавали свое приложение с помощью Expo Router и <NativeTabs>, многие из этих вопросов решаются за вас с помощью нативного навигатора. Оставшаяся работа заключается в проверке ваших экранов, применении дополнительных безопасных зон и принятии решений для внутреннего макета вашего приложения.
Кроме того, iOS 27.1 предоставляет новые API для шарнира устройства, рельса и зарезервированных областей, которые доступны в SDK 58 и подробно описаны далее в этом руководстве.
Требования
Сначала выполните сборку с помощью Xcode 27.1. В нем поставляется SDK iOS 27.1, который необходим для работы с API шарнира и зарезервированных областей, а также среда выполнения симулятора iOS 27.1 с симулятором iPhone Duo, чтобы вы могли тестировать без iPhone Duo. Xcode 27 также заменил Simulator.app на Device Hub (Xcode > Open Developer Tool > Device Hub), где симулятор складывается, а нажатие i в npx expo start запускает его и переводит в фокус, как и раньше. В EAS Build используйте sdk-58 или последний образ. Приложение, собранное с помощью Xcode 26 или более ранней версии, запускается, но iOS удерживает его вне зоны досягаемости рельса под строкой состояния и камерой, поэтому оно не занимает весь дисплей (Подготовка вашего приложения для iPhone Duo).
Общие рекомендации
Рекомендации Apple для iPhone Duo задают тон для всего, что описано ниже: вы по-прежнему проектируете для iPhone. Размещайте элементы в пространстве, доступном вашему представлению, рассматривайте сложенный дисплей как имеющий компактную ширину, а открытый дисплей как имеющий обычную ширину, позволяйте макету расширяться, а не переключать дизайн, и сохраняйте те же функции и состояние на обоих дисплеях. В приложении Expo это сводится к нескольким выборам:
- Позвольте iOS отрисовывать навигацию. Stack и NativeTabs в Expo Router, а также представления SwiftUI через <Host> в Expo UI используют собственные системные панели, которые iOS размещает для вас на обоих дисплеях, включая рельс. Заголовок или панель вкладок, которые вы рисуете на JavaScript, останутся вне рельса. См. раздел Expo Router.
- Выстраивайте макет от безопасной зоны. <SafeAreaView> из react-native-safe-area-context покрывает 99% случаев. На iPhone Duo рельс представляет собой горизонтальный отступ, слева в левом окне Split View, поэтому добавляйте отступы с обеих сторон и позволяйте flex занимать ширину. См. раздел Безопасные зоны.
- Позвольте макету расширяться и оставьте сгиб на усмотрение системы. Предупреждения, листы и системные разделенные представления удаляются от сгиба самостоятельно, а разделенное представление или ArrangementView предоставляют приложению со списком и деталями двухпанельный макет в открытом виде. Зарезервированные области и API шарнира предназначены для определенных макетов и эффектов, а не для повседневных экранов. См. раздел Дополнительные API.
- Проверяйте каждое положение. Складывайте, раскладывайте, поворачивайте и открывайте режим Split View на симуляторе iPhone Duo и проверяйте, что экран остается открытым и сохраняет свое состояние при складывании устройства. См. раздел Тестирование.
Expo Router
Начните с навигации, потому что единственное решение, которое имеет наибольшее значение на iPhone Duo, — это то, будет ли система рисовать ваши панели.
Рекомендуется: использовать нативные панели навигации
На iPhone Duo панель навигации, панель вкладок и панель инструментов не закреплены сверху и снизу. iOS перемещает их в рельс, когда устройство сложено или открыто в ландшафтной ориентации, возвращает к горизонтальным панелям в портретной ориентации и на внешний край каждого окна в режиме Split View, анимируя каждое изменение и заново размещая элементы панели по мере движения: сначала кнопку «Назад», а остальные в меню переполнения, когда они не помещаются. Панели, принадлежащие UIKit, получают все это бесплатно. Заголовок или панель вкладок, отрисованные на JavaScript, не получают ничего из этого: они остаются там, где вы их нарисовали, в то время как рельс занимает их место рядом с ними, и чтобы соответствовать системе, вам нужно самостоятельно считывать край, дублировать правила элементов и перемещать вашу панель при каждом складывании, повороте и изменении режима Split View. Вам будет непросто, и рекомендация Apple заключается в том, чтобы позволить системе размещать панели (Вертикальные элементы управления).
Та же нативная панель инструментов и панель вкладок на открытом дисплее в ландшафтной (слева) и портретной (справа) ориентации. Система перемещает их между рельсом и горизонтальными панелями при повороте; панели, отрисованные на JavaScript, остаются на месте.
Поэтому позвольте iOS отрисовывать навигацию. Это требует меньше всего работы и дает наилучший результат на iPhone Duo: Stack и NativeTabs в Expo Router, а также представления SwiftUI через <Host> в Expo UI используют собственные панели навигации и вкладок системы, которые iOS размещает для вас на обоих дисплеях, включая рельс. Держите пользовательский хром внутри области контента. Заголовок или панель вкладок, написанные на JavaScript, — это совсем другая история: ваше приложение их рисует, поэтому удержание их вне рельса — тоже ваша задача. Это по-прежнему работает, но требует больше внимания, и пользовательский заголовок может по крайней мере определить, на каком крае находится рельс, что охватывает раздел Чтение края рельса. Остальная часть этого раздела охватывает оба варианта.
expo-router 58.0.20 и более поздние версии также по умолчанию используют нативный внешний вид заголовка iOS на iOS 26 и более поздних версиях (#51450). Чтобы сохранить прежний внешний вид, вызовите enableLegacyHeaderAppearance(). NativeTabs перенесены в expo-router/native-tabs в SDK 58, поэтому обновите все импорты из expo-router/unstable-native-tabs.
Кнопки заголовка в рельсе
Рельс упорядочивает элементы заголовка и панели инструментов в один столбец: сначала «Назад» или «Закрыть», затем важные действия, а остальные вытесняет в меню переполнения снизу вверх. Элементу нужен значок, чтобы вообще попасть в рельс: элементы только с текстом остаются на горизонтальной панели, а пользовательские представления остаются за бортом (Организация элементов на ваших панелях). Поэтому предоставьте каждой кнопке заголовка символ SF Symbol через Stack.Toolbar.Button вместо пользовательского компонента headerRight, оставьте текстовую метку в качестве дочернего элемента для доступности и меню и сгруппируйте второстепенные действия в разделе Stack.Toolbar.Menu с системным значком переполнения в виде точек в круге:
Элементы панели инструментов с символами SF Symbols располагаются в рельсе под кнопкой «Назад» на сложенном дисплее.
Панели вкладок JavaScript
Панель вкладок, которую отрисовывает ваше приложение, остается на месте. Компонент Tabs сохраняет свою панель внизу и добавляет к элементам отступы на размер большего горизонтального встэка с обеих сторон. Начиная с expo-router 58.0.25, TopTabs размещает вкладки внутри горизонтальных встэков, а фон простирается до краев, в то время как обычный компонент <TabList> из expo-router/ui добавляет к контенту отступы только на величину перекрываемых горизонтальных встэков. С использованием asChild ваш собственный компонент берет управление на себя, поэтому оберните триггеры в <SafeAreaView edges={['left', 'right', 'bottom']}> или добавьте отступы через insets.left и insets.right.
Компонент NativeTabs получает рельс без дополнительного кода. Какой бы навигатор вы ни использовали, каждый экран по-прежнему удерживает собственный контент на безопасном расстоянии от рельса, о чем рассказывается в следующем разделе.
Разделенные представления
Компонент SplitView в Expo Router обертывает системное разделенное представление. Apple рекомендует использовать разделенное представление при достаточной горизонтальной ширине и отмечает, что оно может содержать две панели, как Почта, или три, как Keynote (Split views). На устройстве Duo разделенное представление показывает одну колонку в сложенном состоянии и колонки бок о бок в разложенном. Панель навигации и колонки списков являются представлениями в одном файле макета, а маршруты заполняют колонку деталей.
Системное разделенное представление в сложенном, частично открытом и открытом состояниях. Колонки располагаются по обе стороны от сгиба, а открытое сообщение остается открытым во всех положениях. Компонент SplitView является экспериментальным в SDK 58.
Боковая панель или панель вкладок на открытом дисплее
На открытом дисплее в альбомной ориентации iOS 27 может отображать NativeTabs в качестве панели вкладок в рельсе или в виде боковой панели — такой же выбор предлагает iPad. Свойство preferredPlacement выбирает вариант, когда может отображаться только один из них: 'sidebar', 'tabBar' или 'automatic', что оставляет выбор за системой. Установка этого свойства также включает sidebarAdaptable. Боковая панель поставляется с кнопкой в заголовке, которая скрывает и показывает ее, а в сложенном состоянии, где ширина компактна, панель вкладок отображает то, что вы запросите.
NativeTabs на открытом дисплее со свойством preferredPlacement, установленным в 'sidebar' (слева) и 'tabBar' (справа).
Безопасные зоны
<SafeAreaView> из react-native-safe-area-context является вашим основным API для 99% случаев. Большинство приложений считывают из него только верхний и нижний отступы. На iPhone Duo рельс представляет собой горизонтальный отступ. В сложенном или открытом состоянии приложение Expo Router в симуляторе сообщает о тех же четырех отступах, и меняется только окно:
Строка состояния находится в рельсе, поэтому insets.top равен 0. Макет, который добавляет отступы только через insets.top или использует <SafeAreaView edges={['top']}>, отображается под рельсом.
Применяйте левый и правый отступы везде, где вы применяете верхний, и указывайте их в каждом <SafeAreaView edges>:
Легко упустить из виду:
- Внутри прокручиваемых представлений: горизонтальная карусель или строка на всю ширину внутри полноразмерного скролла требует собственных горизонтальных отступов на концах.
- Абсолютно позиционированные представления: плавающие кнопки и всплывающие уведомления (тосты) требуют использования right: insets.right + 16 вместо right: 16.
- Размер окна: он изменяется по мере складывания устройства и в режиме Split View, где левое окно имеет ширину 469pt. Считывайте значение useWindowDimensions во время рендеринга или положитесь на flex; значение, закэшированное при показе тоста, устаревает после увеличения окна.
- Языки с письмом справа налево: рельс остается на физическом крае с камерой, поэтому считывайте как левый, так и правый отступы, а не предполагайте наличие замыкающего края.
- Панель вкладок: внутри экрана NativeTabs insets.bottom включает панель вкладок только тогда, когда она проходит вдоль низа, поэтому не добавляйте к нему фиксированную высоту панели вкладок.
Split View на открытом дисплее — это случай, который следует протестировать намеренно. Каждое приложение получает рельс на своем внешнем краю, поэтому в левом окне рельс находится слева: insets.left равен 84, insets.right равен 0, окно имеет размер 469 × 669, а useVerticalBarEdgeIOS() возвращает 'leading'. Макет, который добавляет отступы только к правому краю или предполагает, что рельс всегда находится на замыкающем краю, здесь окажется под рельсом.
То же почтовое приложение в левом окне режима Split View. Рельс перемещается к левому краю, а левый отступ занимает 84pt.
Пространство рядом с рельсом асимметрично по замыслу. Добавляйте отступы к контейнеру контента на основе отступов, а не к корневому представлению экрана: фоновое изображение или шапка могут идти от края до края под рельсом, в то время как прокручиваемый контент остается внутри безопасной зоны, если там нет интерактивных элементов (Vertical controls).
Строка состояния
Свойство style компонента <StatusBar> по-прежнему переключает часы в рельсе между светлым и темным режимами, но свойство hidden не имеет эффекта: часы остаются, а отступы не меняются. Чтобы строка состояния и системные панели оставались горизонтальными на экране, см. раздел «Отключение рельса».
Дополнительные API
В предыдущих разделах описано то, с чем сталкивается каждое приложение. Эти API используются для реализации специфических макетов и эффектов.
Зарезервированные области
Отступы безопасной зоны описывают края. Сгиб и камеры находятся внутри представления, поэтому Apple описывает их как зарезервированные области: фрейм, от которого следует держаться подальше, с флагом активности. Разделитель делит представление, а затемнение (окклюзия) закрывает его часть. У iPhone Duo их три: сгиб, активный только тогда, когда устройство частично открыто; внешняя камера, активная всегда, вокруг которой компонуется рельс; и внутренняя камера, активная только во время использования камеры.
Зарезервированные области на открытом дисплее, затененные поверх почтового приложения: сгиб и внутренняя камера неактивны, пока устройство лежит плашмя, а камера выключена, в то время как область строки состояния активна и расширилась, чтобы охватить Dynamic Island во время воспроизведения аудио.
Система справляется с большей частью задач: оповещения, меню и листы самостоятельно смещаются со сгиба, а системное разделенное представление сужает свои колонки вокруг него. Для вашего собственного макета Apple просит использовать контейнер, который адаптируется самостоятельно, и четное число колонок сетки, чтобы сгиб приходился между ними. Один из случаев, с которым нужно справиться самостоятельно: экран, который остается одной панелью при открытии устройства, должен располагаться на правой половине, той, которая выравнивается с внешним дисплеем, чтобы то, на что смотрел пользователь, оставалось на месте по мере раскладывания (Arrange views in different poses). В iOS 27.1 RC UIKit размещает его слева.
react-native-reserved-regions, от разработчиков react-native-safe-area-context, передает информацию об областях в JavaScript в сборке для разработки, созданной с помощью Xcode 27.1. Оберните интересующую вас область в ReservedRegionsProvider и вызовите useReservedRegions() внутри нее. Каждая область имеет тип (разделитель или окклюзия) и фрейм в точках относительно провайдера с учетом полей, поэтому верстка до значения frame.x позволяет избежать попадания на сгиб:
Он сообщает геометрию и ничего не перемещает, возвращает [] до первого измерения, а версия 0.2.0 сообщает об активных областях только на iOS, поэтому сгиб появляется, пока устройство частично открыто.
Шарнир
expo-hinge сообщает угол шарнира и его состояние — closed (закрыт), partiallyOpen (частично открыт), fullyOpen (полностью открыт) или unknown (неизвестно) — на iOS 27.1 и новее, а также на складных устройствах с Android 11 и новее, и null во всех остальных случаях. Используйте его для взаимодействий и эффектов, таких как затвор камеры, который перемещается, когда устройство стоит наполовину открытым на столе, но не для верстки: состояние меняется раньше, чем интерфейс системы, поэтому макет, привязанный к нему, сместится преждевременно и в неверном направлении при раскладывании устройства. Размер окна, безопасные зоны и зарезервированные области предназначены для верстки.
useHinge() повторно рендерится при каждом изменении. За пределами React используйте getHinge() и addHingeListener(). На Android состояние никогда не закрыто, поэтому проверяйте угол, близкий к 0 (Состояние на Android).
Угол шарнира и состояние из expo-hinge на частично открытом дисплее с компонентом ArrangementView, размещающим панель с каждой стороны сгиба.
Для представления SwiftUI в Expo UI модификатор onHingeChange вызывает ваш обработчик со старым и новым контекстом шарнира в потоке пользовательского интерфейса, когда обработчик получен из useWorkletCallback. Для макета SwiftUI, адаптирующегося к сгибу, ArrangementView размещает основное и дополнительное представления бок о бок с помощью arrangementViewStyle('split') или накладывает их с помощью 'overlay', что перемещает одно представление на каждую сторону сгиба, пока устройство частично открыто. Правила Apple для этого: держите навигацию вне его и никогда не помещайте его внутрь списка, прокручиваемого представления или разделенного представления (Представления расположения (Arrangement views)).
Чтение края панели управления
Два дополнения к expo-status-bar в SDK 58 охватывают то, что система оставляет на ваше усмотрение: заголовок, который вы рисуете сами, и экран, для которого горизонтальные панели подходят лучше. Оба требуют iOS 27.1 и сборки с Xcode 27.1, и ничего не делают в других условиях.
Отступы безопасной зоны показывают, сколько места занимает панель, а не на какой стороне находятся элементы управления. useVerticalBarEdgeIOS() возвращает 'leading' или 'trailing' и выполняет повторный рендеринг при повороте устройства или перемещении приложения в режиме Split View на другую сторону. Он равен null везде, где система не рисует панель: открытый дисплей в портретной ориентации, другие iPhone и iOS до версии 27.1. Это значение является трейтом (trait) UIKit, поэтому пользовательский заголовок может размещать свои кнопки вдоль того же края, который использует система:
Вне компонента StatusBar.getVerticalBarEdgeIOS() возвращает одно и то же значение единожды. Как и трейт, оба продолжают сообщать о крае, пока ваше приложение отключило панель, поэтому проверяйте отступы, если вам нужно узнать, присутствует ли панель.
Отключение панели
<StatusBar verticalBarBehaviorIOS="disabled" /> оставляет строку состояния и системные панели горизонтальными во время ее монтажа (макет, который есть у всех других iPhone), а панель возвращается при ее размонтировании. iOS анимирует изменение и добавляет или удаляет отступ безопасной зоны панели вместе с ней. Apple резервирует это для экранов, для которых горизонтальные панели подходят лучше, таких как полноэкранный видеоплеер с элементами управления или фиксированный макет, например калькулятор, и просит делать это стабильным выбором для экрана, а не чем-то, что переключается с состоянием представления (preferredVerticalBarBehavior):
StatusBar.setVerticalBarBehaviorIOS('disabled') — это императивная форма, а 'automatic' восстанавливает значение по умолчанию.
Это макет, который использует Safari на открытом дисплее с элементами управления вверху и внизу. На симуляторе отключение панели возвращает строку состояния и заголовок наверх, а панель вкладок вниз, как в сложенном, так и в открытом состоянии, и безопасная зона следует за ними. Открытый дисплей в портретной ориентации всегда находится в этом состоянии независимо от того, что запрашивает приложение, поэтому экран, обрабатывающий отключение панели, справился и с портретным режимом.
Открытый дисплей с панелью (слева) и с отключенной вертикальной панелью (справа).
Ограничения
- Блокировки ориентации являются предпочтением в iOS 27. Везде, где приложение для iPhone может быть изменено в размерах (в функции «Дублирование экрана iPhone» (iPhone Mirroring) или на iPad), система рассматривает поддерживаемую ориентацию как предпочтение, в вызове lockAsync может быть отказано, а requireInfo больше не отключает ваше приложение от этого режима (Блокировки ориентации в iOS 27 и более поздних версиях). На самом iPhone Duo, по словам Apple, вы можете заблокировать портретную или ландшафтную ориентацию при условии заполнения экрана по мере изменения положения устройства (Рекомендации (Best practices)).
- Панели вкладок JavaScript могут отрисовываться под панелью. Верхние вкладки JavaScript в Expo Router и TabList из expo-router/ui не добавляют отступы для горизонтальной безопасной зоны до версии expo-router 58.0.25, которая исправляет обе проблемы (#51814, #51813). Обновитесь до нее, оберните пользовательские панели в SafeAreaView с левым и правым краями или используйте NativeTabs.
- Split View и изменения положения устройства — это задача вашего макета. SDK 58 заставляет пакеты Expo подбирать размеры на основе окна и предоставляет ArrangementView и expo-hinge, но он не компонует экраны за вас.
- Симулятор iPhone Duo имеет пробелы. В примечаниях к выпуску Xcode 27.1 указано, что StandBy и большинство расширений приложений недоступны в среде выполнения симулятора iPhone Duo, поэтому проверяйте виджеты и другие расширения на реальном устройстве.
Тестирование
Тестируйте на симуляторе iPhone Duo в Xcode 27.1 (в сложенном и открытом состоянии, а также в каждой ориентации, поддерживаемой вашим приложением). Центр устройств (Device Hub) складывает симулятор под любым углом и показывает дисплей, используемый устройством. Пройдите каждый экран в каждом положении и поверните устройство в каждом положении:
- Сложено: ничего важного не находится под панелью, и каждая кнопка реагирует на нажатие. Кнопки заголовка, которые вы ожидаете увидеть на панели, находятся там, а остальные — в меню переполнения.
- Открыто: макет не переключается на другой дизайн, и ничто не работает под панелью. В портретной ориентации панели возвращаются вверх и вниз, а макет по-прежнему сохраняется.
- Частично открыто: ничто из того, что вам нужно прочитать или нажать, не находится в области сгиба, а листы (sheets) и поповеры попадают на одну его сторону.
- Split View: когда ваше приложение находится в каждом из окон на открытом дисплее, контент не заходит на панель, которая располагается на левом краю левого окна.
- Складывание с открытым экраном: экран остается открытым в той же позиции прокрутки, с тем же состоянием и теми же действиями на обоих дисплеях.
- Стандартный iPhone: ничего не меняется.
Другие работы с iOS 27 в SDK 58
- App Intents. expo-app-intents предоставляет доступ к действиям и контенту вашего приложения для Siri, Команд (Shortcuts), Spotlight и Apple Intelligence. Библиотека находится на стадии альфа-тестирования. Некоторые схемы требуют более новую версию iOS, чем их домен, а .mail.openDraft требует iOS 27. В посте о релизе SDK 58 это подробно описано.
- Центр устройств (Device Hub). Expo CLI запускает симуляторы через Центр устройств Xcode 27, а expo-device-hub предоставляет для них панель управления в браузере (Поддержка Центра устройств для Xcode 27).
Дополнительное чтение
Мы рекомендуем направить агента по кодированию на этот пост в блоге, попросить его открыть PR с исправлениями отступов и запустить сборку на симуляторе iPhone Duo через Expo CICD Workflows перед слиянием. Дайте нам знать, как все прошло. Разумеется, подключение Expo MCP также будет полезно. И вот еще несколько полезных ресурсов для разработки и проектирования под iPhone Duo.
- Проектирование для iPhone Duo в Руководстве по интерфейсу (Human Interface Guidelines), описывающее правила дизайна, лежащие в основе этого руководства: панель, зарезервированные области, представления расположения и положения устройства.
- Подготовка приложения для iPhone Duo, обзор для разработчиков от Apple, включая правила отображения элементов панели.
- Технические доклады Apple: Подготовьте приложение для iPhone Duo, Поднимите планку с iPhone Duo, Примите позу с адаптивными макетами на iPhone Duo и Дизайн для iPhone Duo.
- Документация Expo: Безопасные зоны (Safe areas), Нативные вкладки (Native tabs), Панель инструментов стека (Stack toolbar), expo-hinge и ArrangementView.











