AsyncImage with a placeholder sizing to fit to aspect ratio and clipping

2.2k Views Asked by At

I'm trying to create an AsyncImage with a placeholder image. With the following constraints...

  1. The placeholder image may not have the same aspect ratio as the AsyncImage.
  2. The remote image may not have the same aspect ratio as the AsyncImage.
  3. The remote and placeholder image may not have the same aspect ratios.
  4. The aspect ratio of the AsyncImage is fixed.

The requirements...

  1. The placeholder and the remote image must be scaled to fill the aspect ratio of the AsyncImage.
  2. They must not be squashed.
  3. They must be clipped so that they do not overflow the bounds of the AsyncImage (set by the aspect ratio).

In my head this should be simple.

But I just cannot get this to work without at least one of the requirements breaking.

This is the closest I've got so far but the AsyncImage is just not clipped so overflows beyond either the height or width.

You can paste this into Xcode and it will show the

import SwiftUI

let aspect = 1.5

struct TestView: View {
    var body: some View {
        AsyncImage(
            url: URL(string: "https://placekitten.com/1920/1080"),
            transaction: .init(animation: .easeIn(duration: 3)) // <- slow animation to show issue
        ) { phase in
            switch phase {
            case .success(let image):
                image
                    .resizable()
                    .scaledToFill()
                    .aspectRatio(aspect, contentMode: .fill)
                    .clipped()

            default:
                Image(systemName: "rectangle")
                    .resizable()
                    .scaledToFill()
                    .aspectRatio(aspect, contentMode: .fill)
                    .clipped()
                    .foregroundColor(.white)
            }
        }
        .aspectRatio(aspect, contentMode: .fill)
    }
}

struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        VStack {
            TestView()
                .overlay {
                    // This overlay shows the correct rect but the image is just not cclipped.
                    Color.black
                        .opacity(0.5)
                        .aspectRatio(aspect, contentMode: .fit)
                }
                .padding()
                .aspectRatio(aspect, contentMode: .fit)
        }
        .background(Color.red)
        .padding()
    }
}

This gives me the following...

enter image description here

The red background is from the VStack. The black transparent overlay shows the aspect ratio I'm looking for based on the aspect set at the top of the file.

1

There are 1 best solutions below

2
On BEST ANSWER

have you tried using a geometry reader to set the height and width of the image container?

You can use this to work out the required ratio. That's the approach I would take.