Reputation: 5153
I am using a ScrollView
and a LazyHStack
to create a horizontal carousel.
The goal I want to achieve is this:
What I want is:
What I am able to achieve so far is this:
As you can see, the images don't align and neither does the text due to that.
Here is my code
The carousel:
struct CarouselView<Content: View>: View {
let content: Content
@State private var currentIndex = 0
@State private var contentSize: CGSize = .zero
private let showsIndicators: Bool
private let spacing: CGFloat
private let shouldSnap: Bool
init(showsIndicators: Bool = true,
spacing: CGFloat = .zero,
shouldSnap: Bool = false,
@ViewBuilder content: @escaping () -> Content) {
self.content = content()
self.showsIndicators = showsIndicators
self.spacing = spacing
self.shouldSnap = shouldSnap
}
var body: some View {
ScrollView(.horizontal, showsIndicators: showsIndicators) {
LazyHStack(spacing: spacing) {
content
}.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) }
}
And then I use the carousel like this:
struct ContentView: View {
let imagesNames = ["img-1", "img-2", "img-3", "img-4"]
let numberOfLines = [2, 1, 3, 2]
var body: some View {
VStack(spacing: 40) {
continuousCarousel
}
}
private var continuousCarousel: some View {
CarouselView(showsIndicators: true,
spacing: 20) {
ForEach(0 ..< imagesNames.count, id: \.self) { index in
createImageTile(with: imagesNames[index],
height: 70,
numberOfLines: numberOfLines[index])
}
}
}
private func createImageTile(with image: String,
height: CGFloat,
numberOfLines: Int) -> some View {
VStack(spacing: .zero) {
Image(image)
.resizable()
.cornerRadius(30)
.scaledToFit()
.aspectRatio(contentMode: .fill)
.frame(width: 200, height: 100)
.padding(.bottom, 30)
Text("Headline")
.bold()
.padding(.bottom, 10)
ForEach(0 ..< numberOfLines, id: \.self) { _ in
Text("Some description here")
.padding(.bottom, 5)
}
}
}
}
I feel like applying an alignment somewhere or a spacer might fix this, but I don't know where.
Applying a bottom alignment on my lazyHStack
didn't work as it moved the main content region to the bottom of the scrollview.
How can I achieve what I need ?
Update 1
After giving the first answer below a go, adding a spacer to the VStack, did help align everything to the top as follows:
private func createImageTile(with image: String,
height: CGFloat,
numberOfLines: Int) -> some View {
VStack(spacing: .zero) {
Image(image)
.resizable()
.cornerRadius(30)
.scaledToFit()
.aspectRatio(contentMode: .fill)
.frame(width: 200, height: 100)
.padding(.bottom, 30)
Text("Headline")
.bold()
.padding(.bottom, 10)
ForEach(0 ..< numberOfLines, id: \.self) { _ in
Text("Some description here")
.padding(.bottom, 5)
}
Spacer() // added this
}
}
However, this introduced another issue, the top of my image is getting clipped.
I tried adding the clipped
modifier to the scrollview as suggested here, however, that didn't change anything.
If I add a top spacing to the VStack, this could work but the value seems arbitrary and seems more of a hack than a solution.
Upvotes: 0
Views: 497
Reputation: 100503
You can try
VStack(spacing: .zero) { // .leading align content to the left
Image(image)
.resizable()
.cornerRadius(30)
.scaledToFit()
.aspectRatio(contentMode: .fill)
.frame(width: 200, height: 100)
.padding(.bottom, 30)
Text("Headline")
.bold()
.padding(.bottom, 10)
ForEach(0 ..< numberOfLines, id: \.self) { _ in
Text("Some description here")
.padding(.bottom, 5)
}
Spacer() // Align vertical content to top
}
Upvotes: 0