RMP
RMP

Reputation: 5391

Constraint-based layout of images in a list in SwiftUI

I want to achieve the following constraint-based layout of images in a SwiftUI List:

enter image description here

What I have tried and doesn't work (based on this article):

struct MyView: View {
  @ObservedObject var viewModel: MyViewModel
  let aspectRatio = CGSize(width: 345, height: 120)

  var body: some View {
    List {
      ForEach(viewModel.items) { item in
        GeometryReader { geo in
          Image("test_image")
            .resizable()
            .aspectRatio(aspectRatio, contentMode: .fill)
            .frame(width: geo.size.width)
            .clipped()
        }
      }
    }
  }
}

The size I get from geo is (343, 32) on iPhone 11 Pro. Width makes sense but it's not letting the cells expand beyond a height of 32 for some reason. Any tips welcome because I'm really starting to miss auto layout constraints.

Upvotes: 1

Views: 935

Answers (1)

aheze
aheze

Reputation: 30426

No need to use GeometryReader for something like this. For the fixed height, you can just supply a frame with height only. You also don't need to create your own let aspectRatio = CGSize(width: 345, height: 120) - if you leave it nil (by default) it should be fine.

Edit: Using padding instead of VStack with spacing

struct MyView: View {
    var body: some View {
        List {
            ForEach(0..<10, id: \.self) { item in
                Image("test_image")
                    .resizable()
                    .aspectRatio(contentMode: .fill) /// no need for custom aspect ratio
                    .frame(height: 120) /// fixed height of image
                    .clipped() /// stop image from overflowing
                    .padding(.vertical, 12) /// extra vertical padding
            }
        }
    }
}

Result (with "test_image"):

Rows of images with fixed height, image stretches to fit container while keeping the aspect ratio

However, this has a fixed height of 120, so the top and bottom of the images are cropped out. To fix this, you can just avoid frame and clipped altogether.

struct MyView: View {
    var body: some View {
        List {
            ForEach(0..<10, id: \.self) { item in
                Image("test_image")
                    .resizable()
                    .aspectRatio(contentMode: .fill) /// doesn't matter if it's fit or fill
                    .padding(.vertical, 12) /// extra vertical padding
            }
        }
    }
}

Result:

Rows of images with dynamic height, image determines the height of the container while keeping the aspect ratio

Upvotes: 1

Related Questions