skip to Main Content

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?

2

Answers


  1. 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.

    Login or Signup to reply.
  2. This is what worked with me, considering the scroll view is taking the whole screen width:

    struct PagingScrollView<Content: View>: View {
        var pageCount: Int
        var viewForPage: (Int) -> Content
        @State var offsetX: CGFloat = 0.0
        
        var currentPage: Int {
            return Int(round(-offsetX / UIScreen.main.bounds.width))
        }
        
        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)
                        }
                    }
                    .read(offsetX: $offsetX)
                }
                .scrollTargetBehavior(.paging)
            }
        }
    }
    
    extension View {
        func read(offsetX: Binding<CGFloat>) -> some View {
            self
                .background(
                    GeometryReader { geo in
                        Color.clear
                            .preference(key: ViewOffsetXKey.self, value: geo.frame(in: .global).minX)
                    }
                        .onPreferenceChange(ViewOffsetXKey.self) { minX in
                            let diff = abs(offsetX.wrappedValue - minX)
                            if diff > 1.0 {
                                offsetX.wrappedValue = minX
                                print("readOffsetX: (offsetX.wrappedValue)")
                            }
                        }
                )
        }
    }
    
    struct ViewOffsetXKey: PreferenceKey {
        static var defaultValue: CGFloat = 0
    
        static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
            value = nextValue()
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search