I have a horizontal ScrollView with dot page control (based on this answer SwiftUI create image slider with dots as indicators), and every element in it is a custom view. The height of every element is determined by the data I pass into it, it can have two or three vertical sections depending on some conditions.
At the moment it looks something like this:
I want all the elements in my ScrollView to match the height of the biggest element, but I can't come up with a way of doing it, because we need all the elements to have been rendered in order to find out the height of the biggest one, but once they have been rendered, I can't change the height of the elements.
This is what I want it to look like, I want the bottom section to stretch to match the height of the biggest element (although I'd be fine with it resizing any other way, but I need it to match the biggest element):
I am very inexperienced in SwiftUI and am probably missing something.
Here is some of the code:
struct DotIndicatorCollectionView: View {
var items: [CustomItem]
@State private var index = 0
var body: some View {
return PagingView(index: $index.animation(), maxIndex: items.count - 1) {
ForEach(items, id: \.self) { item in
CustomView(
item: item
)
.padding(.horizontal, 20)
)
}
}
}
}
PagingView code is provided here: SwiftUI create image slider with dots as indicators
One way to solve this is to establish the height of the tallest item in a hidden view, then show the PagingView
as an overlay.
For the hidden view, you could use a ZStack
with all the items layered on top of each other.
For the items inside the PagingView
, you can apply maxHeight: .infinity
, so that they use the full height of the overlay.
Here is an example to show it working. It uses dummy implementations of the structs and views that you had in your original code:
struct CustomItem: Identifiable {
let id = UUID()
let title: String
let text: String
}
struct CustomView: View {
let item: CustomItem
var body: some View {
VStack(spacing: 20) {
Text(item.title)
.font(.largeTitle)
Text(item.text)
}
.padding()
}
}
struct DotIndicatorCollectionView: View {
var items: [CustomItem] = [
CustomItem(title: "Item 1", text: "One line of text"),
CustomItem(title: "Item 2", text: "Two lines of text\nTwo lines of text"),
CustomItem(title: "Item 3", text: "Three lines of text\nThree lines of text\nThree lines of text")
]
@State private var index = 0
@ViewBuilder
private func theItems(maxHeight: CGFloat?) -> some View {
ForEach(items) { item in
CustomView(
item: item
)
.padding(.horizontal, 20)
.frame(maxHeight: maxHeight)
.background(.orange)
}
}
var body: some View {
ZStack {
theItems(maxHeight: nil)
}
.frame(maxWidth: .infinity)
.hidden()
.overlay {
PagingView(index: $index.animation(), maxIndex: items.count - 1) {
theItems(maxHeight: .infinity)
}
}
}
}
You will notice in this example that the orange background is applied after setting maxHeight: .infinity
on a CustomView
. If you do it the other way around then the views will have the maximum height, but the background behind them won't actually fill the height.
If your real CustomView
includes a background color or rounded corners or some other decoration that depends on the height, then you have two choices:
maxHeight
as an init parameter to CustomView
, so that it can be used in the body
theItems
, like it was done in the example above.