TL;DR:
I can't draw an image exactly onto the full screen on wide-aspect (13:6) phones. If I observe the safe area, the error is (predictably) underscan. Using .edgesIgnoringSafeArea()
goes (unexpectedly) too far in the other direction.
Update
Apple DTS have suggested this is a bug, refunded me one support incident, and invited me to submit a bug report. It is in the pipeline at https://feedbackassistant.apple.com/feedback/8192204
Caveat Lector
My presumptions about .scaledToFill
might be wrong. I address that at the end.
Code
So elementary I can put it here and it won't even slow you down
struct ContentView: View {
var body: some View {
Image("testImage").resizable().scaledToFill()
// .edgesIgnoringSafeArea(.all)
}
}
Test Image
The Test Image is a landscape rectangle, proportioned at 13:6, like the wide phone. (E.g. the 812:375 proportion of the original iPhone X.) The gray periphery is not part of the image.
It has its sub-frames marked, that correspond to the narrow (older) phones (16:9) and pads (4:3).
Runtime Results
The Xcode project settings are explicitly landscape-only, for both pads and phones.
For narrow phones and all pads, the code above, observing safe areas, renders the Test Image like I expect:
But on wide phones, I can't get the red rect to coincide with the screen edges.
Wide Phones
With no call to .edgesIgnoringSafeArea()
, that is we are observing safe area. Naturally, our image is mapped to a subset of the full screen.
With the call to .edgesIgnoringSafeArea()
. I expected this to exactly fill the screen but it overscans:
Here is the Xcode view-hierarchy debugger's perspective on the previous: the image is being mapped to a rect larger than the full screen. Why?
Order of Events
If I reverse the order of modifiers, and call .edgesIgnoringSafeArea()
before .scaledToFill()
, I get aspect ratio distortion, which .scaledToFill()
is supposed to prevent. (See circle become ellipse in screen shot.) An explanation of how these operations compose, and why they do not commute, might go a long way to answering my primary questions.
Workaround
I think the above should work, and I don't see why not. What does work — on wide phones — is to eliminate the .scaledToFill
modifier. Then you get this. But it only works because the test image is already the exact aspect ratio as the display — not a very general solution.
Scale to Fill
In the restricted domain of landscape images and displays, I expect the operation of scale-to-fill on the 13:6 test image to be equivalent to (to have the semantics of):
By default, SwiftUI's Image views automatically size themselves to their image contents. If the image is larger than the device screen, it will go beyond it.
Once again, you can use the PREVIEW button to see what the above code sample looks like when rendered. So applying the frame modifier with either an infinite max width or max height can be a great way to tell a given SwiftUI view to stretch itself to fill all available space on either the horizontal or vertical axis.
SwiftUI’s built-in frame modifier can both be used to assign a static width or height to a given view, or to apply “constraints-like” bounds within which the view can grow or shrink depending on its contents and surroundings. At the very basic level, this is what two common usages of the frame modifier could look like:
To make an image scales to fit the current view, we use the resizable () modifier, which resizes an image to fit available space. We can see the whole image now, but it got stretched out and losing its aspect ratio, which is usually not the behavior we want.
I do not know why .edgesIgnoringSafeArea() does not work as it should but here is a workaround that should help you.
GeometryReader { geo in
Image("testImage")
.resizable()
.scaledToFill()
.frame(width: geo.size.width, height: geo.size.height)
}
.edgesIgnoringSafeArea(.all)
Update: Here is another way to do the same thing without GeometryReader:
Image("testImage")
.resizable()
.scaledToFill()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With