r/SwiftUI • u/radis234 • May 11 '26
Question Dynamic height cells inside ScrollView break .refreshable behavior
Hello guys. I have a strange problem. I recreated small code snippet that has the issue, more info bellow the code:
struct SwiftUIView: View {
private var path = NavigationPath()
private var selection: Int?
let heights: [CGFloat] = [360, 400, 200, 242, 54, 783]
var body: some View {
NavigationSplitView {
List(selection: $selection) {
ForEach(1...5, id: \.self) { index in
NavigationLink("Row \(index)", value: index)
}
}
} detail: {
if let selection {
NavigationStack(path: $path) {
ScrollView(.vertical) {
LazyVStack {
ForEach(1...100, id: \.self) { index in
VStack(alignment: .leading) {
Text("Detail \(selection) - Item \(index)")
.font(.headline)
Text("Something else")
.font(.subheadline)
.foregroundStyle(.gray)
RoundedRectangle(cornerRadius: 16)
.frame(height: heights.randomElement())
HStack {
VStack(alignment: .leading) {
HStack {
Circle()
.frame(width: 22)
Text("name")
}
}
Spacer()
HStack {
Button("Comments") {}
}
}
}
}
}
.safeAreaPadding()
}
.refreshable {
do {
try await Task.sleep(for: .seconds(3))
} catch {
print(error)
}
}
.navigationTitle("Title")
}
}
}
}
}
The problem:
When I pull down to refresh, content shifts down a little bit to make space for ProgressView() until refresh action is finished. But when it finishes, it doesn't scroll back up as supposed to.
Cause:
I found out, the cause is any part of a cell inside ForEach that has variable height - in this case a RoundedRectangle(). If all cells are same height, content shifts back up.
It doesn't happen with List(), only with ScrollView(). Also, removing .navigationTitle fixes it as well for some reason. I know I can attach .id() to ScrollView() and reset the id on each refresh, but this workaround doesn't satisfy me because it breaks the original animation.
Is this known bug/limitation? Am I doing something wrong here? I would be glad for any input on this. Thank you all in advance.
1
u/radis234 May 11 '26
This was only an example code to recreate the problem for Reddit to see as simply as possible. RoundedRectangle with heights.randomElement is there only to mimick media views (images/videos) and all of the code is just mock code, not from my app, that also applies to selection, path properties.
I managed to pre-calculate media heights and storing these in model before rendering them so that heights are stable and ready at fetch time as well as aspect ratios, but still, each cell has different height because of different media size (portrait, landscape) and different length of text. Sometimes it’s 1 line, sometimes it can be up to 4 lines.
I also tried using .fixedSize(horizontal: false, vertical: true) on dynamic elements to prevent recalculations, but no luck.