I am trying to create a horizontal carousel using SwiftUI ScrollView
This is what I have done so far:
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) }
}
Then I make use of this as follows:
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()
}
}
Functionality wise, this works well and gives me this:
The issue I have currently is that I want the scrollview to only take up as much space as its content with respect to its height.
While it is not so clear in the example above, if I add a background to the carousel view, you can see how much extra space is taken up.
So now updating this:
var body: some View {
VStack(spacing: 10, content: {
pagingCarousel
.background(.yellow)
continuousCarousel
.background(.green)
})
}
Gives me this:
How can I force my scrollview to set its height based on the height of the content?
I gave a few of these solutions a go but it just caused my content to disappear:


LazyHStackdoesn't know what the maximum height of its contents will be (because it's lazy). In this case your images have a constant height, since you did.frame(height: 200). In general, you will need to find the maximum height some other way, and set that to be theLazyHStack's height.LazyHStackshrinks to the height of the first subview, because it is in a list row.LazyHStack? Could this be achievable if we used anHStackinstead ?HStackand it should work as you expect.