UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: GeometryReader inside ScrollView

Forums > SwiftUI

Hi, happy Monday!

I have a scrollview with a few subviews, and I wanted each of these subviews to have a certain frame height relative to UI screen height. I know you can do this with GeometryReader, but the moment I put GeometryReader inside ScrollView, it fails to read screen height.

Here's what I want to achieve

Without scrollview, this works nicely.

struct CurrentWeatherView: View {

    var body: some View {
        NavigationView {
            GeometryReader { geo in
                VStack {
                    Rectangle()
                        .foregroundColor(Color.red.opacity(0.5))
                        .frame(maxWidth: geo.size.width, maxHeight: geo.size.height * 0.3)
                    Rectangle()
                        .foregroundColor(Color.red.opacity(0.5))
                        .frame(maxWidth: geo.size.width, maxHeight: geo.size.height * 0.3)
                    Rectangle()
                        .foregroundColor(Color.red.opacity(0.5))
                        .frame(maxWidth: geo.size.width, maxHeight: geo.size.height * 0.3)
                }
            }
            .navigationTitle("Title")
            .padding(.horizontal)
        }
    }
}

But the view breaks when I use ScrollView. I tried putting ScrollView inside Geometry Reader, and the other way around, but with the same results (geo.size.height becomes 0 it seems).

struct CurrentView: View {

    var body: some View {
        NavigationView {
            GeometryReader { geo in
                ScrollView {
                    VStack {
                        Rectangle()
                            .foregroundColor(Color.red.opacity(0.5))
                            .frame(maxWidth: geo.size.width, maxHeight: geo.size.height * 0.3)
                        Rectangle()
                            .foregroundColor(Color.red.opacity(0.5))
                            .frame(maxWidth: geo.size.width, maxHeight: geo.size.height * 0.3)
                        Rectangle()
                            .foregroundColor(Color.red.opacity(0.5))
                            .frame(maxWidth: geo.size.width, maxHeight: geo.size.height * 0.3)
                    }
                }
            }
            .navigationTitle("Title")
            .padding(.horizontal)
        }
    }
}

So is there a way to set frame height relative to screen height for frames inside a scrollview? Or will I have to just resort to UIScreen.main.bounds.size.height Thank you!

2      

Just guessing here. The ScrollView adapts automatically to the content it has and has no fixed height you can measure before there is content. Basically, it could scroll to infinity. The whole point of a ScrollView is that you can scroll down when you have more content than it can fit on the screen. And as your rectangles don't have content it shrinks them. What happens if you remove the height from your rectangles completely?

2      

You can also specify a minimum, as well. For example

Rectangle()
    .foregroundColor(Color.red.opacity(0.5))
    .frame(minWidth: geo.size.width,
           maxWidth: geo.size.width,
           minHeight: geo.size.height * 0.3,
           maxHeight: geo.size.height * 0.3)

As you are only scrolling in the vertical direction, the width parameters are not needed. So it can be simplified to.

Rectangle()
    .foregroundColor(Color.red.opacity(0.5))
    .frame(minHeight: geo.size.height * 0.3,
           maxHeight: geo.size.height * 0.3)

minHeight and maxHeight are only really useful if you have differtent possible values for them. In this case you might only be needing a fixed height for the frame, so use height instead, further simplifying the code.

Rectangle()
    .foregroundColor(Color.red.opacity(0.5))
    .frame(height: geo.size.height * 0.3)

Adding a couple more rectangles, with different colours to show the difference, to the scroll view.


NavigationView {
    GeometryReader { geo in
        ScrollView {
            VStack {
                Rectangle()
                    .foregroundColor(Color.red.opacity(0.5))
                    .frame(minHeight: geo.size.height * 0.3, maxHeight: geo.size.height * 0.3)
                Rectangle()
                    .foregroundColor(Color.orange.opacity(0.5))
                    .frame(height: geo.size.height * 0.4)
                Rectangle()
                    .foregroundColor(Color.red.opacity(0.5))
                    .frame(minHeight: geo.size.height * 0.3, maxHeight: geo.size.height * 0.3)
                Rectangle()
                    .foregroundColor(Color.mint.opacity(0.5))
                    .frame(minHeight: geo.size.height * 0.5, maxHeight: geo.size.height * 0.6)
                Rectangle()
                    .foregroundColor(Color.indigo.opacity(0.5))
                    .frame(minHeight: geo.size.height * 0.2, maxHeight: geo.size.height * 0.3)
            }
        }
    }
    .navigationTitle("Title")
    .padding(.horizontal)
}

4      

This taken from Paul's Pro SwiftUI book (Which is on offer until 4 Dec)

“At the core of SwiftUI is its three-step layout process:

1 .A parent view proposes a size for its child.

  1. Based on that information, the child then chooses its own size and the parent view must respect that choice.
  2. The parent view then positions the child in its coordinate space.”

So to try and explain a little about what happening in your code.

“Not all views have a meaningful ideal size, and in fact some views have very little sizing preference at all – they will happily adapt their own size based on the way we use them alongside other views. This is called being layout neutral, and a view can be layout neutral for any combination of its six dimensions. In its purest form, layout neutrality it looks like this:

struct ContentView: View {
    var body: some View {
        VStack {
                ForEach(0..<3, id: \.self) { _ in
                    Color.red
                }
            }
            .padding(.horizontal)
    }
}

That will fill the screen with three Color.red of equal size

But when you add a ScrollView

“Usually Color.red is happy to fill all the available space, but it wouldn’t make any sense here because it would lead to an infinitely sized scroll view. In this situation, the red color will get a nominal 10-point height – enough that we can see it’s being placed, but it won’t expand beyond that.”

var body: some View {
    ScrollView {
        VStack {
            ForEach(0..<3, id: \.self) { _ in
                Color.red
            }
        }
        .padding(.horizontal)
    }
}

So it the ScrollView that causing your issues and GeometryReader would lead to an infinite size.

PS UIScreen.main.bounds.size.height will give the total height of the screen including the safe areas which is different on different phones.

2      

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.