See the code below. The setup:
- There are only 3 items in the
LazyVStack
. - The code scroll to the last item when the view is shown on the screen.
The result:
+--------------+ +--------------+
| c | | a |
| | | b |
| | | c |
| | | |
| | | |
| | | |
| | | |
| | | |
+--------------+ +--------------+
What I saw What I expected
Although the code pass .top
param to scrollTo()
, I expected all 3 items are shown on the screen because there are enough space on the screen (this is the behavior of calling scrollTo
in List
). However, LazyVStack
only shows the third one. VStack
has the same issue.
struct Item: Identifiable {
var id: UUID = UUID()
var value: String
}
struct TestView: View {
var items: [Item]
var scrollTo: UUID
var body: some View {
ScrollViewReader { proxy in
ScrollView {
LazyVStack {
ForEach(items) { item in
Text(item.value)
}
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
proxy.scrollTo(scrollTo, anchor: .top)
}
}
}
}
}
}
struct ContentView: View {
var items: [Item] = [Item(value: "a"), Item(value: "b"), Item(value: "c")]
var body: some View {
TestView(items: items, scrollTo: items.last!.id)
.frame(maxWidth: .infinity)
}
}
While LazyVStack
and VStack
's behavior seems to fit well with .top
semantics, I find it's very confusing because it generates an invalid and misleading UI (it's impossible for user to create such UI interactively). List's behavior, however, makes more sense.
I googled this but didn't find related discussion. I filed FB12173661 to report the issue. I wonder if anyone know how to work around it? My current workaround is to change .top
to .center
. It doesn't solve the issue completely (only .bottom
can, but I don't want to use it).
UPDATE: I did an experiment by adding animation. The result helped to indicate what happened under the hood. LazyVStack
actually showed all three items on the screen at first, and then scrolled up and moved the first two items outside screen and stopped there. It really looks like a bug to me.
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
withAnimation {
proxy.scrollTo(scrollTo, anchor: .top)
}
}
}
UPDATE 2: Two more information:
- I upgraded my phone from iOS 16.3.1 to 16.4. The issue persisted.
- LazyVGrid has the same issue. It seems using List is the only option to get the correct scrolling behavior.