5

See example code below:

struct TestView: View {
    var body: some View {
        ScrollViewReader { proxy in
                List {
                    ForEach(1...30, id: \.self) { item in
                        Text("\(item)")
                        .id(item)
                    }
                }
                .onAppear {
                    proxy.scrollTo(8, anchor: .topLeading)
                }
        }
    }
}

struct ContentView: View {
    var body: some View {
        NavigationView {
            NavigationLink("test") {
                TestView()
            }
        }
    }
}

Two observations:

  1. If I don't put the TestView in NavigationLink (for example, put it directly in ContentView), the code works fine.

  2. The following diff works around the issue:

                      .onAppear {
     -                    proxy.scrollTo(8, anchor: .topLeading)
     +                    DispatchQueue.main.async() {
     +                        proxy.scrollTo(8, anchor: .topLeading)
     +                    }
                      }
    

Does anyone know what's the root cause? The workaround suggests it's a timing issue, which explains my observation 1, where there is no navigation animation. So, is it because, when the onAppear() is run, the animation is still ongoing and hence proxy.scrollTo() fails silently? But if so, shouldn't Apple provide a version of onAppear() which is run after animation is done? The current workaround looks too hacky. I found it on the net and there are different versions, some using async(), some using asyncAfter(), which suggests none is reliable.

rayx
  • 1,329
  • 10
  • 23
  • 1
    this is a common thing with `onAppear`, SwiftUI is still young, maybe we will get a better version of this in June. Submit a bug report, it might help push apple to provide something. – lorem ipsum Mar 05 '22 at 13:27
  • 3
    It is a timing issue. `.onAppear` is called BEFORE the view is shown on screen. It is more akin to a `viewWillAppear`. Therefore, there is nothing to animate at that point. Personally, I have found that using `.asyncAfter()` with a delay works better, but YMMV. You will see the same issue when trying to sequence animations. You have to have the timing right, or something won't fire properly. Personally, I would love to see a sequential animation queue we could use, but currently that is not available. – Yrb Mar 05 '22 at 14:37
  • Thanks all for the confirmation and explanation. I'll submit a bug soon. – rayx Mar 06 '22 at 05:55
  • I had a similar issue. But on watchOS I couldn't find any workaround it works only on iOS and macOS – Ivan Ičin Dec 28 '22 at 14:59

0 Answers0