Reputation: 11427
There is a way in SwiftUI to scroll to a view. like this example:
ScrollView {
ScrollViewReader { value in
Button("Jump to #8") {
// we scroll on button press
value.scrollTo(8, anchor: .top)
}
.padding()
ForEach(0..<100) { i in
Text("Example \(i)")
.font(.title)
.frame(width: 200, height: 200)
.background(colors[i % colors.count])
// We need to set ID for scrolling
.id(i)
}
}
}
But my UI is not so simple. I have a View and ViewModel. Also some views generated with some delay.
I need to scroll to some element after all views will be loaded.
Do it after is not a problem, but how to scroll to needed element programmatically from ViewModel?
@ObservedObject var model: someViewModel
var body: some View {
ScrollView {
ScrollViewReader { pageScroller in
ForEach(0..<100) { i in
Text("Example \(i)")
.frame(width: 200, height: 200)
.background(colors[i % colors.count])
.id(i)
}
}
}
.frame(height: 350)
}
class someViewModel: ObservableObject {
@Published var scrollToPageName = 4
var needToScroll = false
init() { }
}
How to execute pageScroller.scrollTo(pageIndex, anchor: .top)
from my model
WITHOUT page refresh?
Or how to execute some view func from model by some event?
Also I need to not do refresh of the page before the scroll(!) - just like on button click from first example.
Upvotes: 1
Views: 2255
Reputation: 11427
IMPORTANT! This answer will be usefull for you only if it is important to do not make refresh of the view. In another case better to use solution of EmilioPelaez
This answer shows how to:
send event from model to view
and execute some function when event was fired
Full view Refresh will be not fired! Only code will be executed just like on button press!
@ObservedObject var model: someViewModel
var body: some View {
ScrollView {
ScrollViewReader { pageScroller in
ForEach(0..<100) { i in
Text("Example \(i)")
.frame(width: 200, height: 200)
.background(colors[i % colors.count])
.id(i)
}
//Magic here
.onReceive(model.scroller.objectWillChange) {
pageScroller.scrollTo(model.scroller.scrollToPageName, anchor: .top)
}
}
}
.frame(height: 350)
}
class someViewModel: ObservableObject {
//Magic here
@ObservedObject var scroller: scrollerHelper
init() {
someDelayedCall()
}
func someDelayedCall(){
scroller.scrollToPageName = 8
}
}
//Magic here
class scrollerHelper: ObservableObject {
@Published var scrollToPageName: Int = -1
}
Upvotes: 1
Reputation: 19912
It's not the view model's responsibility to scroll, but what you can do is add a selectedIndex
property to your view model, and observe and react to the change.
var body: some View {
ScrollView {
ScrollViewReader { pageScroller in
ForEach(0..<100) { i in
Text("Example \(i)")
.frame(width: 200, height: 200)
.background(colors[i % colors.count])
.id(i)
}
.onChange(of: viewModel.selectedIndex) { newIndex in
pageScroller.scrollTo(newIndex, anchor: .top)
}
}
}
.frame(height: 350)
}
Since you mentioned that your content is loaded asynchronously, you can probably update the selectedIndex
property once everything is loaded.
Upvotes: 4