Основы протокола Layout
Реализуйте sizeThatFits и placeSubviews.
«Основы протокола Layout» — бесплатный урок Swift Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Swift Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Swift Academy содержит 4 уроков всего.
Зачем нужна пользовательская компоновка
SwiftUI предоставляет HStack, VStack и ZStack, но иногда требуется логика размещения, которую они не могут выразить: радиальные меню, потоковые компоновки, мозаичные сетки.
Протокол компоновки позволяет написать такую логику и при этом по-прежнему сочетать её с обычными представлениями SwiftUI.
Протокол компоновки
Layout требует двух методов: sizeThatFits (каков мой размер?) и placeSubviews (куда поместить каждого дочернего элемента?).
Реализуйте это соответствие для типа-значения и используйте его как любой встроенный стек.
struct MyLayout: Layout {
func sizeThatFits(proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()) -> CGSize { .zero }
func placeSubviews(in bounds: CGRect,
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()) { }
}Использование пользовательской компоновки
Значение Layout выступает в роли контейнера. Передавайте дочерние представления в его замыкании в конце вызова точно так же, как в стеке.
var body: some View {
MyLayout {
Text("One")
Text("Two")
Text("Three")
}
}ProposedViewSize
Родительский элемент предлагает размер Вашей компоновке через ProposedViewSize. Его значения width и height являются опциональными: nil означает «размер не задан, выберите размер ideal».
Специальные значения .zero, .infinity и .unspecified позволяют получить минимальный, максимальный и ideal размеры.
func sizeThatFits(proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()) -> CGSize {
let width = proposal.width ?? 0
return CGSize(width: width, height: 44)
}Подробно о sizeThatFits
sizeThatFits возвращает размер, который требуется Вашей компоновке для данного предложения. SwiftUI может вызывать его несколько раз с разными предложениями, чтобы определить гибкость компоновки.
func sizeThatFits(proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()) -> CGSize {
// Sum child ideal sizes for a simple horizontal stack
let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
let w = sizes.reduce(0) { $0 + $1.width }
let h = sizes.map(\.height).max() ?? 0
return CGSize(width: w, height: h)
}Коллекция дочерних представлений
Subviews — это коллекция прокси-объектов для каждого дочернего элемента. Каждый прокси отвечает на вызов sizeThatFits и предоставляет приоритеты компоновки и пользовательские значения.
Вы никогда не видите настоящие представления — только эти прокси для измерений.
for subview in subviews {
let size = subview.sizeThatFits(.unspecified)
print(size)
}placeSubviews
placeSubviews назначает каждому дочернему элементу позицию внутри переданного Вам прямоугольника bounds.
Вызовите place(at:anchor:proposal:) для каждого прокси дочернего элемента.
func placeSubviews(in bounds: CGRect,
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()) {
var x = bounds.minX
for subview in subviews {
let size = subview.sizeThatFits(.unspecified)
subview.place(at: CGPoint(x: x, y: bounds.midY),
anchor: .leading,
proposal: .unspecified)
x += size.width
}
}Якоря при размещении
Параметр anchor указывает, какая точка дочернего элемента окажется в заданной Вами позиции. .topLeading размещает элемент по верхнему левому углу; .center центрирует дочерний элемент относительно этой точки.
subview.place(at: CGPoint(x: 0, y: 0),
anchor: .topLeading,
proposal: .unspecified)Границы не всегда начинаются в начале координат
Прямоугольник bounds может начинаться не в точке (0,0). Всегда размещайте элементы относительно bounds.minX и bounds.minY, никогда не используя абсолютный ноль.
let startX = bounds.minX // not 0
let startY = bounds.minYМинимальная компоновка без промежутков
Объединим всё: вот компоновка, размещающая дочерние представления слева направо без промежутков. Два метода согласованы: сообщаемый размер соответствует размещению.
struct SimpleHStack: Layout {
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
return CGSize(width: sizes.reduce(0){ $0 + $1.width },
height: sizes.map(\.height).max() ?? 0)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
var x = bounds.minX
for s in subviews {
let size = s.sizeThatFits(.unspecified)
s.place(at: CGPoint(x: x, y: bounds.minY), anchor: .topLeading, proposal: .unspecified)
x += size.width
}
}
}Согласованность — Ваша задача
SwiftUI рассчитывает, что два Ваших метода согласуются. Если placeSubviews использует больше места, чем сообщил sizeThatFits, дочерние элементы могут обрезаться или перекрываться.
Используйте общие геометрические вычисления в обоих методах.
Быстрая проверка: основы компоновки
Проверьте своё понимание протокола компоновки.
Итоги: основы протокола компоновки
Протокол Layout превращает пользовательскую логику размещения в повторно используемый контейнер. sizeThatFits сообщает размер для ProposedViewSize, а placeSubviews размещает каждый прокси дочернего элемента внутри bounds с помощью place(at:anchor:proposal:).
Предложения содержат опциональные размеры, границы могут быть смещены относительно начала координат, а два метода должны оставаться геометрически согласованными. Далее мы подробно измерим дочерние представления.
Часто задаваемые вопросы
Урок «Основы протокола Layout» бесплатный?
Да — полный текст урока «Основы протокола Layout» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Swift Academy, подпишись на CoddyKit PRO. Курс Swift Academy содержит 4 уроков всего.
Чему я научусь в уроке «Основы протокола Layout»?
Реализуйте sizeThatFits и placeSubviews. Ты практикуешь Swift Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Swift Academy?
Предыдущий опыт не требуется. Swift Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «Основы протокола Layout»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Swift Academy?
Да. Каждый урок Swift Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Основы протокола Layout
- Измерение подвида
- Создание потоковой компоновки
- Кэш компоновки и производительность