Consider the following (in a landscape-only app):
struct ContentView: View {
var body: some View {
ZStack {
Rectangle()
.foregroundColor(.blue)
.frame(width: UIScreen.main.bounds.size.width * 0.9, height: UIScreen.main.bounds.size.height * 0.9)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea(.all)
.background(.gray)
}
}
Why is the rectangle not centered? Why is there more space on the right than on the left? Happens on a device too.

This is an interesting one. If you change the scaling factor from 0.9 to 0.8 then the blue area is centered perfectly. But when you set a frame size that needs to use the safe areas to fit, the position is not centered.
I did some measurements of the screen size of an iPhone 15 in landscape mode (which is what you appear to be using in your screenshot):
The sizes delivered by the (deprecated)
UIScreen.main.bounds.sizeare the full screen sizes. So when a factor of 0.9 is applied, the size of the blue area is being fixed at 766.8 x 353.7. This fits within the available height (of 372), but not within the available width (of 734). The excess width is 32.8 pt.It is perhaps useful to note that the gray background is giving the impression that the
ZStackis filling the screen, but this is not the case. You have applied the background using.background(.gray), which ignores safe areas by default - see background(_:ignoresSafeAreaEdges:). If you change it to.background { Color.gray }then safe areas are not ignored and the background shows you the exact size and position of theZStack. The result is quite interesting:What we see is:
ZStackis still observing the safe areas as far as possible.ZStackexactly matches the width of the blue area, because the blue content is forcing it to be wider than the area within the safe insets. So theZStackhas expanded into both the leading and trailing safe areas by half the excess width or 16.4 pt on both sides.ZStack. This content is being vertically centered within the full screen height, so the gap to the bottom of the screen is (393 - 353.7) / 2 = 19.65. This is slightly less than the bottom safe inset of 21.I think that what is happening is that the
ZStackis calculating the horizontal offset for the content based on the assumption that theZStackis in its default position. So the relative offset is half the excess width or 16.4 pt. But since theZStackitself is already displaced by this much, it means the blue content has a double displacement. The result is that the correction for the excess width is fully applied on the leading side and the gap on the trailing side is the default safe area inset.Workarounds
1. Only ignore safe areas on the top and bottom edges
The effect of this change is to stop the
ZStackfrom applying an offset to the content, so the horizontal position of theZStackbecomes the horizontal position of the content too (they are horizontally aligned and also horizontally centered).This workaround is based on the assumption that the leading and trailing safe area insets are always matched. However, this might not be the case on all devices.
2. Apply maximum size to the
ZStackagain after the modifier.ignoresSafeAreaThe first frame modifier on the
ZStackextends theZStackup to the safe areas. After ignoring safe areas, the second frame modifier extends theZStackto the screen size.3. Apply max size and
ignoresSafeAreato the blue rectangleA frame with max size no longer needs to be set on the
ZStack, because its size is dictated by the content. But theZStackstill needs to ignore safe areas.4. Use a
GeometryReaderas the parent containerUsing a
GeometryReaderis a better way of getting the screen size (becauseUIScreen.mainis deprecated and it doesn't work properly with split screen on iPad). If the modifier.ignoresSafeAreais moved from theZStackto theGeometryReaderthen theZStackextends to the full screen size when the frame with max size is applied:I would suggest, this is the best workaround.
All workarounds give the same result: