Я пытаюсь создать интерфейс с представлением вертикальной прокрутки на верхнем уровне, а затем представлением горизонтальной прокрутки (с использованием новой прокрутки с разбиением на страницы из iOS 17), чтобы отобразить несколько дочерних представлений, между которыми пользователь может прокручиваться вбок. Пока что он ведет себя точно так, как я хочу, за исключением того, что высота первого из представлений в представлении с горизонтальной прокруткой, похоже, задает высоту для каждого из других представлений, даже если они имеют более высокий контент. Честно говоря, я не уверен, какое поведение я себе представляю, но мне было интересно, решал ли кто-нибудь подобную проблему или разработал аналогичный макет другим способом.
Вот минимальный воспроизводимый пример:
import SwiftUI
struct ContentView: View {
@State private var selectedTab: String? = "Tab 1"
var body: some View {
ScrollView(.vertical) {
LazyVStack {
Image(systemName: "photo.fill")
.resizable()
.aspectRatio(contentMode: .fill)
ScrollView(.horizontal) {
LazyHStack(spacing: 0) {
SampleView(.purple, 5)
.id("Tab 1")
.containerRelativeFrame(.horizontal)
SampleView(.red, 12)
.id("Tab 2")
.containerRelativeFrame(.horizontal)
SampleView(.blue, 20)
.id("Tab 3")
.containerRelativeFrame(.horizontal)
}
.scrollTargetLayout()
}
.scrollPosition(id: $selectedTab)
.scrollTargetBehavior(.paging)
}
}
}
@ViewBuilder
func SampleView(_ color: Color, _ size: Int) -> some View {
LazyVGrid(columns: Array(repeating: GridItem(), count: 2), content: {
ForEach(1...size, id: \.self) { _ in
RoundedRectangle(cornerRadius: 15)
.fill(color.gradient)
.frame(height: 150)
}
})
}
}
Как видно из примера, высота горизонтального прокрутки зафиксирована на высоте первого дочернего представления.
Это происходит из-за того, что LazyHStack
используется для горизонтальной прокрутки контента.
LazyVStack
основывает свою высоту на идеальной высоте вложенного (горизонтального) ScrollView
.ScrollView
использует идеальную высоту LazyHStack
.LazyHStack
использует высоту первого дочернего элемента, потому что другие подпредставления не видны и поэтому еще не имеют размера.Если вы измените LazyHStack
на HStack
, то вертикальная прокрутка будет работать на полный рост самого высокого ребенка. Но, конечно, вы также можете прокрутить вниз за пределы первого дочернего элемента. Вероятно, вы тоже захотите добавить выравнивание .top
:
ScrollView(.vertical) {
LazyVStack {
Image(systemName: "photo.fill")
// ...
ScrollView(.horizontal) {
HStack(alignment: .top, spacing: 0) { // 👈 HERE
// ...
}
.scrollTargetLayout()
}
.scrollPosition(id: $selectedTab)
.scrollTargetBehavior(.paging)
}
}
Если известен рост самого высокого ребенка, альтернативным решением было бы применить это значение как minHeight
к LazyHStack
:
LazyHStack(alignment: .top, spacing: 0) {
// ...
}
.frame(minHeight: 1000)
.scrollTargetLayout()
Однако, если вы переоцените высоту, то вертикальная прокрутка просто прокрутит в пустое пространство, а если вы недооцените высоту, то более высокий ребенок будет обрезан, как вы видели изначально.
Если вы хотя бы знаете, какое из подпредставлений будет самым высоким, тогда другим обходным решением будет использование самого высокого подпредставления для формирования скрытого контура для первого подпредставления. Фактическое первое подпредставление затем можно отобразить как наложение поверх этого отпечатка:
LazyHStack(alignment: .top, spacing: 0) {
SampleView(.blue, 20)
.id("Tab 1")
.containerRelativeFrame(.horizontal)
.hidden()
.overlay(alignment: .top) {
SampleView(.purple, 5)
}
SampleView(.red, 12)
.id("Tab 2")
.containerRelativeFrame(.horizontal)
SampleView(.blue, 20)
.id("Tab 3")
.containerRelativeFrame(.horizontal)
}
.scrollTargetLayout()
РЕДАКТИРОВАТЬ Ваш комментарий заставил меня задуматься. Поскольку вы используете scrollTargetLayout
, изменение подпредставления будет отражено изменением на selectedTab
. Используя GeometryReader
на заднем плане каждого подпредставления, minHeight
для LazyHStack
можно обновить, чтобы он соответствовал высоте выбранного подпредставления при каждом изменении содержимого.
@State private var selectedTab: String? = "Tab 1"
@State private var minContentHeight = CGFloat.zero
private func heightReader(target: String) -> some View {
GeometryReader { proxy in
Color.clear
.onChange(of: selectedTab, initial: true) { oldVal, newVal in
if target == newVal {
withAnimation {
minContentHeight = proxy.size.height
}
}
}
}
}
var body: some View {
ScrollView(.vertical) {
LazyVStack {
Image(systemName: "photo.fill")
.resizable()
.aspectRatio(contentMode: .fill)
ScrollView(.horizontal) {
LazyHStack(alignment: .top, spacing: 0) {
SampleView(.purple, 5)
.id("Tab 1")
.containerRelativeFrame(.horizontal)
.background { heightReader(target: "Tab 1") }
SampleView(.red, 12)
.id("Tab 2")
.containerRelativeFrame(.horizontal)
.background { heightReader(target: "Tab 2") }
SampleView(.blue, 20)
.id("Tab 3")
.containerRelativeFrame(.horizontal)
.background { heightReader(target: "Tab 3") }
}
.frame(minHeight: minContentHeight)
.scrollTargetLayout()
}
.scrollPosition(id: $selectedTab)
.scrollTargetBehavior(.paging)
}
}
}
@BrandonSlaght Спасибо, что приняли ответ! Ваш комментарий заставил меня задуматься, ответ дополнен тем, что, по моему мнению, является лучшим решением. Возможно, это то решение, на которое вы намекали.
Ваше предложение намного чище, чем то, что я пытался сделать, и гораздо более без ошибок, очень ценю, что вы нашли на это время!
@BrandonSlaght Эй, спасибо за награду! Рад слышать, что решение оказалось полезным.
Ответьте несколькими разными решениями, спасибо! Я почти смог динамически изменять высоту горизонтальной прокрутки с помощью программ чтения геометрии, тем не менее, все еще решая некоторые проблемы.