9 сентября Apple показала iPhone Duo — первый складной iPhone. Пользователи обсуждают цену ($1999) и подэкранную камеру, а нам с вами Apple выкатила шесть Tech Talks, страницу HIG и обещание Xcode 27.1 beta до конца месяца. Я просмотрел все шесть докладов и собрал в этой статье то, что придётся сделать почти каждому приложению — с кодом и на Swift, и на Objective-C, потому что UIKit-часть новых API доступна из обоих языков, а legacy-кодовых баз в мире сильно больше, чем хотелось бы верить.
Сразу оговорка про достоверность: устройство выходит 23 октября, Xcode 27.1 на момент написания ещё в статусе «coming later this month». Все сигнатуры ниже — из официальных Tech Talks, но до сверки с заголовками SDK это «форма API», а не гарантия побуквенного написания.
Не новая идиома, а континуум размеров#
Первое, что Apple повторяет почти в каждом докладе: iPhone Duo — это не «новый класс устройств», под который надо писать отдельный интерфейс. Это всё ещё iPhone, просто диапазон доступных размеров стал шире.
Конкретика такая. Внешний дисплей (5.4″) ведёт себя как обычный iPhone: compact ширина, привычные ориентации. Внутренний (7.6″) — уже почти iPad: regular × regular в любой ориентации. И вот деталь, которая сломает больше всего приложений: внутренний дисплей игнорирует supportedInterfaceOrientations. Приложение, залоченное в портрет, на внутреннем экране будет вращаться. Никакого способа это запретить нет.
| Конфигурация | Horizontal | Vertical |
|---|---|---|
| Внешний экран, портрет | compact | regular |
| Внешний экран, ландшафт | compact | compact |
| Внутренний экран, любая ориентация | regular | regular |
Отсюда главное правило: все ветвления по ориентации и по идиоме устройства (userInterfaceIdiom, «если iPad — две колонки») превращаются в дефекты. Ветвиться можно только по size classes:
// SwiftUI
@Environment(\.horizontalSizeClass) private var hSizeClass
var body: some View {
if hSizeClass == .regular {
TwoColumnLayout()
} else {
SingleColumnLayout()
}
}// Objective-C: тот же вопрос через trait collection
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
BOOL isWide = self.traitCollection.horizontalSizeClass ==
UIUserInterfaceSizeClassRegular;
[self applyLayoutForWideMode:isWide];
}Если в кодовой базе есть ветка, смысл которой «если это iPhone Duo» — это сигнал остановиться и переформулировать её в вопрос «сколько места доступно прямо сейчас».
Три SDK-тира: что даёт пересборка#
Поведение приложения на Duo зависит от того, каким SDK оно собрано. Тиров три:
- Pre-iOS 27 SDK. Работает без изменений, но в «письмах»: на закрытом устройстве контент не доходит до статус-бара и камеры, на открытом — привычный размер с полями.
- iOS 27 SDK. Контент расширяется в зону статус-бара на внутреннем дисплее.
- iOS 27.1 SDK (Xcode 27.1). Полный edge-to-edge, системные бары автоматически перестраиваются вертикально, открываются API reserved regions и arrangements.
Пересборка с 27.1 — самое дешёвое и самое результативное изменение из всех возможных. Без неё остальная адаптация просто недоступна.
Охота на зашитые допущения#
Каждый баг фолдабл-порта — это какое-то фиксированное допущение в коде. Вот три, которые стоит грепнуть уже сейчас.
UIScreen.main мёртв. На устройстве с двумя экранами «главный экран» — неоднозначное понятие, и Apple прямо говорит, что API идёт к депрекации. Замены:
// Было
let scale = UIScreen.main.scale
// Стало
let scale = traitCollection.displayScale
// Если экран действительно нужен
let screen = view.window?.windowScene?.screen// Objective-C
CGFloat scale = self.traitCollection.displayScale;
UIScreen *screen = self.view.window.windowScene.screen;Симметричная математика инсетов. Safe area на Duo регулярно асимметрична: вертикальный бар и Dynamic Island сидят с одной стороны, и с какой именно — зависит от позы устройства и от того, в какой половине Split View живёт приложение. Классика вида width - insets.left * 2 теперь считает неправильно:
// ❌ предполагает симметрию
let width = view.bounds.width - view.safeAreaInsets.left * 2
// ✅ каждая грань сама за себя
let width = view.bounds.inset(by: view.safeAreaInsets).width// Objective-C
CGRect contentFrame = UIEdgeInsetsInsetRect(self.view.bounds,
self.view.safeAreaInsets);Хардкод ширин. Таблицы «ширина устройства → layout», брейкпоинты типа width > 390 — всё это разваливается на устройстве, которое меняет ширину движением руки пользователя.
Вертикальные бары: бесплатно, но только для системных контейнеров#
Оба дисплея Duo шире и короче обычного iPhone, поэтому системные контролы переезжают на боковую грань: статус-бар, Dynamic Island (теперь вертикальный), тулбары и таб-бары. Приложение получает это автоматически — при двух условиях: сборка против iOS 27.1 SDK и системные контейнеры баров.
Кастомные UIToolbar, UINavigationBar и UITabBar, добавленные руками во view-иерархию, в вертикальной раскладке не участвуют вообще. Участвуют только UINavigationController, UITabBarController и SwiftUI .toolbar. Если у вас самодельная панель кнопок — это первый кандидат на миграцию.
Из нового API самое полезное:
// SwiftUI: закрепить главное действие и отдать второстепенные в overflow
.toolbar {
ToolbarItem(placement: .topBarPinnedTrailing) {
Button("Post", action: post)
}
ToolbarItem(placement: .cancellationAction) {
Button("Close", action: close)
}
}// Objective-C: та же семантика через UINavigationItem
self.navigationItem.pinnedTrailingGroup =
[UIBarButtonItemGroup fixedGroupWithRepresentativeItem:nil
items:@[postButton]];
// Второстепенные действия — в overflow-меню вертикального бара
self.navigationItem.additionalOverflowItems =
[UIDeferredMenuElement elementWithProvider:^(void (^completion)(NSArray *)) {
completion(@[shareAction, archiveAction]);
}];Вертикальный бар имеет фиксированную ширину, поэтому Apple прямо советует symbol-only кнопки: текстовые лейблы и segmented controls в него не помещаются — такие элементы оставляйте в navigation bar. Отказаться от вертикальной раскладки для конкретного экрана можно (.toolbarVerticalBehavior(.disabled) в SwiftUI, preferredVerticalBarBehavior в UIKit), но это осознанное решение, а не дефолт.
Split View и несколько окон — теперь на iPhone#
Duo — первый iPhone со Split View: два приложения бок о бок 50/50, и участвуют в этом все приложения без какого-либо opt-in. Ваше приложение может в любой момент оказаться в половине экрана — это ещё одна причина, почему фиксированные ширины больше не работают.
Второе новшество — несколько сцен одного приложения, тоже впервые на iPhone. Если вы уже поддерживаете многооконность на iPad (UIApplicationSupportsMultipleScenes в Info.plist, сцены вместо AppDelegate-центричного жизненного цикла), бо́льшая часть работы сделана. Duo-специфичных ключей в Info.plist нет.
Но есть поведение, которого на iPad не было: новые окна создаются только на внутреннем дисплее. На закрытом устройстве запрос новой сцены упадёт, и это надо обрабатывать:
// Objective-C: запрос сцены теперь может не выполниться
UISceneSessionActivationRequest *request =
[UISceneSessionActivationRequest requestWithRole:UIWindowSceneSessionRoleApplication];
[UIApplication.sharedApplication activateSceneSessionForRequest:request
errorHandler:^(NSError *error) {
// Устройство закрыто — новые окна недоступны
[self showSingleWindowFallback];
}];В SwiftUI Apple рекомендует стандартный UIWindowSceneActivation-аффорданс: он сам скрывается, когда создание окна недоступно, и избавляет от ручной обработки половины случаев.
Мой план по чеклисту#
Чтобы это не осталось теорией, вот что я буду делать со своими приложениями (MeteoHealth первым), когда Xcode 27.1 доедет:
- Пересобрать с iOS 27.1 SDK и открыть в симуляторе Duo: Device Hub в Xcode 27.1 умеет открывать, закрывать и сгибать виртуальное устройство. Позозависимые баги чтением кода не находятся.
- Грепнуть допущения:
UIScreen.main, ветки по ориентации и идиоме, симметричные инсеты, хардкод ширин. - Проверить бары: у меня SwiftUI и системные контейнеры, так что вертикальная раскладка должна прийти бесплатно — но порядок кнопок и overflow-приоритеты всё равно смотреть глазами.
- Прогнать оба сценария Split View (приложение слева и справа) и обе половины.
Честный вывод из всех шести докладов: если приложение уже свободно ресайзится — использует size classes, уважает safe area по граням, живёт в системных контейнерах — то iPhone Duo для него почти ничего не меняет. Вся боль достаётся приложениям с зашитыми допущениями. Их время истекает 23 октября.
В следующей статье серии разберу API шарнира — onHingeChange и UIHingeInteraction — и то, почему Apple запрещает строить от угла сгиба layout.



