TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

Why does Text return jank size?

Forums > 100 Days of SwiftUI

@Ahbee  

i was watching project 18 about GeometryReader and noticed the height of the Text looked really off

basically if I have this

var body: some View {
      GeometryReader { _ in
        Text("Hello, World!")
          .background(.red)
      }
   }

it renders likes this

my question is why is the height of the Text view so tall? Its also bottom aligning itself? If I remove the Geometry reader the height of the Text looks like what it should be

2      

When using GeometryReader it rending from x: 0 y: 0 so the background go from 0,0 to the size of the text. So it not the text that tall the background color.

You should finish the rest of the acticle to understand what it does.

2      

@Ahbee  

hmm I get that that the the geomertyReader takes all the space its given. but this looks like a bug in swiftUI. maybe drawing the geometryReader bounds will make it more clear

struct TestView2: View {
  var body: some View {
    GeometryReader {_ in
      Text("hello World")
        .background(.red)
    }.border(.green, width: 3)
  }
}

In the follwing image can you explain what the red backround represents vs the green Border?

my understanding is that green border is the space that geometryReader was given, and is also the same space that is offered to the textView. the textView then picks a bounds which should just be enough to cover the text. Then the geometry reader should position the textView in the topleft. But swiftUI just decides to render some extra red for no reason at the topleft. The red also goes outside the safeArea too which is wierd

2      

Hacking with Swift is sponsored by RevenueCat.

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

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

This is the way it currently works 😂😂😂 sounds strange but this is the reality we all living in. Sometimes this is weird but what to do. So let me try to explain what is happining here. Backgrounds (in your case red color) will ignore all safe area edges by default. Simply putting your backgound color ignores the safe area and it expands itself up. To prevent this, explicitly specify a different safe area edge to ignore - >

struct TestView2: View {
  var body: some View {
    GeometryReader {_ in
      Text("hello World")
        .background(.red, ignoresSafeAreaEdges: .bottom)
    }.border(.green, width: 3)
  }
}

2      

As for the green vs red -> green this is so called safe area where you can draw your UI. Beyond green is area needed by the system for different parts of iOS interface tabs, indicators you name it, and it is so to say unsafe. So by default all views that take up all available space do not go beyond that area unless you explicitly make it go further, using .ignoresSafeArea() modifier. As for your example you use geometry reader. When you use it, all items inside it will be placed at x: 0 y: 0 coordinates of geometry reader, not in the center as many views do. So here you created geometry reader which placed your text in the upper left corner. You placed red backround attached to text. That background ignores safe area by default and expands that color up.

2      

@Ahbee  

@ygeras even if you do .background(.red, ignoresSafeAreaEdges: .all)
It makes no sense why it goes to the top only? why doesnt the color view extend right left and bottom past the safe area?

Also In apple wwdc video they specially say background is layout neutral? Was it an oversight?

2      

@Ahbee I am trying to explain you what it does and not why it does this particluar way. With the post above I explained you that by default backgrounds ignore safe area and again you are making the same with .background(.red, ignoresSafeAreaEdges: .all). How do you expect different result when you use the code which system uses by default? By using .background(.red, ignoresSafeAreaEdges: .bottom) you are telling SwiftUI: "Hey, SwiftUI ignore only bottom safe area! But do not cross safe area on other edges!" As you text's background does not touch bottom safe area it does nothing. On the top it touches safe area and as you said to ignore only bottom safe area, the text is not allowed to ignore top, and it does NOT cross that line. Text view is so called push-in view it takes up only so much space as it needs. Color, on the contrary is push out view - cuz it doesn't know how much space it can take unless you specify it with .frame() modifier.

Put it simply if you write:

Text("Hello, world!")

Let's assume it that string takes up 100 by 50 frame - that's it it is the size of the view that will be placed in UI. You attach later background to that text view, it also takes up the same size. How do you expect to fill out all the entire area of the screen, when you said to take only 100 by 50?

Just for the sake of the experiment remove geometry reader from your code and you will see text view with red background in the middle of the screen. Right?

When you used geometry reader - it moved your text to left top. In that position, background touches the line where safe area starts, unless you say NOT to ignore it, it will cross that line and goes up.

Hope it makes some sense now :)

2      

To make it even more clearer, let me show you some more examples ->

GeometryReader { geo in
            Text("Hello, World!")
        }
        .background(.red)

If you attach background to geometry reader NOT to text view, it will fill up all available space on the screen. As background ignores safe area edges by default.

BUT if you use this ->

GeometryReader { geo in
            Group {
                Color.red
                Text("Hello, World!")
            }
        }

you will see that color fills up all the area that geometry reader takes, BUT not safe area!

2      

@Ahbee  

@ygress thanks I think Im just confused on how safeArea restictions fit in to the 3 layout rules of SwiftUI

  1. parent proposes a size
  2. child picks a size
  3. parent then positions the child in its bounds

if someone could explain what is going on in those 3 rules it would clear my understanding

take this code

 var body: some View {
    GeometryReader { geo in
      Text("Hello, World!")
        .background(.red)
    }
  }

the heiercahcy looks something like this

GeometryReader has 1 child Background. Background then has 2 children Text and Color.Red. Text and Color.red are leaf nodes

here is my understanding

  1. GeometryReader offers all safe area space to background
  2. Background then offers the same space to Text
  3. Text picks a size
  4. Background then offers color.red the space of Text, but also applies a modifier on Color.red saying it can ignore safe areas?
  5. Color.red will now pick a bounds that extends past safe areas(but it should not know it is touching a safe area because it does not know its position) 6 Backround does not care what Color.red picks and picks the same bounds as Text
  6. GeometryReader positions Backround at its top left
  7. Background positions Color.red (I have no idea how, why is Text centered and Color.red is not? they are both children but treated so diffrenty in layout)
  8. Background View positions Text in its center

    My problem is with steps 5 and 9? Because they seem impossible with just the 3 rules of swiftUI.

2      

@Ahbee let me use Paul's explanation and adjust it to your needs

If we put this into the three-step layout system, we end up with a conversation a bit like this:

  1. SwiftUI: “Hey, ContentView, you have the whole screen to yourself – how much of it do you need?” (Parent view proposes a size)
  2. ContentView: “I don’t care; I’m layout neutral. Let me ask my child: hey, geometry reader, you have the whole screen to yourself – how much of it do you need?” (Parent view proposes a size)
  3. Geometry Reader: “Well, I am container view and work as push-out view i.e. I take all availalbe space in safe area, because I am set up this way. So I need exactly X pixels width by Y pixels height, which is the whole screen in the safe area. (Child chooses its size.) But I also have a child view let me ask it as well".
  4. Geomerty Reader: "Hey red backgound how much space do you need?
  5. Background: "“I don’t care; I’m layout neutral too. Let me ask my child: hey, Text, you can have Geometry Reader that offered you X pixels width by Y pixels height – how much of it do you need?” (Parent view proposes a size)
  6. Text: “Well, I have the letters ‘Hello, World’ in the default font, so I need exactly X pixels width by Y pixels height. I don’t need the whole space you offer, just that.” (Child chooses its size.)
  7. Background: "Ok, as I return you Text as a ModifiedContent type, i will take only space as you take". (Child chooses its size. This is a child of Geometry Reader.)
  8. Background: "Hey, Geomtery Reader, I am a parent of text and we take so much space". (background with text and THIS IS IMPORATANT is one modified VIEW with type - <ModifiedContent<Text, _BackgroundStyleModifier<Color>>)
  9. Geometry Reader: “Got it. Hey, I will place you in left up corner. But as you need only that small space, I will leave rest place empty.”
  10. ContentView: “Right on. Hey, SwiftUI: I need X by Y pixels.”
  11. SwiftUI: “Nice. I place Geometry Reader inside safe area, and text view with red background in the upper left corder as you asked.” (Parent positions the childs in its coordinate space.)

AND REMEMBER that when you apply a modifier (in your case .backgound(.red)) to a view we actually get back a new view type called ModifiedContent, which stores both our original view and its modifier. This means when we apply a modifier, the actual view that goes into the hierarchy is the modified view, not the original one.

READ ON MODIFIED CONTENT HERE

When Paul uses word background in this context he means .backround(.red) modifier and nothing ELSE!

Hierarchy is as follows: ContentView -> GeomertyReader -> ModifiedContent(Text <- backround.red)

  1. ConentView is parent of GeometryReader, GeometryReader is child of ContentView
  2. GeometryReader is parent of Modified content
  3. ModifiedContent consists of backround.red which is parent of text.
  4. Text takes only space it needs, backround.red is layout neutral and takes the same space - now it is one modifed view!
  5. The rest is straightforward.

Modified content that is Text.backround(.red) knows its place and coordinates on the screen. Just try to run this and you will see coordinates in global coordinates system i.e. on the screen

 GeometryReader { geo in
            let frame = geo.frame(in: CoordinateSpace.global)
            Text("origin: X \(frame.origin.x), Y \(frame.origin.y),\n width :\(frame.size.width), height: \(frame.size.height)")
                .background(.red)
        }

And as it knows it space coordinates on the screen it can realize, ok i am touching here border of safe area, and backrounds are told to ignore that, so I will cross it :)

You are just going too much into details, it will come to you soon, that you don't always need to know how everything works in so much details. Not knowing how tv set works under the hood, does not prevent you from watchin it and switching channels. Just keep coding :) I still do not understand all the details that might be happining under the hood. I doubt there is a person who knows all.

3      

@Ahbee  

@ygeras thanks! I think I understand how it works now, with your example

 GeometryReader { geo in
            let frame = geo.frame(in: CoordinateSpace.global)
            Text("origin: X \(frame.origin.x), Y \(frame.origin.y),\n width :\(frame.size.width), height: \(frame.size.height)")
                .background(.red)
        }

it must mean that SwiftUI has already positioned the GeometryReader and all its parents in global space before the TextView or Backround even decides its bounds. That actually makes sense

2      

Hacking with Swift is sponsored by RevenueCat.

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

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.