SwiftUI - Using GeometryReader Without Modifying The View Size

谁都会走 提交于 2020-12-01 09:35:08

问题


I have a header view which extends its background to be under the status bar using edgesIgnoringSafeArea. To align the content/subviews of the header view correctly, I need the safeAreaInsets from GeometryReader. However, when using GeometryReader, my view doesn't have a fitted size anymore.

Code without using GeometryReader

struct MyView : View {
    var body: some View {
        VStack(alignment: .leading) {
            CustomView()
        }
        .padding(.horizontal)
        .padding(.bottom, 64)
        .background(Color.blue)
    }
}

Preview

Code using GeometryReader

struct MyView : View {
    var body: some View {
        GeometryReader { geometry in
            VStack(alignment: .leading) {
                CustomView()
            }
            .padding(.horizontal)
            .padding(.top, geometry.safeAreaInsets.top)
            .padding(.bottom, 64)
            .background(Color.blue)
            .fixedSize()
        }
    }
}

Preview

Is there a way to use GeometryReader without modifying the underlying view size?


回答1:


I tried with the previewLayout and I see what you mean. However, I think the behavior is as expected. The definition of .sizeThatFits is:

Fit the container (A) to the size of the preview (B) when offered the size of the device (C) on which the preview is running.

I intercalated some letters to define each part and make it more clear:

A = the final size of the preview.

B = The size of what you are modifying with .previewLayout(). In the first case, it's the VStack. But in the second case, it's the GeometryReader.

C = The size of the screen of the device.

Both views act differently, because VStack is not greedy, and only takes what it needs. GeometryReader, on the other side, tries to have it all, because it does not know what its child will want to use. If the child wants to use less, it can do it, but it has to start by being offered everything.

Perhaps if you edit your question to explain exactly what you would like to accomplish, I can refine my answer a little.

If you would like GeometryReader to report the size of the VStack. you may do so by putting it inside a .background modifier. But again, I am not sure what's the goal, so maybe that's a no go.

I have written an article about the different uses of GeometryReader. Here's the link, in case it helps: https://swiftui-lab.com/geometryreader-to-the-rescue/


UPDATE

Ok, with your additional explanation, here you have a working solution. Note that the Preview will not work, because safeInsets are reported as zero. On the simulator, however, it works fine:

As you will see, I use view preferences. They are not explained anywhere, but I am currently writing an article about them that I will post soon.

It may all look too verbose, but if you find yourself using it too often, you can encapsulate it inside a custom modifier.

import SwiftUI

struct InsetPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0

    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }

    typealias Value = CGFloat
}

struct InsetGetter: View {
    var body: some View {
        GeometryReader { geometry in
            return Rectangle().preference(key: InsetPreferenceKey.self, value: geometry.safeAreaInsets.top)
        }
    }
}

struct ContentView : View {
    var body: some View {
        MyView()

    }
}

struct MyView : View {
    @State private var topInset: CGFloat = 0

    var body: some View {

        VStack {
            CustomView(inset: topInset)
                .padding(.horizontal)
                .padding(.bottom, 64)
                .padding(.top, topInset)
                .background(Color.blue)
                .background(InsetGetter())
                .edgesIgnoringSafeArea(.all)
                .onPreferenceChange(InsetPreferenceKey.self) { self.topInset = $0 }

            Spacer()
        }

    }
}

struct CustomView: View {
    let inset: CGFloat

    var body: some View {
        VStack {
            HStack {
                Text("C \(inset)").color(.white).fontWeight(.bold).font(.title)
                Spacer()
            }

            HStack {
                Text("A").color(.white)
                Text("B").color(.white)
                Spacer()
            }
        }

    }
}



回答2:


I managed to solve this by wrapping the page main view inside a GeometryReader and pass down the safeAreaInsets to MyView. Since it is the main page view where we want the entire screen thus it is ok to be as greedy as possible.




回答3:


Depending on your view and use case it may be also feasable to define a fixed preview size using PreviewLayout.fixed(width:height:).

struct MyView_Previews: PreviewProvider {
    static var previews: some View {
        MyView().previewLayout(.fixed(width: 400, height: 150))
    }
}

The only downside is probably, that this won‘t scale dynamically. But still better than full device previews, especially when using multiple previews.



来源:https://stackoverflow.com/questions/56859095/swiftui-using-geometryreader-without-modifying-the-view-size

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!