3

I need to programmatically animate the scroll of a scrollview. The scrollview contains either an HStack or a VStack. Code I tested with is this:

        ScrollViewReader { proxy in
            ScrollView(.horizontal, showsIndicators: false) {
                HStack(spacing: spacing) {
                    
                    ForEach(cids, id: \.self) { cid in

                        ....
                                
                    }
                    
                }
                .onAppear {
                    withAnimation(Animation.easeInOut(duration: 4).delay(3)) {
                        proxy.scrollTo(testCid)
                    }
                    
                }
            }
            .frame(maxWidth: w, maxHeight: w / 2)
        }

The scrollview does land on the item with the testCid, however, it does not animate. As soon as the view comes on screen the scrollview is already on testCid...

How can I animate the scroll?

zumzum
  • 17,984
  • 26
  • 111
  • 172

1 Answers1

2

The interactive scroll works if you start it from somewhere else (f.e. Button action) but not from the onAppear modifier. I'd guess this is intentional behavior to prevent the user seeing the scrolling when the view appears (or a bug in SwiftUI...). An ugly workaround is to defer the animation with an DispatchQueue.main.async:

import SwiftUI

struct ContentView: View {
    let words = ["planet", "kidnap", "harbor", "legislation", "soap", "management", "prejudice", "an", "trunk", "divide", "critic", "area", "affair"]

    @State var selectedWord: String?

    var body: some View {
        ScrollViewReader { proxy in
            VStack(alignment: .leading) {
                ScrollView(.horizontal, showsIndicators: false) {
                    HStack(spacing: 10) {
                        ForEach(words, id: \.self) { word in
                            Text(word)
                                .background(self.selectedWord == word ? Color.yellow : nil)
                                .id(word)
                        }
                    }
                }

                Button("Scroll to random word") {
                    withAnimation(Animation.easeInOut(duration: 1)) {
                        let word = words.randomElement()
                        self.selectedWord = word
                        proxy.scrollTo(word)
                    }
                }
            }
            .onAppear {
                DispatchQueue.main.async {   // <--- workaround
                    withAnimation(Animation.easeInOut(duration: 1).delay(1)) {
                        let word = self.words.last
                        self.selectedWord = word
                        proxy.scrollTo(word)
                    }
                }
            }
        }
        .padding(10)
    }
}
Ralf Ebert
  • 3,556
  • 3
  • 29
  • 43
  • this does animate, however the animation duration time is not respected. So, the time it takes to the scroll to the new offset is always the default time. Seems to be a SwiftUI limitation for now? – zumzum Jul 15 '22 at 18:02