I have the following SwiftUI code:
Rectangle()
.fill(Color.blue.opacity(0.5))
.frame(width: 130)
.frame(maxWidth: .infinity)
.background(Color.orange.opacity(0.5))
.padding()
.frame(maxHeight:100)
Here is the output.
Clearly it can be seen that the inner rectangle in blue has been placed in the centre of outer rectangle. I tried experimenting with the alignment of frames and can see alignment of inner rectangle can only be changed by altering frame alignment of outer rect as follows:
.frame(maxWidth: .infinity, alignment: .trailing)
But nothing changes if I change frame alignment property of inner rectangle.
I want to understand this behaviour more clearly -- whether view is centred by default, and why inner frame alignment modification changes nothing.
The way layout works in SwiftUI is that parent views proposes a size to their children, and the children chooses how large they want to be, based on the proposal. Note that some views can ignore the proposal completely.
From the documentation, what frame(width:height:alignment:)
does is,
Positions this view within an invisible frame with the specified size.
We can understand this as proposing a specific size to the view that frame
is modifying. After the view chooses its size based on the proposal, it is aligned in the frame based on alignment
. The default value of the alignment
parameter is .center
, so that's why views are centered in the frame by default.
The way a Rectangle
chooses its size, is that it resizes itself to the same size as the proposal (all Shape
s behave like this). Therefore, it will always cover the entire frame
you specified. This is why changing the alignment
of the first frame
modifier does not do anything - a view that covers the entire frame looks the same no matter how it is aligned inside the frame.
The frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:)
modifier also positions the view it is modifying in a frame, but it gives the size proposal differently. It takes the size proposal it received from its parent, and clamps it to the mins and maxes.
So in this case,
.frame(maxHeight:100)
receives the size proposal and creates an invisible frame with the screen's width and height of 100pt. It changes the size proposal's height to 100 and passes it down..padding()
receives the size proposal and chooses its size to be the same as the proposed size. It further reduces the size proposal's width and height by some system-defined padding amount and passes the proposal down..frame(maxWidth: .infinity)
receives the size proposal and adds an invisible frame of the same size as the size proposal. (The orange .background
is added to this frame.) It doesn't change the size proposal and passes it down..frame(width: 130)
receives the size proposal and ignores the proposed width. It creates an invisible frame with width 130pt and same height as the size proposal. The size proposal's width is now changed to 130pt.Rectangle
receives the size proposal and chooses its size to be 130pt wide.Notably, the frame added by .frame(width: 130)
is less wide than the frame added byc.frame(maxWidth: .infinity)
, so changing the alignment
parameter of the wider frame from .center
to .trailing
visibly makes a difference.