Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUi - Filling a wide-aspect screen

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.

test 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:

first results

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.

wide phone

With the call to .edgesIgnoringSafeArea(). I expected this to exactly fill the screen but it overscans:

enter image description here

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?

enter image description here

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.

ipad 11 distortion

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.

enter image description here

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):

  1. Center the test image in the destination (container) rect, sized to fit entirely in the container.
  • I have been expecting that ignoring safe areas means the "destination" will be the full screen, but that may be where I err.
  1. Expand the test image, maintaining proportion and center, until one pair of sides coincides with those of the container.
  • For narrower displays, the left and right edges will meet first, and the top and bottom will be inside the destination rect.
  1. But don’t stop now. That would be scale to fit, or letterboxing.
  2. Expand until your top and bottom coincide with those of the container.
  • For narrower displays this means there will be content cropped on both sides
  • For 13:6 displays all four image edges will to coincide with the display edges at the same time.
like image 206
Andrew Duncan Avatar asked Jun 03 '20 03:06

Andrew Duncan


People also ask

How does SwiftUI handle images larger than the screen size?

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.

How to stretch a SwiftUI view to fill all available space?

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.

What does the frame modifier do in SwiftUI?

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:

How to resize an image to fit the current view?

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.


1 Answers

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)
like image 72
TheLegend27 Avatar answered Oct 08 '22 22:10

TheLegend27