Search code examples
iosswiftiphoneswiftui

SwiftUI Image scaledToFill() takes entire screen width


I'm new to SwiftUI and I'm trying to implement a card :

Desired layout

I want the image to be 124 units tall. Everything works fine until I try to add scaledToFill() to the Image.

Here is my code:

    var body: some View {
        VStack {
            Spacer()
            
            VStack (spacing: 0) {
                Image(uiImage: Asset.Assets.licencePerksBannerCover.image)
                    .resizable()
//                    .scaledToFill()
                    .frame(height: 124)
                
                Text("Title")
            }
            .background(Color.white)
            
            Spacer()
        }
        .padding()
        .background(Color.green)
    }

This is what it looks like without when scaledToFill() is commented: Layout with scaledToFill() commented As you can see, the image is stretched, but the card itself respects the padding that I've given to the main VStack.

This is what it looks like when I uncomment scaledToFill(): Layout when uncommenting scaledToFill() The image doesn't stretch anymore but now the card takes the entire width of the phone.

If I also give a width of 50 to the Image, the main green stack becomes 50 units wide as expected, but the image still takes all the width of the phone:

    var body: some View {
        VStack {
            Spacer()
            
            VStack (spacing: 0) {
                Image(uiImage: Asset.Assets.licencePerksBannerCover.image)
                    .resizable()
                    .scaledToFill()
                    .frame(width: 50, height: 124)
                
                Text("Title")
            }
            .background(Color.white)
            
            Spacer()
        }
        .padding()
        .background(Color.green)
    }

Layout when giving the image a width

I don't understand why scaledToFill() makes the image take the entire width of the screen. I couldn't find any explanation online on the reason it does this and how to fix it.


Solution

  • The modifier .scaledToFill() causes the image to overflow the frame you are setting, so it needs to be clipped. If it is not clipped, the overflow remains visible.

    If you only know one dimension of the frame (the height in this case), then you can use a greedy view such as a Color to fill the available space. The image can then be shown as an overlay and clipped:

    VStack (spacing: 0) {
        Color.clear
            .frame(height: 124)
            .overlay {
                Image(uiImage: Asset.Assets.licencePerksBannerCover.image)
                    .resizable()
                    .scaledToFill()
            }
            .clipped()
    
        Text("Title")
    }
    .background(Color.white)
    

    Looking at your first image, I am wondering whether in fact you might want to be using scaledTofit for wide images, instead of scaledToFill? If you are not sure whether an image will be wide or tall then you can use ViewThatFits(in: .horizontal) to try scaledToFill first, followed by scaledToFit as fallback. The answer to Image is being rendered with 1/3 of screen width shows how.