ScrollView shrink to fit

Hi,

I have embedded a view into a scroll view in case the content is too large, but mostly it is not. Is there a way to have the ScrollView shrink to fit its content?
To my surprise it looks like this is a simple task not possible in SwiftUI? Am I missing something?

All the best
Christoph
Not really sure what your scenario is, so there might be a simpler way.

However, I had a somewhat similar situation, but this was without a ScrollView. I found something on Hacking With Swift forum that got me to a solution (bit of a hack)
  • Create a State variable that stores the CGSize of your content

  • Put an overlay on your content view that has a GeometryReader with a Color.Clear view (this is the tacky part)

  • In the onAppear of your Color.Clear, set the CGSize State to your GeometryProxy's size

  • Use a basic .frame modifier to set the size of your Scrollview to the size of the State CGSize


Code Block
struct ContentView: View {
    @State private var contentSize: CGSize = .zero
    var body: some View {
        ScrollView {
            Rectangle()
                .fill(Color.blue)
                .frame(width: 100, height: 100)
                .overlay(
                    GeometryReader { geo in
                        Color.clear.onAppear {
                            contentSize = geo.size
                        }
                    }
                )
        }
        .background(Color.red)
        .frame(maxWidth: contentSize.width, maxHeight: contentSize.height)
    }
}


I probably miss something.

Why do you need to shrink the ScrollView ?

If the content is smaller than scrollView, there will be no scroll.
Do you just want to hide scroll indicators ?
Or hide the frame of the scrollView ?
Hi,

thank you for the responses.

@Calude31 The scenario why I want to shrink it is, that it is an info box. So around the scrollview is a frame, which is super ugly with empty space inside. The idea basically is that in 90% it is no scrollview and only for the few time where the info does not fit on that screen area it would be scrollable. Something quite natural to do with autolayout seems impossible with SwiftUI.

@joosttk Yes, this was the only thing I could think of, however the content is dynamic, too, so instead of onAppear I was consider doing with a preferenceKey and onPreferenceChange. Does not see as a clean way, but I guess it'll have to do :)

Thank you!

All the best
Christoph
Just quickly here is the code how I finally used it. It is based @joosttk approach, but using preference to be changed at any time. Also I added a stroke so it is obvious why the ScrollView should shrink, too.

Code Block Swift
struct HeightPreferenceKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue: CGFloat = 40
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}
struct FittedScrollView: View {
static func newString() -> String { String(repeating: "Hello World! ", count: Int.random(in: 1..<35)) }
@State private var contentString = Self.newString()
@State private var contentHeight: CGFloat = 40
var body: some View {
VStack {
ScrollView {
Text(contentString)
.padding(20)
.overlay(
GeometryReader { geo in
Color.clear.preference(key: HeightPreferenceKey.self, value: geo.size.height)
})
}
.frame(maxWidth: 300, maxHeight: contentHeight, alignment: .center)
.padding(20)
.background(RoundedRectangle(cornerRadius: 20).stroke(Color(white: 0.4), lineWidth: 3))
.background(RoundedRectangle(cornerRadius: 20).fill(Color(white: 0.8)))
Button("Next Text", action: { contentString = Self.newString() })
}
.frame(height: 300)
.onPreferenceChange(HeightPreferenceKey.self) {
contentHeight = $0
}
}
}


we are using solution proposed here: https://github.com/onmyway133/blog/issues/769#issue-802788982


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

If you don't mind the bouncing, you can simply do

ScrollView {
    &#x2F;&#x2F;content
}
.frame(maxHeight: 300).fixedSize(horizontal: false, vertical: true)

This will make the ScrollView fit its content vertically until it grows up to a max height of 300, and it won't grow any bigger afterwards. You can hide the indicator, but unfortunately without some hacking around, you can't easily turn the bounces off when the height is < 300, which is a limitation here.

You can just add the extra Space to

ScrollView{

&#x2F;&#x2F;Your code is here 
&#x2F;&#x2F;rest of space here
 Spacer(minLength: Help.screenHeight&#x2F;3.3)
}

and when your app start you can just

WindowGroup {
    if appStillLoading {
            GeometryReader { gemotry in
                ZStack{
                    SplashScreen()
                .onAppear {Help.screenWidth = gemotry.size.width;Help.screenHeight = gemotry.size.height}
            }
    }else{
        ContentView()
    }
}
ScrollView shrink to fit
 
 
Q