2

Is it possible to attach a scroll change event to a TabView in SwiftUI? I'd like to get the scroll position when the user touches and drags one of the views, which gets updated on every change of the position.

    TabView {

        Text("Example 1")
        Text("Example 2")
        Text("Example 3")

    }
    .tabViewStyle(PageTabViewStyle())

I tried by adding DragGesture but then the TabView didn't work anymore (probably because the DragGesture takes over the input). I also didn't see any corresponding events in Apple's docs.

Does anyone know a way to implement this?

Cheers..

Rick Waalders
  • 43
  • 1
  • 3

2 Answers2

4

We can track it in different way - by changed view coordinates (actually it is almost the same as drag translation)

Here is a dome for one tab. Tested with Xcode 13.4 / iOS 15.5

var body: some View {
    TabView {

        Text("Example 1")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(GeometryReader {
                // read and store origin (min X) of page
                Color.clear.preference(key: ViewOffsetKey.self,
                    value: $0.frame(in: .global).minX)
            })

        Text("Example 2")
        Text("Example 3")

    }
    .tabViewStyle(PageTabViewStyle())
    .onPreferenceChange(ViewOffsetKey.self) {
        // process here update of page origin as needed
        print("Offset >> \($0)")
    }
}

demo

Asperi
  • 228,894
  • 20
  • 464
  • 690
  • oh thanks! That's awesome. Coming from an Objective-C background, some of these concepts require me to think different about setting things up, but in most cases with Swift it looks a lot more logical. – Rick Waalders Aug 15 '22 at 18:29
0

You can track the progress of the swipe with the geometry reader and handle accordingly. This example below gives the solution to your problem. It shows you the correct selected tap page in the Text()... You can then do a .onChange(of:) where you can handle any changes when the selected tap changes.

struct ContentView: View {
    enum SwipeDirection {
        case left
        case right
        case none
    }
    @State var items = [0, 1, 2]
    @State var selection: Int = 0
    @State var swipeDirection: SwipeDirection = .none

    var body: some View {
        VStack() {
            TabView(selection: $selection) {
                ForEach($items.indices, id: \.self) { item in
                        VStack {
                            Image(systemName: "globe")
                                .imageScale(.large)
                                .foregroundColor(.accentColor)
                            Text("Hello, world! \(item)")
                        }
                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                        .tag(item)
                        .background(
                            GeometryReader { proxy in

                                Color.blue.preference(key: CustomPreferenceKey.self, value: proxy.frame(in: .global).minX)
                            }
                        )
                    }
            }.tabViewStyle(.page)
            Text("\(selection)")
        }
        .onPreferenceChange(CustomPreferenceKey.self) {
            if $0 >= (UIScreen.main.bounds.width - 20) {
                swipeDirection = .left
            } else if $0 <= -(UIScreen.main.bounds.width - 20) {
                swipeDirection = .right
            }
                switch swipeDirection {
                case .right:
                    if $0 == 0 {
                        selection += 1
                    }
                case .left:
                    if $0 == 0 {
                        selection -= 1
                    }
                case .none: break
                }
            swipeDirection = .none
        }
        .onChange(of: selection) { newValue in
            // this is where you do your thing!!
        }
    }
}

struct CustomPreferenceKey: PreferenceKey {
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {}

    static var defaultValue: CGFloat = 0
}
Benjamin Buch
  • 4,752
  • 7
  • 28
  • 51