Reputation:
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?
Upvotes: 16
Views: 27204
Reputation: 382
ScrollViewReader { proxy in
ScrollView {
content
.id("content")
}
.onChange(of: store.step) { // some state change triggers scroll
proxy.scrollTo("content", anchor: .top)
}
}
Upvotes: 0
Reputation: 2096
From iOS 14.
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 ?)
Upvotes: 23
Reputation: 11426
Easy way to scroll to top:
struct MyView: View {
@State var scrollToTopVar = false
var body: some View {
VStack {
//will scroll to top on button press
Button("Scroll to top") { scrollToTopVar.toggle() }
ScrollViewReader { reader in
ScrollView {
ScrollerToTop(reader: reader, scrollOnChange: $scrollToTopVar)
VStack {
//Some Views
}
}
}
}
}
}
///
ScrollerToTop 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)
}
}
}
}
Upvotes: 4
Reputation: 3275
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
Upvotes: -3
Reputation: 2958
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)
Upvotes: 5
Reputation: 163
I think that currently the best way to do it is using the View´s id function
@State private var scrollViewID = UUID()
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
Upvotes: 13
Reputation: 312
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
}
}
Upvotes: 0