SwiftUI measuring the height of a view - swiftui

I tried measuring the height of a view by using coordinatespace on it but the value it returns is the height of the full screen. Any idea why my code isn't giving me what I want ?
struct GeoReader: View {
var body: some View {
GeometryReader { geo in
VStack {
ZStack {
Rectangle()
.foregroundColor(.blue)
Text("Heigt of full screen is \(geo.size.height)")
}
ZStack {
Rectangle()
.foregroundColor(.red)
.coordinateSpace(name: "Redbox")
Text("Height of red box is \(geo.frame(in: .named("Redbox")).height)")
}
}
}
}
}

The showing size is the dimension of the full view that is the container of the inner view.
You need to use another GeometryReader to get the inner dimension of a second ZStack.
struct ContentView: View {
var body: some View {
GeometryReader { geo in
VStack {
ZStack {
Rectangle()
.foregroundColor(.blue)
Text("Heigt of full screen is \(geo.size.height)")
}
GeometryReader { innterGeo in //<Here
ZStack {
Rectangle()
.foregroundColor(.red)
.coordinateSpace(name: "Redbox")
Text("Height of red box is \(innterGeo.frame(in: .named("Redbox")).height)")
}
}
}
}
}
}
if you need to use this in any places then you can use this approch.
First, create one struct and wrapped your content with GeometryReader.
struct GeometryContentSize<Content: View>: View {
public var content: (CGSize) -> Content
var body: some View {
GeometryReader { geo in
content(geo.size)
}
}
}
usage:
struct ContentView: View {
var body: some View {
GeometryReader { geo in
VStack {
ZStack {
Rectangle()
.foregroundColor(.blue)
Text("Heigt of full screen is \(geo.size.height)")
}
GeometryContentSize { (size) in //<--- Here
ZStack {
Rectangle()
.foregroundColor(.red)
Text("Height of red box is \(size.height)")
}
}
}
}
}
}

The geometry reader measures the entires screen, because you put the geometry reader right on top in the body of the view. It will therefore read the geometry of the view it returns. In this case the entire screen.
If you want to get the size of the two rectangles, you schoul put the geometry reader in the Stack of the rectangle and the text.
struct GeoReader: View {
var body: some View {
GeometryReader { geo1 in
VStack {
ZStack {
Rectangle()
.foregroundColor(.blue)
Text("Heigt of full screen is \(geo1.size.height)")
}
ZStack {
GeometryReader { geo2 in
Rectangle()
.foregroundColor(.red)
.coordinateSpace(name: "Redbox")
Text("Height of red box is \(geo2.frame(in: .named("Redbox")).height)")
}
}
}
}
}
}

Related

Prevent SwiftUI Divider expanding horizontally

I have the following setup:
struct ContentView: View {
var body: some View {
VStack {
Text("Title").font(.title)
HStack {
Text("Hello:").bold()
Text("World")
}
}
.padding()
.background {
RoundedRectangle(cornerRadius: 8).stroke(.black)
}
}
}
which renders as follows:
When I add a Divider() into the VStack…
struct ContentView: View {
var body: some View {
VStack {
Text("Title").font(.title)
Divider() // Added this
HStack {
Text("Hello:").bold()
Text("World")
}
}
.padding()
.background {
RoundedRectangle(cornerRadius: 8).stroke(.black)
}
.padding()
}
}
… it forces the VStack to expand horizontally as large as possible.
How do I make the Divider fit to the other VStack content?
Having written the question, I tried some more things, and found a solution. Leaving here in case it is useful for others.
I just needed to add
.fixedSize(horizontal: true, vertical: false)
to the VStack, which makes all the subviews have the same horizontal size, resulting in…

How to keep a view always centered independently from the size of the left view

I have a simple HStack containing a few views and I would like to keep one of the views ("Center") always centered. The size of the left view can change dynamically.
struct ExperimentView: View {
var body: some View {
HStack() {
Text("A")
.background(.red)
Spacer()
Text("Center")
.background(.yellow)
Spacer()
Button("B1") {
}
Button("B2") {
}
}
}
}
The Center view is not centered and moves to the right depending on the size of Text. I have tried something using alignmentGuide but I had no success.
A possible way is to wrap the "Center" text and the other views in a ZStack
struct ExperimentView: View {
var body: some View {
ZStack {
Text("Center")
.background(.yellow)
HStack {
Text("A")
.background(.red)
Spacer()
Button("B1") { }
Button("B2") { }
}
}
}
}
But this will not prevent the objects from overlapping if A becomes too wide.

Why scaleToFill not filling the whole screen?

I would like to use an image as the background of the app.
struct DetailView: View {
var borderWidth: CGFloat = 0
var body: some View {
// ZStack: Background, ScrollView(vertical)
ZStack {
Image("background_green").resizable().scaledToFill().opacity(0.8)
.frame(width:UIScreen.main.bounds.width, height:UIScreen.main.bounds.height)
VStack {
}.frame(width: UIScreen.main.bounds.width, height:UIScreen.main.bounds.height)
}
}
}
How come there are still some white space at the top?
You should ignore safe area, like
var body: some View {
// ZStack: Background, ScrollView(vertical)
ZStack {
Image("background_green").resizable().scaledToFill().opacity(0.8)
.edgesIgnoringSafeArea(.all) // << here !!
VStack {
}.frame(width: UIScreen.main.bounds.width, height:UIScreen.main.bounds.height)
}
}

Size a SwiftUI view to be safeAreaInsets.top + 'x'

I am trying to create a full bleed SwiftUI 'header' view in my scene. This header will sit within a List or a scrollable VStack.
In order to do this, I'd like to have my text in the header positioned below the safe area, but the full view should extend from the top of the screen (and thus, overlap the safe area). Here is visual representation:
V:[(safe-area-spacing)-(padding)-(text)]
here is my attempt:
struct HeaderView: View {
#State var spacing: CGFloat = 100
var body: some View {
HStack {
VStack(alignment: .leading) {
Rectangle()
.frame(height: spacing)
.opacity(0.5)
Text("this!").font(.largeTitle)
Text("this!").font(.headline)
Text("that!").font(.subheadline)
}
Spacer()
}
.frame(maxWidth: .infinity)
.background(Color.red)
.background(
GeometryReader { proxy in
Color.clear
.preference(
key: SafeAreaSpacingKey.self,
value: proxy.safeAreaInsets.top
)
}
)
.onPreferenceChange(SafeAreaSpacingKey.self) { value in
self.spacing = value
}
}
}
This however, does not seem to correctly size 'Rectangle'. How can I size a view according to the safe area?
Is this what you're looking for? I try to avoid using GeometryReader unless you really need it... I created a MainView, which has a background and a foreground layer. The background layer will ignore the safe areas (full bleed) but the foreground will stay within the safe area by default.
struct HeaderView: View {
var body: some View {
HStack {
VStack(alignment: .leading) {
Text("this!").font(.largeTitle)
Text("this!").font(.headline)
Text("that!").font(.subheadline)
}
Spacer(minLength: 0)
}
}
}
struct MainView: View {
var body: some View {
ZStack {
// Background
ZStack {
}
.frame(maxWidth:. infinity, maxHeight: .infinity)
.background(Color.red)
.edgesIgnoringSafeArea(.all)
// Foreground
VStack {
HeaderView()
Spacer()
}
}
}
}
add an state to store desired height
#State desiredHeight : CGFloat = 0
then on views body :
.onAppear(perform: {
if let window = UIApplication.shared.windows.first{
let phoneSafeAreaTopnInset = window.safeAreaInsets.top
desiredHeight = phoneSafeAreaTopnInset + x
}
})
set the desiredHeight for your view .
.frame(height : desiredHeight)

Overlay with relative Width

Xcode 11 Beta 4 deprecated .relativeSize as well as .relativeWidth and .relativeHeight (see this related post).
So what is the alternative?
I want to create an overlay that has a width relative to it's parent.
Let's say I have the following main view
struct MainView: View {
var body: some View {
ZStack(alignment: .topLeading) {
BackgroundView()
SideBarView()
.frame(idealWidth: 200)
.fixedSize(horizontal: true, vertical: false)
}
}
}
With a simple BackgroundView and SideBarView as well those work as expected.
struct SideBarView: View {
var body: some View {
Rectangle()
.foregroundColor(.green)
}
}
struct BackgroundView: View {
var body: some View {
Rectangle()
.foregroundColor(.red)
}
}
This was suggested in the release notes and this answer.
How can I avoid to hardcode those values as I could before by using .relativeWidth(0.3) instead of .frame(idealWidth:)?1
1Note: Using .relativeWidth never actually worked, e.g. using 0.3 as a relative value never resulted in a view that was 30 % of the width of the parent, but you could get close to your desired result through trial-and-error.
There are multiple ways to achieve it, one way is using .overlay instead of ZStack. The view you use in the overlay, will get the size of the BackgroundView offered by the parent. Then you simply use GeometryReader to get the width and multiply it by 0.7:
struct ContentView: View {
var body: some View {
BackgroundView().overlay(SideBarView())
}
}
struct SideBarView: View {
var body: some View {
GeometryReader { proxy in
HStack {
Spacer()
Rectangle()
.frame(width: proxy.size.width * 0.7)
.fixedSize(horizontal: true, vertical: false)
.foregroundColor(.green)
}
}
}
}
struct BackgroundView: View {
var body: some View {
Rectangle()
.foregroundColor(.red)
}
}