2

I'm trying to create a PageView in pure SwiftUI. There's my test code below. And everything works as expected but the DragGesture. It just doesn't call 'onEnded' function. Never. How can I fix it?

struct PageView<V: View>: Identifiable {
    let id = UUID()
    var content: V
}

struct InfinitePageView: View {
    @State private var pages: [PageView] = [
        PageView(content: Text("Page")),
        PageView(content: Text("Page")),
        PageView(content: Text("Page"))
    ]
    
    @State private var selectedIndex: Int = 1
    @State private var isDragging: Bool = false
    
    private var drag: some Gesture {
        DragGesture()
            .onChanged { _ in
                self.isDragging = true
            }
            .onEnded { _ in
                self.isDragging = false
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
                    resolvePages()
                }
            }
    }
    
    var body: some View {
        NavigationView {
            TabView(selection: $selectedIndex) {
                ForEach(pages) { page in
                    page.content
                        .tag(pages.firstIndex(where: { $0.id == page.id })!)
                }
            }
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
            .gesture(drag)
            .onChange(of: selectedIndex, perform: { value in
                guard !isDragging else { return }
                DispatchQueue.main.async {
                    resolvePages()
                }
            })
        }
    }
    
    private func resolvePages() {
        if selectedIndex > 1 {
            addNextPage()
        }
        if selectedIndex < 1 {
            addPreviousPage()
        }
    }
    
    private func addNextPage() {
        pages.append(PageView(content: Text("Page")))
        pages.removeFirst()
        selectedIndex = 1
    }
    
    private func addPreviousPage() {
        pages.insert(PageView(content: Text("Page")), at: 0)
        pages.removeLast()
        selectedIndex = 1
    }
}
Vitalii Vashchenko
  • 1,777
  • 1
  • 13
  • 23
  • I don't have an answer for your question, but I was trying to do a similar thing and ended up using the infinite carousel in this video (https://www.youtube.com/watch?v=fB5MzDD1PZI&feature=emb_title). He included a link to the source code in the description, which is pure SwiftUI (using ZStack/Offset/Animation). You could probably update the code pretty easily to make it a full screen pager. – nicksarno Dec 24 '20 at 17:58

1 Answers1

0

This is a known issue with SwiftUI

the DragGesture that you setup likely gets overridden by the DragGesture within the TabView.

Can detect onEnded with the setup in this post Detect DragGesture cancelation in SwiftUI but that deactivates the TabView/Page gestures.

Your onChange code for selectedIndex runs once the new page is selected and would act should act the same as onEnded.

lorem ipsum
  • 21,175
  • 5
  • 24
  • 48
  • The problem is that if I change the pages array state variable while dragging continuous then TabView reloads immediately with no finishing scroll to page animation. – Vitalii Vashchenko Dec 24 '20 at 15:47
  • Good point that is probably that Gesture that overrides your `DragGesture`. Because of the inherent nature of SwiftUI (So much is automated) there are tradeoffs. Apple has SwiftUI tutorials and one of them has a "manual" PageView you might get more flexibility with that one. https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit but if you need more you might have to create your own (Does not use the features of `TabView`) – lorem ipsum Dec 24 '20 at 15:55
  • Yep, I guess there's no other way but to use UIKit's UPageViewController wrapper. Just tried to complete the task with pure SwiftUI. – Vitalii Vashchenko Dec 24 '20 at 15:57
  • Looks like, the TabView with PageTabViewStyle uses some private class named _UIPageSheetPresentationController. Couldn't even get any result with Introspect framework :( – Vitalii Vashchenko Dec 24 '20 at 15:59