1

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.

2
  • Do you want to know how you can scroll and use pageScroller? Commented Mar 30, 2021 at 15:39
  • @swiftPunk I need to know how to call pageScroller.scrollTo(pageIndex, anchor: .top) from my viewModel Commented Mar 30, 2021 at 15:40

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

14 Comments

great! Thanks a lot!
is viewModel.selectedIndex must be a @Published for a such code?
It must be! for sure.
@swiftPunk This is not an issue. This is as designed behavior of SwiftUI. Please, read the following text: stackoverflow.com/a/62919526/4423545 for understanding basics of work with vievModels in swiftUI
@swiftPunk I have resolved issue and found solution for all of my problems and you both helped to me, swiftPunk and EmilioPelaez. I will write alternative answer in a few hrs that will work without view refresh.
|
1

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
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.