Hi community,
I have an issue where the content of a horizontal scroll view, embedded in a vertical scroll view, changes its' size while scrolling away from the initial scroll content. At some point the entire scroll content of the inner (horizontal) scroll view collapses in height. When scrolling back to the initial scroll content, the height of the scroll content is restored.
Please see the attached view to reproduce the issue.
Tested with Xcode 15.2 with Xcode Previews, iOS 17.2 Simulator and 17.2.1 on Device.
/// This view demonstates a bug that causes the content of a
/// nested ``ScrollView`` to collapse in on itself when the
/// horizontal scroll view is being scrolled.
/// The proper size of the scroll content is restored if the user
/// scrolls to the leading position of the view again.
struct CollapsedViewHierarchy: View {
@State private var images: [CatImage] = []
var body: some View {
NavigationStack {
if images.isEmpty {
ContentUnavailableView("Cats unavailable", systemImage: "cat", description: Text("Your cats are being fetched from the internet"))
} else {
ScrollView {
Text("To reproduce this bug, scroll through the list of cats. At some point the content collapses in on itself. If you scroll back to the beginning of the list, the proper size will be restored.")
.foregroundStyle(.secondary)
ScrollView(.horizontal) {
ScrollViewContent()
}
.contentMargins(12.0, for: .scrollContent)
.scrollIndicators(.hidden)
}
.navigationTitle("Cats")
.refreshable {
images = try! await fetchImages()
}
}
}
.task {
images = try! await fetchImages()
}
}
private func ScrollViewContent() -> some View {
LazyHStack {
ForEach(images, id: \.self) { image in
Tile(image: image)
.containerRelativeFrame(.horizontal, count: 2, span: 2, spacing: 20.0)
}
}
}
private func Tile(image: CatImage) -> some View {
VStack(alignment: .leading) {
AsyncImage(url: image.url) { image in
image
.resizable()
.aspectRatio(2.0, contentMode: .fill)
.clipped()
} placeholder: {
Color.gray
}
.mask(RoundedRectangle(cornerRadius: 12))
Text(image.id)
.bold()
Text(image.url.absoluteString)
.font(.footnote)
.foregroundStyle(.secondary)
}
}
private func fetchImages() async throws -> [CatImage] {
let metadataUrl = URL(string: "https://api.thecatapi.com/v1/images/search?limit=20")!
let (data, _) = try await URLSession.shared.data(from: metadataUrl)
let decoder = JSONDecoder()
return try decoder.decode([CatImage].self, from: data)
}
}
fileprivate struct CatImage: Codable, Hashable {
let id: String
let url: URL
}
#Preview {
CollapsedViewHierarchy()
}
Why does the scroll content (cat pictures in the example view) changes size while scrolling? This is not related with the loading state of the async image as it also occurs after all images have been loaded?
I greatly appreciate any feedback and suggstions how to fix this!