Я пытаюсь создать горизонтальную карусель, используя SwiftUI ScrollView.
Это то, что я сделал до сих пор:
struct CarouselView<Content: View>: View {
let content: Content
@State private var currentIndex = 0
@State private var contentSize: CGSize = .zero
private var showsIndicators: Bool
private var spacing: CGFloat
private var offset: CGFloat
private var shouldSnap: Bool
init(showsIndicators: Bool = true,
spacing: CGFloat = 0,
offset: CGFloat = 0,
shouldSnap: Bool = false,
@ViewBuilder content: @escaping () -> Content) {
self.content = content()
self.showsIndicators = showsIndicators
self.spacing = spacing
self.offset = offset
self.shouldSnap = shouldSnap
}
var body: some View {
ScrollView(.horizontal, showsIndicators: showsIndicators) {
LazyHStack(spacing: spacing) {
content
.offset(x: offset)
}.apply {
if #available(iOS 17.0, *), shouldSnap {
$0.scrollTargetLayout()
} else {
$0
}
}
}
.apply {
if #available(iOS 17.0, *), shouldSnap {
$0.scrollTargetBehavior(.viewAligned)
} else {
$0
}
}
}
}
extension View {
func apply<V: View>(@ViewBuilder _ block: (Self) -> V) -> V { block(self) }
}
Затем я использую это следующим образом:
struct ContentView: View {
let imagesNames: [String] = ["img-1", "img-2", "img-3"]
var body: some View {
VStack(spacing: 10, content: {
pagingCarousel
continuousCarousel
})
}
private var pagingCarousel: some View {
CarouselView(showsIndicators: false,
spacing: 10,
shouldSnap: true) {
ForEach(imagesNames, id: \.self) { image in
createTile(with: image)
}
}
}
private var continuousCarousel: some View {
CarouselView(showsIndicators: true,
spacing: 20,
offset: 20) {
ForEach(imagesNames, id: \.self) { image in
createTile(with: image)
}
}
}
private func createTile(with image: String) -> some View {
ZStack {
Image(image)
.resizable()
.scaledToFill()
.overlay(Color.black.opacity(0.5))
Text("Headline")
.bold()
.foregroundStyle(.white)
}
.frame(height: 200)
.cornerRadius(30)
.clipped()
}
}
С точки зрения функциональности это работает хорошо и дает мне следующее:
Проблема, с которой я сейчас сталкиваюсь, заключается в том, что я хочу, чтобы представление прокрутки занимало столько же места, сколько и его содержимое, относительно его высоты.
Хотя в приведенном выше примере это не так ясно, если я добавлю фон в представление карусели, вы увидите, сколько дополнительного места занято.
Итак, теперь обновляем это:
var body: some View {
VStack(spacing: 10, content: {
pagingCarousel
.background(.yellow)
continuousCarousel
.background(.green)
})
}
Дает мне это:
Как я могу заставить свой прокрутку устанавливать высоту в зависимости от высоты содержимого?
Я попробовал несколько из этих решений, но из-за этого мой контент просто исчез:
Ах, это опять лень ленивых стопок. LazyHStack
не знает, какой будет максимальная высота его содержимого (потому что ленив). В этом случае ваши изображения имеют постоянную высоту, так как вы это сделали .frame(height: 200)
. В общем, вам нужно будет найти максимальную высоту другим способом и установить ее как высоту LazyHStack
.
См. также этот вопрос, который вызван той же ленью. Хотя в этом случае LazyHStack
сжимается до высоты первого подпредставления, потому что оно находится в строке списка.
@Sweeper Итак, можно ли сделать вывод, что этого невозможно достичь из-за LazyHStack
? Было бы это достижимо, если бы вместо этого мы использовали HStack
?
Да, вы можете изменить это на HStack
, и все должно работать так, как вы ожидаете.
Это потому, что LazyHStack
предназначен для ленивого рисования своих видов. Он не будет пытаться все разложить и найти максимальную необходимую ему высоту (это совсем не лениво).
Поэтому в этом случае он пытается заполнить все доступное пространство. В других случаях, когда необходимо использовать идеальный размер, он рассчитает идеальный размер, используя только первое представление в стеке. Вы можете добиться этого, выполнив .fixedSize(horizontal: false, vertical: true)
.
В вашем примере с игрушкой все изображения имеют постоянную высоту 200, поэтому это легко исправить, установив высоту карусели с помощью .frame(height: 200)
. В общем, вам нужен другой способ узнать, какая будет максимальная высота, и сделать что-то вроде .frame(height: findMaxHeight(data))
. Например, изображения могут быть получены в результате вызова API, который также возвращает размеры изображения. Затем вы можете использовать эту информацию, чтобы найти максимальную высоту, не рисуя все виды.
Если нет другого способа узнать максимальную высоту, кроме рисования видов, вместо этого вам следует просто использовать неленивый HStack
.
Спасибо, это имеет смысл. Мне просто интересно узнать о вашем комментарии: Therefore, it tries to fill all the available space in this case. In other cases, where its ideal size needs to be used, it will calculate its ideal size using only the first view in the stack.
Есть ли способ хотя бы сделать высоту прокрутки такой же, как у первого элемента в lazyHStack?
@ShawnFrank Да. Используйте .fixedSize(horizontal: false, vertical: true)
, чтобы заставить его использовать идеальную высоту.
Спасибо за ваше время и помощь, вы также можете добавить этот последний бит к ответу, так как все мои элементы будут иметь фиксированную ширину, поэтому высота первого элемента работает, и поэтому .fixedSize(horizontal: false, vertical: true)
работает для меня.
@Sweeper - ты прав, он не нужен и тоже не использовался. Я забыл удалить его из одного из примеров решений, которые я пробовал. Я обновил код, чтобы отразить ваш комментарий - нет средства чтения геометрии... однако результат точно такой же, как показано в моем вопросе, потому что я никогда ни для чего не использовал средство чтения геометрии.