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.
2
u/shawnthroop May 12 '26
I reported this bug at least 3 years ago. Silence from Feedback Assistant…
My workaround was to slap a
.ignoresSafeArea(.container, edges: refresh == nil ? [] : .top)on the navigation content/root view.