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!