SwiftUI: Making elements of a ScrollView match the height of the biggest element

201 views Asked by At

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:

Existing ScrollView

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):

Desired ScrollView

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

1

There are 1 answers

1
Benzy Neez On BEST ANSWER

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:

  • either, pass the maxHeight as an init parameter to CustomView, so that it can be used in the body
  • or, move the background decoration to the function theItems, like it was done in the example above.