How to replicate UIImageView's `scaleAspectFill` for Images inside a custom Layout?

I've defined a custom layout container by having a struct conform to Layout and implementing the appropriate methods, and it works as expected.

The problem comes when trying to display Image, as they are shown squished when just using the .resizable() view modifier, not filling the container when using .scaledToFit() or extending outside of the expected bounds when using .scaledToFill().

I understand that this is the intended behaviour of these modifiers, but I would like to replicate UIImageView's scaledAspectFill.

I know that this can usually be achieved by doing:

Image("adriana-wide")
   .resizable()
   .scaledToFill()
   .frame(width: 200, height: 200)
   .clipped()

But hardcoding the frame like that defeats the purpose of using the Layout, plus I wouldn't have direct access to the correct sizes, either.

The only way I've managed to make it work is by having a GeometryReader per image, so that the expected frame size is known and can bet set, but this is cumbersome and not really reusable.

GalleryGridLayout() {
        
        GeometryReader { geometry in
            
            Image("adriana-wide")
                .resizable()
                .scaledToFill()
                .frame(width: geometry.size.width, height: geometry.size.height)
                .clipped()
        }

       [...]
}

Is there a more elegant, and presumably efficient as well as reusable, way of achieving the desired behaviour?

Here's the code of the Layout.

Answered by DTS Engineer in 795604022

@Thecafremo Sorry for the delayed response, I was looking into this some more.

Using the GeometryReader is one approach, the other would be to reach down to UIKit and use a UIImageView and wrap that in a UIViewRepresentable.

Please file a bug report using Feedback Assistant for an API that allows you scale a View to an aspect ratio without stretching it.

@Thecafremo Since an aspect ratio isn't provided, it uses the intrinsic aspect ratio.

You cold try using this instead:


image(...)
    .resizable()
    .scaledToFill()
    .aspectRatio(1.0, contentMode: .fill)
    .clipped()

@DTS Engineer – This doesn't work, I'm afraid. Adding the .aspectRatio(1.0, contentMode: .fill) makes no difference – I assume because we're doing .scaledToFill() before, which is the same as doing .aspectRatio(nil, contentMode: .fill)

@Thecafremo Sorry for the delayed response, I was looking into this some more.

Using the GeometryReader is one approach, the other would be to reach down to UIKit and use a UIImageView and wrap that in a UIViewRepresentable.

Please file a bug report using Feedback Assistant for an API that allows you scale a View to an aspect ratio without stretching it.

Accepted Answer

@DTS Engineer – Thanks for the response. I had considered those options but weren't ideal.

I found a slightly better approach, though.

As the expected behaviour is to fill all the available space with the image, we can just use Color (which, when used as a view, expands to fill all the space it is given) and the Image as an overlay.

This will effectively make the Image expand to use all the available space whilst clipping it to its bounds.

Color.clear
     .overlay {
                    
          Image("adriana-wide")
               .resizable()
               .scaledToFill()
      }

Not quite the solution I was expecting, but does the trick.

How to replicate UIImageView's `scaleAspectFill` for Images inside a custom Layout?
 
 
Q