SwiftUI: Layout & Interfaces

Nov 18 2021 Swift 5.5, iOS 15, Xcode 13

Part 2: Aligning Views

17. Challenge: 3-Axis Layout

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: 16. GeometryReader Next episode: 18. Conclusion

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

Welcome to you final SwiftUI layout challenge!

For this one, I got inspired by a DuoLingo app again: this time, it was Tinycards. They key is that it’s got a fun little badge on top of another view.

So your challenge is to go from this…

…to this! You can restrict the frame of the cat background to be 300 points wide. But the Swift badge has to be one third the width of the cat. And the badge has to scale, if the cat scales.

There’s more than one way to pin a badge on a cat, as they say. You should have at least one in mind, if you’ve followed this course. Good luck!

How’d it go? Your code for this one could have been simple, or complex. As long as it looks good, it is good. My* first step was to convert the HStack into a GeometryReader.

  var body: some View {
    GeometryReader { proxy in
      Image("Catground")

Then, I embedded an Image in a Stack…

moved the other image into that stack and changed it to be a ZStack.

    GeometryReader { proxy in
      ZStack {
        Image("Catground")
          .resizable()
          .scaledToFit()

        Image("Badge")
          .resizable()
          .scaledToFit()
      }
    }
      .frame(width: 300)

Then, I set the frame width of the badge to be a third of the cat’s.

          .scaledToFit()
          .frame(width: proxy.size.width / 3)
      }

And set the alignment at bottomTrailing.

ZStack(alignment: .bottomTrailing) {

And that’s almost there, but the reference image had the badge a little farther outside. I accomplished that by using padding, a tenth the width of the badge.

          .frame(width: proxy.size.width / 3)
          .padding(proxy.size.width / 30)
      }

…except, I made the padding negative!

.padding(-proxy.size.width / 30)

If you didn’t know about that trick, you still could have accomplished the offset using alignment guides. As long as everything scales nicely…

.frame(width: 100)

…you’re golden.

.frame(width: 300)

And while I was hoping this would be a good chance for you to get practice with a ZStack, it’s not actually necessary.

    GeometryReader { proxy in
      Image("Catground")
        .resizable()
        .scaledToFit()

      Image("Badge")
        .resizable()
        .scaledToFit()
        .frame(width: proxy.size.width / 3)
        .padding(-proxy.size.width / 30)
    }

Instead, you could overlay the badge.

        .overlay(
          Image("Badge")
            .resizable()
            .scaledToFit()
            .frame(width: proxy.size.width / 3)
            .padding(-proxy.size.width / 30),
          alignment: <#T##Alignment#>)

Because it has an overload that takes an alignment!

        .scaledToFit()
        .overlay(
          Image("Badge")
            .resizable()
            .scaledToFit()
            .frame(width: proxy.size.width / 3)
            .padding(-proxy.size.width / 30),
          alignment: .bottomTrailing
        )
    }

But I’m pretty sure there’s no simpler way to do it, not using a GeometryReader. But let me know in the comments if you find one!