9

I am using ScrollView, and is that possible to scroll the view to the top when I do any actions, such as click button and etc?

  • You can check my post on how to scroll to the bottom, just replace the code to make it scroll to the top. Here is the link https://stackoverflow.com/a/62719645/9497800 – multitudes Jul 04 '20 at 08:35

6 Answers6

12

I think that currently the best way to do it is using the View´s id function

  1. Declare a state var with a unique id
    @State private var scrollViewID = UUID()
  1. and then add the id function to the ScrollView

    ScrollView(.horizontal, showsIndicators: true) {
    // ...
    }.id(self.scrollViewID)
    
    

Whenever you change that id - for example in a button event - the scrollview is rebuilt from scratch - which is like scrolling to the top

Tintenklecks
  • 145
  • 1
  • 4
  • 3
    When I tried this I saw the view reevaluated but the scroll position stayed the same. – t9mike Jan 11 '21 at 01:48
  • This solution works but perorms blink of whole of the ScrollView in som cases. OK solution for test code, but really bad solution for release app. You can check my solution – Andrew_STOP_RU_WAR_IN_UA Dec 19 '22 at 05:14
12

From iOS 14, maybe before.


struct Example: View {

    private static let topId = "topIdHere"

    /*
      Use only for toggling, binding for external access or @State for internal access
    */
    @Binding var shouldScrollToTop: Bool = false 

    var body: some View {

        ScrollViewReader { reader in  // read scroll position and scroll to
            ScrollView { 
                VStack {
                   TopView() // << first view on top
                    .id(Self.topId) // << assigned id (use for scroll)
                }
                .onChange(shouldScrollToTop) { _ in 
                    withAnimation {  // add animation for scroll to top
                       reader.scrollTo(Self.topId, anchor: .top) // scroll
                    }
                }
            }
        }
    }
}

Something like that ?)

iTux
  • 1,946
  • 1
  • 16
  • 20
  • 1
    only from iOS 14.0, if check the documentation. previously we had to find other ways to achieve this – Hrabovskyi Oleksandr Apr 25 '23 at 10:29
  • Line with `@Binding var shouldScrollToTop: Bool = false` triggers an error `Cannot convert value of type 'Bool' to specified type 'Binding'`. The binding variable is only referencing another variable from another view, which is not mentioned here, so the solution is not complete. Can you elaborate? – Nojas Aug 22 '23 at 08:03
  • @Nojas - which Swift, SwiftUI, Xcode versions do you use for this example apply? :) I see that example not suitable for latest SwiftUI and Swift. For example onChange method now want "to:" for right usage :) – iTux Aug 29 '23 at 08:24
  • @Nojas for a new version you can try to this: change `@Binding var shouldScrollToTop: Bool = true` to `@Binding var shouldScrollToTop: Bool` (but at this time you need to provide `@State` property from parent. and `.onChange(shouldScrollToTop)` replace to .onChange(of: shouldScrollToTop). When sate changed (for example by button/toggle) your ScrollView should scroll to top. But, when content less than for scroll present, it's not work for reason :) – iTux Aug 29 '23 at 08:35
5

In just published ScrollViewProxy.

A proxy value allowing the scrollable views within a view hierarchy to be scrolled programmatically. – https://developer.apple.com/documentation/swiftui/scrollviewproxy

Xcode 12.0 beta (12A6159)

Ugo Arangino
  • 2,802
  • 1
  • 18
  • 19
2

Easy way to scroll to top:

usage: on change of bool "scrollToTopVar" ScrollView will be scrolled to top :)

struct MyView: View {
    @State var scrollToTopVar = false

    var body: some View {
        ScrollViewReader { reader in
            ScrollView { 
                ScrollerToTop(reader: reader, scrollOnChange: $scrollToTopVar)

                VStack {
                    //Some Views
                }
            }
        }
    }
}

custom View code:

struct ScrollerToTop: View {
    let reader: ScrollViewProxy
    @Binding var scrollOnChange: Bool
    
    var body: some View {
        EmptyView()
            .id("topScrollPoint")
            .onChange(of: scrollOnChange) { _ in
                withAnimation {
                    reader.scrollTo("topScrollPoint", anchor: .bottom)
                }
            }
    }
}
Andrew_STOP_RU_WAR_IN_UA
  • 9,318
  • 5
  • 65
  • 101
  • I don't fully get it, because it's missing the information how and where to initialize the `scrollToTopVar`. Also, what is the `$model` variable? It's only called in your example. Can you elaborate? – Nojas Aug 22 '23 at 08:01
  • `scrollToTopVar` must be `@Published` inside model or `@State` in view (any type, bool will be ok) – Andrew_STOP_RU_WAR_IN_UA Aug 22 '23 at 11:26
0

There is a workaround that can accomplish ScrollToTop() effect by hiding everything before showing new content.

@State var hideEverything = false

var body: some View {
  ScrollView {
    if hideEverything {
      EmptyView()
    } else {
      // your content view
    }
  }
}

func ScrollToTop() {
  self.hideEverything = true
  DispatchQueue.main.asyncAfter(deadline: .now() + 0.01)  
  {  
    self.data = ... // update data source
    self.hideEverything = false
  }
}
Tomasen
  • 142
  • 1
  • 3
-3

Of course from iOS 14.0 and later you should use ScrollViewProxy, definitely it has been developed for this reason.

But if you check when the question has been asked, you'll define the answer was for iOS 13.0, the SwiftUI 1.0 and at that point of time you had to reinvent the bicycle somehow:

Using this solution (previously simplified) I made example of moving ScrollView.content.offset strict to the top when button pressed:

struct ScrollToTheTop: View {
    
    @State private var verticalOffset: CGFloat = 0.0
    @State private var gestureOffset: CGFloat = 0.0
    @State private var itemCount: Int = 200
    
    var body: some View {
        
        NavigationView {
            VStack {
                
                ScrollView(.vertical, showsIndicators: false) {
                    
                    VStack {
                        
                        ForEach(1...itemCount, id: \.self) { item in
                            Text("--- \(item) ---") // height 17.5
                        }
                        
                    }
                    
                }
                .content.offset(y: (self.verticalOffset + self.gestureOffset)
                    // all the content divided by 2 for zero position of scroll view
                    + CGFloat(17.5 * Double(self.itemCount/2)))
                    .gesture(DragGesture().onChanged( { value in
                        self.gestureOffset = value.translation.height
                    }).onEnded( { value in
                        withAnimation {
                            // here should calculate end position with value.predictedEndLocation.y
                            self.verticalOffset += value.translation.height
                            self.gestureOffset = 0.0
                        }
                    }))
                
                
            }.navigationBarItems(trailing: Button("To top!") {
                withAnimation {
                    self.verticalOffset = 0.0
                }
            })
        }
        
    }
    
}

if this example fits, you have to refine DragGesture

Hrabovskyi Oleksandr
  • 3,070
  • 2
  • 17
  • 36