1
votes

I'm trying to make a sub view which contains a drawing which I want the size to be proportional to a fraction of the parent container.

This works fine except when put inside a scroll view somewhere up the hierarchy.

As seen from the screenshots when inside of the stack the loudest as expected. But when you put inside a scroll view the size of the geometry reader collapses to near zero and its children overflow its boundaries. As though it's rendering its children on a different Z index. In fact if you remove the wrapping VStack from the geometry render contents it doesn't fact layout all its children as though it were wrapped in a ZStack.

EDIT: To be clear: the ScrollView is not owned by the component. The component should not be aware if it's in a ScrollView or not - it's just a poor helpless component, being thrown around. That's why I put the ScrollView into the preview code, not the component.

I've tried all sorts of combinations of fixed sizes and men and mags and ideal. The only work around I could find was this hacky solution - the edited part of the question near the bottom where the width reported by the geometry reader is captured through a workaround into a state variable and then re-used to set the frame size of a sibling view.

Note, I'm a complete beginner. There seems to be some interaction between the scroll view and the geometry reader that is beyond my current understanding.

Seems to be a confusing topic.

import SwiftUI

struct ScrollViewGeoReader: View {
    var body: some View {
        GeometryReader{ g in
            VStack{
                let width = g.size.width
                Circle().frame(width: width/3, height: width/3, alignment: .center)
                Text("inside geo")
                Text("inside geo")
                Text("inside geo \(width)")
                Text("inside geo")
                Text("inside geo")
            }
            .border(Color.green, width: 2)
        }
        .border(Color.red, width: 3)
    }
}

struct ScrollViewGeoReader_Previews: PreviewProvider {
    static var previews: some View {
        VStack {
//            VStack {
            ScrollView {
                ScrollViewGeoReader()
                Text("Next scrollview item")
            }
            .border(Color.blue, width: 2)

        }
    }
}

ZStack (expected layout):

ZStack (expected layout)

ScrollView (see how the red frame of the geometry read it has collapsed to size 10):

enter image description here

EDIT:

Also note that the same problem occurs with or without the circle, which is also a problem I have. So it's not chicken / egg as far as the width capturing is concerned, at least in terms of Circles. I would have though the Text components know their own size, and would tell the GR.

enter image description here

vs

enter image description here

1
ScrollView has no own size and GeometryReader has no own size, so you've got into chicken-egg problem, ie. no-one knows size to render, so collapsed. You must have definite frame size for items inside ScrollView. - Asperi
Most probably you just need to place GeometryReader outsize ScrollView to give width/3 size for Circle and everything else will be ok. - Asperi
Thanks. Can’t rearrange order because the child view doesn’t know if it’ll be in a scroll view or not. Ok forget the circle. But the Text events specify a size, but I see the same thing happen. (Ie remove the circle and the layout is still busted). - Antony Stubbs
So why isn't Text enough to tell GR it's size? - Antony Stubbs
GR takes free size of parent, not children. Reread my first comment. - Asperi

1 Answers

0
votes

Here is scratchy (w/o subviews separation) possible solution for layout. If/when internal subviews separated the geometry width can be injected by constructor arguments.

Tested with Xcode 12.

demo

struct TestScrollViewWithGeometry: View {
    var body: some View {
        GeometryReader{ g in
            ScrollView {
                VStack{
                    let width = g.size.width
                    Circle().frame(width: width/3, height: width/3, alignment: .center)
                    Text("inside geo")
                    Text("inside geo")
                    Text("inside geo \(width)")
                    Text("inside geo")
                    Text("inside geo")
                }
                .border(Color.green, width: 2)
                Text("Next scrollview item")
            }
            .border(Color.blue, width: 2)
        }
        .border(Color.red, width: 3)
    }
}