How to correctly change the aspect ratio of a SwiftUI Image?
Asked Answered
A

3

5

I'm desperately trying to change the aspect ratio of an image to 16:9 without stretching it. .aspectRatio(16/9, contentMode: .fill or .fit) isn't usable because it's pure garbage (no one on Earth wants to stretch an image). Also, there is no difference between CGFloat or CGSize.

So I have first tried something like this:

Button("Button", action: { })
Image("example")
    .resizable()
    .aspectRatio(contentMode: .fill)
    .frame(width: UIScreen.main.bounds.width - x, height: (UIScreen.main.bounds.width - x) * 0.5625)
    // .border(Color.red)
    .clipped()

And it seems to be right but actually not at all, the hidden part of the image goes beyond the button. Strangely, the button is visible but of course not usable. You can see this by yourself if you uncomment .border(Color.red) and comment .clipped().

Then, I have tried the 2 solutions from: https://alejandromp.com/blog/image-aspectratio-without-frames/ but it's actually the exact same result.

Please, tell me you know how to deal with this issue? A func? A custom view? Thanks!

Ambsace answered 20/4, 2023 at 16:25 Comment(0)
U
3

After reading the article you posted, and seeing the update in it, I ended up with the following:

Image("example")
    .resizable()
    .aspectRatio(contentMode: .fill)
    .frame(
        minWidth: 0,
        maxWidth: .infinity,
        minHeight: 0,
        maxHeight: .infinity
    )
    .aspectRatio(16 / 9, contentMode: .fit)
    .clipShape(.rect)

For me, the overlay option suggested in another answer didn't work at all, and I didn't want to have to set the frame to a specific width or height or use a GeometryReader. So this allows you to set an image to fill the entire space it's given using .aspectRatio(contentMode: .fill), set the min and max width and height of the frame, then tell it to fit in a 16:9 aspect ratio.

I also read that .clipped() doesn't affect hit testing, hence the .clipShape(.rect) which, in my experience, does properly clip the hit testing area.

Unitarian answered 22/8 at 21:19 Comment(1)
I had to change .clipShape(.rect) to .contentShape(.rect) because with .clipShape other UI elements on the view stopped being clickable, with .contentShape hit testing on other elements works fine.Torto
H
2

this works

Color.clear
                            .aspectRatio(16/9, contentMode: .fit)
                            .overlay(
                                image
                                    .resizable()
                                    .scaledToFill()
                            )
                            .clipShape(RoundedRectangle(cornerRadius: 8))
Hesitate answered 15/8 at 21:32 Comment(1)
Initially, this approach didn't work for me, but now it does. Maybe I missed the .clipShape. I suppose this message is just a warning to the reader: don't skip any of this. But that should be obvious anyway. Thanks for the simple answer! I will add that you need a .contentShape(.rect) if your image is tappable and want to make sure the hit testing area is clipped similarly to the view. Otherwise, you can tap on the invisible area where the image was clipped.Unitarian
D
1

You should choose either width or height in the frame but not both and set the aspectRatio of 16/9 :

Button("Button", action: { })
Image("example")
            .resizable()
            .aspectRatio(16/9, contentMode: .fit)
            .frame(width: UIScreen.main.bounds.width - x)
            // .border(Color.red)
            .clipped()
Didynamous answered 20/4, 2023 at 21:2 Comment(1)
Unfortunately, the result is exactly the same!Ambsace

© 2022 - 2024 — McMap. All rights reserved.