Данные о размере окна теперь нельзя трактовать как данные о физическом экране: Apple отдельно показывает переход от глобальных экранных предположений к контексту текущей сцены в сессии WWDC26 № 278. Поэтому замена UIScreen.main в iOS 27 не бывает универсальной: свойства дисплея берите из текущего UIWindowScene, локальную вёрстку — из view.bounds или контейнера, масштаб и size class — из текущего trait-контекста, а effectiveGeometry оставляйте для операций уровня сцены.
Симптом → самый быстрый способ исправить:
UIScreen.main.boundsзадаёт неверный breakpoint → замените его размером фактического контейнера.UIScreen.main.scaleдаёт неожиданный результат в зеркалировании → читайтеdisplayScaleиз trait-контекста представления.- код получает экран из фонового сервиса → передайте в него контекст сцены или view, а не глобальный singleton.
Кому нужен этот разбор
Материал рассчитан на UIKit-разработчиков, которые поддерживают старый код с UIScreen.main, экранными bounds и проверками ориентации. Он также полезен архитекторам, которым нужно закрепить одну стратегию для команды, и ответственным за QA и CI, проверяющим изменяемые окна в Xcode 27, iPhone Mirroring и на iPad.
Последнее обновление — 26 августа 2026 года. Технические выводы сверены с сессией Apple Developer WWDC26 и документацией UIKit; поведение и доступность API следует повторно проверить после выхода финальных документов iOS 27 и Xcode 27.
Сначала определите, какие данные действительно нужны коду
Большинство спорных замен возникает потому, что один старый вызов обслуживает разные задачи. Перед рефакторингом разнесите обращения к экрану по смыслу:
- Свойства дисплея. Реальный экран, к которому относится сцена, его параметры и контекст вывода.
- Геометрия текущей сцены. Размер и положение окна, которыми управляет
UIWindowScene. - Доступное место для конкретного интерфейса. Bounds представления, родительского контейнера или области контента.
- Среда адаптации.
displayScale, size class и другие признаки текущего trait-контекста.
Это не четыре варианта одного и того же API. Они описывают разные уровни системы. Если функция вычисляет ширину карточки, ей не нужен физический экран. Если менеджер сцены запрашивает геометрию окна, ему недостаточно view.bounds. Если рендерер выбирает масштаб, он должен знать среду представления, а не экран, который когда-то оказался главным.
| Что нужно получить | Предпочтительный источник | Где применять | Главный риск неправильной замены |
|---|---|---|---|
| Свойства дисплея | view.window?.windowScene?.screen |
Работа с конкретным экранным контекстом | Выбор чужой сцены или отсутствие окна |
| Размер локального интерфейса | view.bounds |
Layout, жесты, отрисовка, координаты | Подмена локального пространства экраном |
| Размер родительского контейнера | Bounds фактического контейнера | Панели, split view, ячейки, дочерние контроллеры | Breakpoint рассчитан по внешней области |
| Геометрия сцены | UIWindowScene.effectiveGeometry |
Управление окном на уровне сцены | Использование scene API в обычной вёрстке |
| Масштаб и size class | Trait-контекст view или контроллера | Рендеринг и адаптация интерфейса | Глобальный масштаб не соответствует текущему окну |
Такой подход даёт команде критерий ревью: после замены разработчик должен объяснить, к какому scene, window, view или trait относится каждое измерение.
Сценарий 1: экранный объект против контекста текущего окна
UIScreen.main удобен короткой записью, но именно это и создаёт ложное ощущение универсальности. В приложении с несколькими сценами «главной» может оказаться не та поверхность, где сейчас находится ваш контроллер. Ситуация усложняется внешним дисплеем и зеркалированием: физический экран, окно приложения и видимая область представления перестают быть взаимозаменяемыми.
Apple помечает доступ к UIScreen.main как устаревающий API. Это не означает, что любой вызов нужно механически переписать на другой глобальный объект. Рекомендация означает смену точки входа: сначала найдите объект, который владеет нужным контекстом.
Для кода, уже связанного с интерфейсом, безопаснее двигаться по цепочке:
guard
let scene = view.window?.windowScene,
let screen = scene.screen
else {
return
}
Здесь есть важная граница. Пока view не добавлено в window, у него нет надёжной цепочки до сцены. Попытка получить экран в init, раннем loadView или из независимого utility-сервиса может вернуть nil либо заставить разработчика снова использовать глобальный fallback.
Разделите варианты по качеству:
Плюсы контекстного доступа:
- сцена соответствует текущему окну;
- код лучше работает при нескольких окнах;
- источник данных виден в сигнатуре или жизненном цикле объекта;
- проще тестировать разные window-сценарии.
Минусы:
- контекст может быть недоступен до подключения view;
- сервисам нужно явно передавать scene, window или trait;
- старые статические хелперы придётся изменить, а не только переименовать.
Сервис, которому нужен именно экран, лучше сделать зависимым от переданного UIWindowScene или протокола-поставщика. Не передавайте в него UIScreen.main ради сохранения старой сигнатуры: это оставляет архитектурную ошибку скрытой.
Размер окна и размер интерфейса — разные метрики
В изменяемом окне устройство больше не является надёжным источником ширины приложения. iPhone Mirroring дополнительно показывает эту границу: изображение устройства выводится на Mac, но layout приложения должен реагировать на доступное пространство окна, а не на габариты панели Mac. Условия работы функции Apple описывает в официальной инструкции по iPhone Mirroring.
На iPad та же ошибка проявляется при изменении размера окна. Экран устройства может быть широким, но конкретный контроллер занимать только часть split view. Поэтому UIScreen.main.bounds.width способен выбрать «планшетный» layout для узкой панели.
| Проверяемая задача | Что измерять | Почему это корректнее |
|---|---|---|
| Разместить дочерние элементы | view.bounds |
Это фактическая локальная система координат view |
| Выбрать число колонок внутри панели | Bounds панели или коллекции | Решение зависит от доступного контейнера |
| Рассчитать область сцены | effectiveGeometry |
Геометрия относится к управляемому окну сцены |
| Проверить safe area | view.safeAreaInsets и bounds view |
Экранные bounds не описывают внутренние отступы |
| Обработать касание или жест | Координаты конкретного view | Событие приходит в локальном пространстве интерфейса |
Документация UIView.bounds описывает bounds как прямоугольник собственной системы координат представления. Это и есть нужный источник для большинства layout-решений.
UIWindowScene.effectiveGeometry имеет другой уровень ответственности. Используйте документацию Apple по effectiveGeometry, когда сцена управляет геометрией окна или когда вам нужно реагировать на параметры самой сцены. Не переносите это свойство в каждый компонент: view не должно знать о геометрии всего окна, если ему нужна только собственная ширина.
Небольшой пример неправильного предположения
Плохая логика:
let width = UIScreen.main.bounds.width
if width > 700 {
showWideLayout()
}
Она смешивает физический экран, сцену и локальный контейнер.
Более точная логика:
let width = view.bounds.width
if width > wideLayoutThreshold {
showWideLayout()
} else {
showCompactLayout()
}
Сам порог должен быть частью дизайн-контракта компонента, а не скрытой копией размера конкретного устройства. Если представление ещё не вошло в иерархию, дождитесь layout pass или передайте ему измерение от владельца контейнера.
Масштаб, trait и ориентация: три разных решения
В старых проектах часто встречается связка: получить UIScreen.main.scale, проверить ширину экрана, затем определить ориентацию и выбрать layout. В iOS 27 такую цепочку следует разобрать на отдельные вопросы.
Для масштаба интерфейса нужен текущий trait-контекст. Apple рекомендует реагировать на изменения trait среды, а не хранить один глобальный снимок; это изложено в руководстве по адаптации при изменении traits.
Пример для компонента, который рисует содержимое:
let scale = view.traitCollection.displayScale
В контроллере применяйте trait того объекта, чью среду вы реально обслуживаете. Это особенно важно при iPhone Mirroring: масштаб вывода и ширина окна на Mac не должны заставлять рендерер принимать решение по глобальному экрану.
Где подходы расходятся
screen.scale— выбор, когда бизнес-логике действительно нужно свойство дисплея текущей сцены.traitCollection.displayScale— выбор для отрисовки и адаптации конкретного view или контроллера.- Size class — сигнал об адаптивной среде, но не точная ширина и не идентификатор устройства.
- Ориентация — состояние интерфейса, но не универсальный аргумент для выбора «широкого» или «узкого» layout.
Вертикальная ориентация не гарантирует узкое окно. В изменяемой сцене пользователь может получить компактную или расширенную область независимо от физического положения устройства. Поэтому условие «если portrait, показать мобильную версию» нужно заменить на проверку доступного пространства либо на trait, если именно trait является частью согласованного контракта компонента.
Матрица миграции по смыслу вызова
Не все места в проекте требуют одинакового объёма работы. Полезно распределить найденные вызовы по четырём уровням.
Можно заменить локально. Это обращения, где код уже находится в view или контроллере и измеряет собственный layout. Обычно экранные bounds заменяются на view.bounds, а экранный масштаб — на traitCollection.displayScale.
Нужно поднять на уровень сцены. Так работают менеджеры окон, scene delegate и координаторы, которым нужна геометрия UIWindowScene. Здесь не следует протаскивать случайный view только ради получения размера.
Нужно опустить на уровень view. Сервисы, которые сейчас получают экран глобально, но на деле считают размеры кнопок, координаты жестов или область отрисовки, должны получать контекст компонента. Это изменение ответственности, а не косметическая замена API.
Нужно перепроектировать. Сюда относятся проверки вроде «устройство планшетное», «экран шире фиксированного числа» и «portrait означает компактный режим». Такие условия зашивают предположение об одном окне. Их следует заменить на capability, trait, размер контейнера или явное состояние интерфейса.
Для совместимости со старыми версиями не создавайте новую глобальную оболочку с названием вроде AppScreen. Это лишь перенесёт проблему. Лучше сделать небольшой адаптер с явным контекстом:
protocol DisplayContextProviding {
func displayScale(for view: UIView) -> CGFloat
func sceneScreen(for scene: UIWindowScene) -> UIScreen?
}
Конкретная реализация может учитывать доступность API, но вызывающий код должен сообщать, что именно он хочет получить. Так постепенная миграция не превращается в набор разрозненных решений.
Первый этап проверки: найдите скрытую зависимость
Перед изменением кода проведите поиск не только по строке UIScreen.main. Проверьте также:
UIScreen.screens;screen.bounds;screen.nativeBounds;screen.scale;- проверки
UIDevice.current.orientation; - фиксированные ширины, выведенные из моделей устройств;
- кэши изображений, зависящие от масштаба;
- преобразования координат через экранные bounds;
- статические helper-функции без параметров контекста.
Затем для каждого вызова запишите три поля: какие данные нужны, на каком уровне они живут, когда этот уровень гарантированно доступен. Если разработчик не может заполнить хотя бы одно поле, вызов нельзя считать готовым к замене.
Второй этап: зафиксируйте правила в код-ревью
Внутреннее правило команды может быть коротким:
- layout-компонента не читает
UIScreen.main.bounds; - scene coordinator не использует случайный
view.boundsдля управления окном; - рендеринг получает scale из актуального trait;
- экран запрашивается только при доказанной потребности в свойствах дисплея;
- фоновые службы не извлекают UI-контекст через глобальный singleton;
- каждый fallback имеет объяснение совместимости и срок удаления.
При проверке pull request просите не «заменить deprecated API», а показать семантическую цепочку. Например: «эта ширина относится к collection view внутри панели, поэтому читается из bounds collection view». Такая формулировка намного надёжнее автоматической замены символов.
Третий этап: соберите проверочную матрицу
Одной проверки на рабочем iPhone недостаточно. Для изменяемых окон вам нужны разные отношения между устройством, сценой и контейнером.
Минимальный набор сценариев:
- свободно изменить размер окна в Xcode 27;
- открыть приложение через iPhone Mirroring;
- проверить iPad с изменяемым окном;
- пройти основной сценарий на физическом устройстве;
- повторить запуск после отсоединения и повторного подключения сцены.
В каждом сценарии фиксируйте не только скриншот. Сравнивайте:
- ширину и высоту ключевых контейнеров;
- текущий
displayScale; - size class и другие traits;
- safe area;
- координаты касания и преобразования жестов;
- размеры bitmap- и текстовых кэшей;
- состояние после изменения размера окна;
- поведение при повторном подключении сцены.
Если визуально всё выглядит нормально, но кэш создаётся по UIScreen.main.scale, ошибка может появиться только на изображениях, рендеринге Metal или PDF. Если layout не деформируется, это ещё не доказывает правильность координат интерактивных элементов.
Для команды, которая уже распределяет сборки и регрессию между несколькими Mac, полезно заранее описать среду в консоли MacHTML, а правила передачи доступа и проверки рабочего состояния сверить со справочным разделом MacHTML. Это не заменяет тестовую матрицу: удалённая машина помогает увеличить число параллельных прогонов, но не исправляет неверно выбранный API.
FAQ: короткие решения для спорных случаев
Чем заменить UIScreen.main в iOS 27?
Не ищите один эквивалент. Для дисплейных свойств используйте экран из текущего UIWindowScene; для локальной вёрстки — bounds соответствующего view или контейнера; для масштаба и size class — traits. effectiveGeometry оставляйте сцене. Если старый вызов делает сразу несколько вещей, сначала разделите его на независимые операции.
Когда выбирать effectiveGeometry вместо view.bounds?
view.bounds отвечает на вопрос «какое пространство доступно этому представлению». effectiveGeometry отвечает на вопрос «какова эффективная геометрия сцены». Компонентам интерфейса почти всегда нужен первый вариант. Второй нужен координатору или scene delegate, когда решение относится ко всему окну. Подмена приводит к breakpoint, рассчитанному по неправильному уровню.
Где проверять displayScale при iPhone Mirroring?
Начинайте с traitCollection.displayScale текущего view или контроллера. Так рендеринг получает масштаб среды, в которой реально находится интерфейс. UIWindowScene.screen используйте только для кода, которому нужны свойства дисплея. Не переносите экранный scale в условие выбора ширины: масштаб и доступная геометрия отвечают на разные вопросы.
Можно ли оставить старый вызов на время миграции?
Да, но только как явно изолированный legacy-слой. Сначала пометьте его владельца, допустимый сценарий и условие удаления. В layout, расчёте жестов и выборе breakpoint временное сохранение опасно: приложение продолжит проверять не то пространство. Для журналирования или редкого дисплейного сценария оболочка допустима, если получает сцену явно.
Как проверить результат в Xcode 27?
Проверьте несколько размеров окна, затем повторите те же действия в iPhone Mirroring и на iPad. Для каждого запуска сохраните значения bounds контейнера, traits, displayScale и координат интерактивных областей. Отдельно измените размер уже работающей сцены: ошибка жизненного цикла часто не проявляется при холодном запуске. После этого включите сценарий в CI.
Четвёртый этап: оцените стоимость ошибки, а не только объём правки
Быстрая замена может выглядеть дешевле, но цена дефекта распределяется между несколькими слоями:
- layout получает ширину не своего контейнера;
- графика создаёт кэш под чужой scale;
- координаты жестов расходятся с визуальными bounds;
- многосценный режим воспроизводит состояние только у части пользователей;
- QA не может стабильно повторить дефект на одной конфигурации;
- архитектура постепенно получает несколько несовместимых способов «узнать размер экрана».
Если проект небольшой и почти весь UI уже находится в контроллерах, миграция на локальные bounds обычно ограничивается изменением зависимостей. Если в коде есть глобальные менеджеры, статические кэши и бизнес-правила на основе ориентации, сначала понадобится перепроектирование границ ответственности.
Не пытайтесь закрыть весь объём одной заменой перед ближайшей сборкой. Разделите изменения по семантике: сначала исправьте layout и coordinate space, затем scale и traits, после этого — scene-level управление. Такой порядок снижает риск, что новый источник данных будет выбран правильно для одной задачи и неправильно для другой.
Что выбрать: быстрый патч или контекстную миграцию
Быстрый патч оправдан, если вызов находится в изолированном компоненте, view уже подключено к окну, а код действительно использует локальную геометрию. В этом случае можно заменить источник и сразу добавить тест на изменение bounds.
Контекстная миграция обязательна, если вызов находится в:
- singleton-сервисе;
- кэше изображений или шрифтов;
- scene coordinator;
- общем модуле, используемом несколькими окнами;
- логике, которая одновременно учитывает ориентацию, ширину и масштаб;
- коде, работающем до появления первого окна.
Плюсы явного контекста:
- меньше скрытых зависимостей;
- понятнее тестовые фикстуры;
- корректнее работа нескольких сцен;
- проще отличить размер view от геометрии окна.
Минусы:
- изменяются сигнатуры;
- нужно учитывать отсутствие window на ранних этапах;
- возрастает объём тестов;
- legacy-совместимость требует отдельного слоя.
Ваш критерий готовности прост: после ревью любой вызов должен отвечать, чьё пространство он измеряет. Если ответ звучит как «главный экран приложения», но конкретные scene, window или view не названы, миграция ещё не закончена.
Если текущая схема держится на одном Mac и последовательной регрессии, она ограничивает проверку именно там, где iOS 27 вводит изменяемую геометрию: нельзя одновременно сравнить Xcode 27, iPhone Mirroring и iPad-сценарий, а повтор дефекта зависит от занятости среды. Аренда MacHTML имеет смысл как временное расширение для параллельных сборок и возвратных прогонов, когда нужно быстро добавить тестовый контур, не покупая отдельное устройство и не превращая рабочую станцию разработчика в общий CI-узел. При этом для постоянной тяжёлой нагрузки или тестов, требующих физического интерфейса, собственный Mac и локальное оборудование остаются более предсказуемым выбором. Начните с матрицы данных и только затем определяйте, сколько параллельных сред действительно необходимо.
Проверьте миграцию UIKit на удалённом Mac
Арендуйте Mac в MacHTML, чтобы тестировать изменяемые окна и сценарии с UIWindowScene в реальной среде. Используйте удалённый доступ для проверки view.bounds, effectiveGeometry и trait-контекста при разных конфигурациях дисплея. Подключайтесь к Mac через консоль MacHTML и выполняйте сборку и отладку без дополнительного локального оборудования. Выберите подходящий тариф MacHTML для проверки совместимости проекта и подготовки к iOS 27.