onmyway133/blog

How to fit ScrollView to content in SwiftUI

onmyway133 opened this issue · 5 comments

If we place ScrollView inside HStack or VStack, it takes all remaining space. To fit ScrollView to its content, we need to get its content size and constrain ScrollView size.

Use a GeometryReader as Scrollview content background, and get the local frame

import SwiftUI

struct HSearchBar: View {
    @State
    private var scrollViewContentSize: CGSize = .zero

    var body: some View {
        HStack {
            searchButton
            ScrollView(.horizontal, showsIndicators: false) {
                HStack(spacing: 12) {
                    ForEach(store.collections) { collection in
                        collectionCell(collection)
                    }
                }
                .background(
                    GeometryReader { geo -> Color in
                        DispatchQueue.main.async {
                            scrollViewContentSize = geo.size
                        }
                        return Color.clear
                    }
                )
            }
            .frame(
                maxWidth: scrollViewContentSize.width
            )
        }
    }
}

Thnaks alot

Thanks a lot for this solution. Just one question: Why is it necessary to use DispatchQueue.main.async to set the scrollViewContentSize value? Is this assingment done in background? I do not understand the need of that dispatch

Thanks a lot for this solution. Just one question: Why is it necessary to use DispatchQueue.main.async to set the scrollViewContentSize value? Is this assingment done in background? I do not understand the need of that dispatch

Leaving it out causes runtime warning: "Modifying state during view update, this will cause undefined behavior."

I am not exactly sure why because I agreed with you until trying this, will figure this out if I got some spare time on my hands...

            .background(
                GeometryReader { geo in
                    Color.clear
                        .onAppear {
                            Task { @MainActor in
                                scrollViewContentSize = geo.size
                            }
                        }
                }
            )

something like this is a bit cleaner

          .background(
              GeometryReader { geo in
                  Color.clear.task { scrollViewContentSize = geo.size }
              }
          )

Maybe maybe maybe...