SwiftUI 4: scrollTo() generates invalid UI in VStack and LazyVStack

167 Views Asked by At

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.
0

There are 0 best solutions below