SwiftUI: Layout & Interfaces

Nov 18 2021 Swift 5.5, iOS 15, Xcode 13

Part 2: Aligning Views

16. GeometryReader

Episode complete

Play next episode

Next
Save for later
About this episode
See versions

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 15. ZStacks Next episode: 17. Challenge: 3-Axis Layout

This course was originally recorded in 2019. It has been reviewed and all content and materials updated as of October 2021.

With what you know about Stacks in SwiftUI by now, you have access to …gigawatts of power. But there’s one more tool I want to leave you with, that pairs well with Stacks: the GeometryReader.

It’s a different sort of container view. Let’s revisit the first project from this course, to see when and why you might want to use it.

Children of a Stack will come up with some reasonable way to be laid out, with very little input from you. You can provide more guidance, say, by defining a frame width (or height) for one of the views.

        .scaledToFit()
        .frame(width: 250)
      Text("Reading is dreaming with open eyes.")

But hardcoding frames, especially nested in hierarchies, is bound to cause trouble, across various screen sizes and accessibility settings. For example, watch what happens if you try to give the parent Stack the same width.

        .scaledToFit()
    }
      .frame(width: 250)
  }

The other views don’t even have any room to show up! A better way to think about this problem might be to define the frame of the Image as a percentage of the HStack. A GeometryReader will make that possible.

The easiest way I know of to wrap what you’ve got in one of those is to first, put the HStack in a Group…

    Group {
      HStack {
        Image("Cake VStack")
          .resizable()
          .scaledToFit()
          .frame(width: 250)
        Text("Reading is dreaming with open eyes.")
        Image("Pancake VStack")
          .resizable()
          .scaledToFit()
      }
      .frame(width: 250)
    }

…and then change Group, which you don’t want, to GeometryReader, which you do.

  var body: some View {
    GeometryReader {
      HStack {

Then, to keep compiling, you’ll just need to add a closure parameter. Which we’ll call proxy.

GeometryReader { proxy in

GeometryReaders are views. They don’t render anything, but they do have sizes. Our “proxy” parameter is an instance of the GeometryProxy structure. Which is what provides access to the size of a corresponding GeometryReader View.

Because you’ve got a closure with a single parameter, and “size”, in this context, means width and height, working with GeometryProxy should feel a little familiar. It’s similar to ViewDimensions, which you know from Alignment Guides.

So now, instead of hard-coding a frame width number for the Image view, define it as being one third the width of the reader. Get the proxy’s size…

.frame(width: proxy.size)

And from that, you can get its width, and scale it.

.frame(width: proxy.size.width * 0.5)

Now that is half of the reader’s width. The reason it looks so bad is that the reader is taking up the whole safe area and the HStack’s frame is smaller than that. Where we actually would want to set a frame size is on the reader itself.

          .scaledToFit()
      }
    }
      .frame(width: 250)
  }

And now, the Image is actually half the width of the stack, as desired. Get rid of the frame modifier…

          .scaledToFit()
      }
    }

…and that continues to be true! That takes care of everything that I wanted to tell you about GeometryReader. But one tip, before we move on:

If you ever find that you’re getting more word wrapping than you’d like, when defining the frames for stacked views, you can increase the layout priority for the offending Text.

        Text("Reading is dreaming with open eyes.")
          .layoutPriority(1)
        Image("Pancake VStack")

Anything else in the stack that doesn’t have a frame modifier, will shrink as necessary.