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