When a ScrollView is nested in a TabView, you can press on the tab button and the scroll view will scroll to top.
import SwiftUI
struct SwiftUIView: View {
let items = (1...100).map { "Item \($0)" }
var body: some View {
TabView {
Tab("home", systemImage: "house") {
ScrollView {
ForEach(items, id: \.self) { item in
Text(item)
.frame(maxWidth: .infinity, alignment: .center)
}
}
}
}
}
}
#Preview {
SwiftUIView()
}
If I modify this code to set a background color on the ScrollView, this scroll-to-top functionality breaks.
import SwiftUI
struct SwiftUIView: View {
let items = (1...100).map { "Item \($0)" }
var body: some View {
TabView {
Tab("home", systemImage: "house") {
ScrollView {
ForEach(items, id: \.self) { item in
Text(item)
.frame(maxWidth: .infinity, alignment: .center)
}
}
// Set background on ScrollView.
.background(Color.red)
}
}
}
}
#Preview {
SwiftUIView()
}
This is a really strange problem. At first I thought it might be related to the safe area insets. However, I found that when any visible view is added to the background of the ScrollView
then it breaks the scroll-to-top behavior, even if it's just a simple Text
view.
As a workaround, you can nest the ForEach
inside a VStack
(or LazyVStack
) and then add a background color to the stack.
It seems that the nested content is not in contact with the safe area inset, so adding .ignoresSafeArea()
has no effect and the color doesn't reach the top of the screen. To workaround this problem too, you can add negative vertical padding to the background. The negative padding needs to be quite large, so that when the content inside the ScrollView
is pulled down, the color fills the gap.
ScrollView {
VStack { // or LazyVStack
ForEach(items, id: \.self) { item in
Text(item)
.frame(maxWidth: .infinity, alignment: .center)
}
}
.background {
Color.red
.padding(.vertical, -500)
}
}