A common layout desire is to place views in the corner of a larger view, or of the device. This is particularly interesting because it’s more instructive to visit the methods that don’t work and explain why, before showing the best way to do it.
Our desired result is as follows:
We’ll concentrate first on the problem of putting the dragoon Image
in
the bottom-right, since once that’s solved, the others are easy.
Having read about flexible frames and infinite frames, stacks, and alignments, our first attempt might be to use an alignment of a z-axis stack, and place that in an infinite frame:
struct ContentView: View {
var body: some View {
// ⚠️ This is an example that does not work.
ZStack(alignment: .bottomTrailing) {
Image("dragoon")
}
.frame(minWidth: 0, maxWidth: .infinity,
minHeight: 0, maxHeight: .infinity)
}
}
But that means we skipped views choose their own
sizes, where we
learned that .frame
creates a new view and positions the modified view
inside it. And that’s what happens:
The Image
has a fixed size, and the ZStack
has only the minimum size
necessary to contain its children. The frame around it has the full size
and positions the ZStack
inside it, centered because that’s the
default of the frame.
So since we can’t use a .frame
around the ZStack
to change the size
of it, what can we do?
We can put the infinite frame inside the ZStack
, around the Image
:
struct ContentView: View {
var body: some View {
// ⚠️ This is an example that does not work.
ZStack(alignment: .bottomTrailing) {
Image("dragoon")
.frame(minWidth: 0, maxWidth: .infinity,
minHeight: 0, maxHeight: .infinity)
}
}
}
I refer to this trick as exploding the stack, the stack has the minimum
size of its children, but its child is the frame around the Image
and
that causes it to be sized as large as it can be:
The ZStack
now fills the entire device, and the Image
is still fixed
in size within it, but we’re still having an issue with the Image
being centered rather than aligned.
This might seem like a surprise since the ZStack
has a specified
alignment of .bottomTrailing
, but what we’ve missed here is that the
child of stack is now the frame causing it to explode out; the Image
is being positioned by the .frame
and not the ZStack
.
What we need to do is move the alignment
to the .frame
:
struct ContentView: View {
var body: some View {
ZStack {
Image("dragoon")
.frame(minWidth: 0, maxWidth: .infinity,
minHeight: 0, maxHeight: .infinity,
alignment: .bottomTrailing)
}
}
}
Since the ZStack
has just one child, its alignment isn’t important, in
fact the stack is not even necessary at all, but we’ll keep it in since
we’re just aligning one view out of four.
This does exactly what we want.
Now we can bring back the other three Image
views, using the ZStack
to overlay them together, but specifying different alignment
for each:
struct ContentView: View {
var body: some View {
ZStack {
Image("brawler")
.frame(minWidth: 0, maxWidth: .infinity,
minHeight: 0, maxHeight: .infinity,
alignment: .topLeading)
Image("rogue")
.frame(minWidth: 0, maxWidth: .infinity,
minHeight: 0, maxHeight: .infinity,
alignment: .topTrailing)
Image("barbarian")
.frame(minWidth: 0, maxWidth: .infinity,
minHeight: 0, maxHeight: .infinity,
alignment: .bottomLeading)
Image("dragoon")
.frame(minWidth: 0, maxWidth: .infinity,
minHeight: 0, maxHeight: .infinity,
alignment: .bottomTrailing)
}
}
}
Each Image
has its fixed size, and each is surrounded by a .frame
that explodes out the surrounding ZStack
to the proposed size of its
own parent. Alignment is specified for each frame individually, to align
the Image
within it: