我正在尝试使用 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
被设计为延迟绘制其视图。它不会尝试将所有东西都布置好并找到它需要的最大高度(这根本不是懒惰的)。
因此,在这种情况下,它会尝试填充所有可用空间。 在其他情况下,需要使用其理想大小时,它将仅使用堆栈中的第一个视图来计算其理想大小。
在您的玩具示例中,所有图像的高度均为 200,因此通过使用
.frame(height: 200)
设置轮播的高度来解决此问题很简单。一般来说,您需要一些其他方法来找出最大高度,并执行类似 .frame(height: findMaxHeight(data))
的操作。例如,图像可能来自 API 调用,该调用也恰好返回图像的大小。然后您可以使用该信息来找到最大高度,而无需绘制所有视图。
如果除了绘制视图之外没有办法知道最大高度,则应该使用非惰性
HStack
来代替。