swiftxcodeswiftui

Get index of currently displayed view in a paging scroll view


With the new modifier scrollTargetBehavior(.paging), ScrollViews now get paging behavior. It works great but I've yet to find a way to get the currently displayed view. I've tried using the onAppear on each view, but it doesn't correlate to when the view is displayed, and it's only called once. If you go back to the page it won't be called again.

The view named PagingScrollView contains a GeometryReader to set the frame of each of the displayed child views. The view takes in a function to get the child views to be displayed.

Here is a sample complete with the preview modifier, you can just copy and paste it to xcode.

import SwiftUI

struct PagingScrollView<Content: View>: View {
    var pageCount: Int
    var viewForPage: (Int) -> Content

    var body: some View {
        GeometryReader { geo in
            ScrollView (.horizontal) {
                HStack (spacing: 0) {
                    ForEach(0..<pageCount, id:\.self) { index in
                        viewForPage(index)
                            .frame(width: geo.size.width, height: geo.size.height)
                    }
                }
                .scrollTargetLayout()
            }
            .scrollTargetBehavior(.paging)
        }
    }
}

struct PreviewView: View {
    var body: some View {
        PagingScrollView(pageCount: 10, viewForPage: getView(index:))
    }

    func getView(index: Int) -> some View {
        return Text("View: \(index)")
    }

}

#Preview {
    PreviewView()
}

Is there a way to get the currently displayed view?


Solution

  • Use the scrollPosition modifier. Be sure to give each view in the HStack an id. Then, create a state variable scrolledID (for example) and add the .scrollPosition(id: $scrolledID) modifier on the ScrollView. In your case:

    struct PagingScrollView<Content: View>: View {
        var pageCount: Int
        var viewForPage: (Int) -> Content
        @State var scrolledID: Int?  //  <----
    
        var body: some View {
            GeometryReader { geo in
                ScrollView (.horizontal) {
                    HStack (spacing: 0) {
                        ForEach(0..<pageCount, id:\.self) { index in
                            viewForPage(index)
                                .frame(width: geo.size.width, height: geo.size.height)
                                .id(index)  //  <----
                        }
                    }
                    .scrollTargetLayout()
                }
                .scrollTargetBehavior(.paging)
                .scrollPosition(id: $scrolledID)  //  <----
            }
        }
    }
    

    This modifier can be used independently of scrollTargetBehavior.