Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting View frame based on GeometryReader located inside that View in SwiftUI

Tags:

ios

swift

swiftui

As a part of my effort to learn new technologies, I'm trying to implement one of the screens of an app I'm working on in SwiftUI. I have this rough layout:

enter image description here

This element should stretch to accommodate the container size (devices of different widths) and will be used in a ScrollView.

Here's the preliminary solution I came up with after some stumbling around:

enter image description here

This works well enough for the first attempt but I want to refactor the View by moving all the relevant parts into a separate View. In addition to that, I want that new view to automatically account for paddings and other size modification I might add later.

So, here's a naive rewrite:

enter image description here

As you can readily spot, GeometryReader inside ScrollView assumed the size the parent provided it, effectively hiding most of the View.

There are several "dirty" solutions to this situation: set a fixed frame height to the View, set an aspect ratio that would approximate the correct size (a bit tricky given that there are Text views with fixed heights), keep GeometryReader as the outermost element of the main view and pass the width to the subview as an initialization parameter.

I'm looking for a "clean" solution for this particular layout and, perhaps, for a more general understanding about how Views is SwiftUI regulate their sizes – in WWDC videos it is said that parent View proposes a size but then child View returns it's own size; is there a way to interfere in that process somehow or is it all done through private APIs?

Thank you!

–Baglan

p.s. I thought code screenshot besides the UI preview would be the most illustrative but let me know if I should provide code snippets as well!

like image 409
Baglan Avatar asked Nov 28 '19 07:11

Baglan


2 Answers

I believe my answer is supposed to be a comment, but as it's going to be a lengthy one, I'm posting this as an answer. I answer a similar question if that can bring more light.

It's important in which View component you are using GeometryReader.

  • If you are inside a typical View that spans up to the screen's size, you get the width & height of the screen itself.

  • If you are inside a ScrollView environment, you get

    • height of the container in case of .horizontal alignment
    • width of the container in case of .vertical alignment

Typically, ScrollView without specifying alignment explicitly defaults to .vertical.


I suppose, the design decision is correct. Because there is a Content View in a Scroll View if you recall UIScrollView from UIKit. You don't know the size of the Content View in advance until you add some contents. In SwiftUI, the GeometryReader inside ScrollView mimics the mathematics of the Content View from UIScrollView. And that is the reason, you don't get the actual size from geometry reader.

like image 139
nayem Avatar answered Nov 15 '22 06:11

nayem


This code works fine:

struct GeometryReaderExperiment: View {
    var body: some View {

        GeometryReader { geometry in

            ScrollView {

                Text("First")

                DesignedFancy(parentGeometry: geometry, rectangleColor: .red)

                Text("Second")

                DesignedFancy(parentGeometry: geometry, rectangleColor: .green)

                Text("Third")

                DesignedFancy(parentGeometry: geometry, rectangleColor: .blue)

            }

        }

    }
}

struct DesignedFancy: View {

    var parentGeometry: GeometryProxy
    var rectangleColor: Color

    var body: some View {

        HStack(alignment: .bottom) {
            VStack {
                Rectangle()
                    .aspectRatio(1, contentMode: .fit)
                    .foregroundColor(rectangleColor)

                Text("One")
            }
            .frame(width: parentGeometry.size.width * 2/3)

            VStack {
                Rectangle()
                    .aspectRatio(contentMode: .fit)
                    .foregroundColor(rectangleColor)
                Text("Two")
            }

        }

    }

}

I think the problem is that DesignedFancy doesn't know how much space it has. Therefore, you must provide this information through a variable. The result is:

enter image description here

like image 41
Hrabovskyi Oleksandr Avatar answered Nov 15 '22 06:11

Hrabovskyi Oleksandr