При создании представлений SwiftUI, которые должны адаптироваться к размерам контейнера, многие разработчики обращаются к GeometryReader. Это стандартное решение, но оно имеет свои издержки: влияя на иерархию представлений, может усложнить логику макета. В этой статье я покажу, как создать правильный модификатор с помощью onGeometryChange и contentMargins — двух мощных API iOS 17+, которые позволяют отслеживать изменения геометрии без обертывания представлений в GeometryReader. Примечание: onGeometryChange вышел с iOS 17 SDK, но был портирован на iOS 16. Проблема: скрытые затраты GeometryReader Допустим, вам надо центрировать контент по горизонтали с ограничением максимальной ширины. Это распространенная задача для макетов iPad или дизайнов для широких экранов. Традиционный подход выглядит примерно так: var body: some View { GeometryReader { geometry in ScrollView { LazyVStack { // Контент } .frame(maxWidth: 768) } .contentMargins( .horizontal, max(0, (geometry.size.width - 768) / 2), for: .scrollContent ) } } Это работает, но GeometryReader имеет несколько недостатков: влияет на компоновку: занимает все доступное пространство, что может нарушить иерархию представлений; приводит к многословности: требуется оборачивать всю структуру представлений; не подлежит переиспользованию: этот паттерн приходится повторять во множестве представлений. Решение: правильный ViewModifier Вместо оборачивания представлений в GeometryReader, можно создать переиспользуемый модификатор, который применяет onGeometryChange для отслеживания изменений размера без влияния на компоновку. Вот как это сделать: import SwiftUI struct MaxWidthContentMargins: ViewModifier { @State private var containerWidth: CGFloat = 0 func body(content: Content) -> some View { content .onGeometryChange( for: CGFloat.self, of: { geometry in geometry.size.width } ) { width in containerWidth = width } .contentMargins( .horizontal, max(0, (containerWidth - .editorMaxColumnWidth) / 2), for: .scrollContent ) } } extension View { func maxWidthContentMargins() -> some View { modifier(MaxWidthContentMargins()) } } Как это работает 1. onGeometryChange: отслеживание без влияния Модификатор onGeometryChange позволяет отслеживать изменения геометрии без обертывания представления в GeometryReader. Он имеет три параметра: for: тип отслеживаемого значения (в данном случае CGFloat для ширины); of: замыкание, которое извлекает значение из GeometryProxy; action: замыкание, которое выполняется при изменении геометрии. .onGeometryChange( for: CGFloat.self, of: { geometry in geometry.size.width } ) { width in containerWidth = width } Вот ключевое отличие: onGeometryChange отслеживает геометрию, не влияя на иерархию представлений. Ваши представления остаются чистыми и неизмененными. 2. contentMargins: точное позиционирование контента Модификатор contentMargins позволяет регулировать отступы вокруг прокручиваемого контента, не затрагивая индикаторы прокрутки. Это идеально подходит для центрирования контента при сохранении индикаторов прокрутки у краев экрана. При этом чистое пространство, которое стало отступами, остается прокручиваемым и интерактивным. .contentMargins( .horizontal, max(0, (containerWidth - .editorMaxColumnWidth) / 2), for: .scrollContent ) Данное вычисление max(0, (containerWidth — .editorMaxColumnWidth) / 2) обеспечивает: на широких экранах: центрирование Content с равными отступами с обеих сторон; на узких экранах: отсутствие дополнительных отступов (функция max(0, …) предотвращает появление отрицательных значений) Применение модификатора Теперь применение модификатора становится предельно простым: struct LibraryView: View { var body: some View { ScrollView { LazyVStack { // Контент } .frame(maxWidth: .editorMaxColumnWidth) } .maxWidthContentMargins() } } Никаких оберток GeometryReader. Никаких повторяющихся геометрических вычислений. Всего лишь чистый, декларативный модификатор. Читайте также: Как вернуть контроль над состоянием данных с RemoteResult Apple убивает Swift 7 лучших ресурсов для iOS-разработчиков в 2025 году Читайте нас в Telegram, VK и Дзен Перевод статьи Thomas Ricouard: Beyond GeometryReader: Building Better SwiftUI Modifiers with onGeometryChange ============== В статье рассматривается альтернатива использованию GeometryReader для адаптации представлений SwiftUI к размерам контейнера. Предлагается использование модификатора `MaxWidthContentMargins` на основе API `onGeometryChange` и `contentMargins`. Модификатор `onGeometryChange` позволяет отслеживать изменения геометрии без влияния на иерархию представлений, что упрощает компоновку. Модификатор `contentMargins` обеспечивает точное позиционирование контента, сохраняя интерактивность индикаторов прокрутки. Использование модификатора позволяет избежать проблем, связанных с GeometryReader, таких как влияние на компоновку и многословность, обеспечивая переиспользуемый и декларативный подход.