При создании представлений 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, таких как влияние на компоновку и многословность, обеспечивая переиспользуемый и декларативный подход.