iosswiftui

SwiftUI view centring behaviour on using new frame modifier


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.

enter image description here

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.


Solution

  • 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 Shapes 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,

    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.