fermoya / SwiftUIPager

Native Pager in SwiftUI
MIT License
1.3k stars 172 forks source link

[BUG] Pager within NavigationView scrolls to next page during edge swipe gesture (for navigating back) #252

Closed robinst closed 2 years ago

robinst commented 2 years ago

Describe the bug When using a Pager within subview of a NavigationView and then navigating back using a swipe gesture from the left screen edge, the gesture causes the pager to go to the next page while the swipe gesture is being performed, see video below.

To Reproduce

  1. Have a NavigationView that contains a NavigationLink with a destination view that contains a Pager
  2. Navigate into the destination view
  3. Swipe from the left screen edge (pop gesture) to navigate back

Expected: Navigates back to the previous view, the pager doesn't change. Actual: The pager swipes to the next page while the gesture is being performed. It's doesn't always happen but pretty often, maybe depending on how quick the gesture is performed.

Code to reproduce (drop into the Examples in SwiftUIPagerExample):

import SwiftUI

struct NavigationExampleView: View {
    var body: some View {
        NavigationView {
            NavigationLink(destination: PagerView()) {
                Label("Navigate", systemImage: "folder")
            }
        }
    }
}

struct PagerView: View {
    @StateObject var page: Page = .first()
    // For older versions:
    // @State var page: Int = 0

    @State var items = [
        Item(name: "blue", color: Color.blue),
        Item(name: "red", color: Color.red)
    ]

    var body: some View {
        Pager(page: page,
              data: items,
              content: { item in
                  Text(item.name)
                      .frame(maxWidth: .infinity, maxHeight: .infinity)
                      .background(item.color)
              })
        // Doing this fixes the problem, but disables dragging (duh):
        // .allowsDragging(false)
    }
}

struct Item: Equatable, Identifiable {
    var name: String
    var color: Color

    var id: String {
        name
    }
}

struct NavigationExampleView_Previews: PreviewProvider {
    static var previews: some View {
        NavigationExampleView()
    }
}

Screenshots / Videos

https://user-images.githubusercontent.com/16778/158742041-9943a2d6-adb1-4019-a22d-aa10c06e867b.mp4

Notice how, while performing the edge swipe gesture to navigate back, the pager goes to the next page (from blue to red).

Environment:

Additional context Some observations:

denizdogan commented 2 years ago

I'm having the exact same issue, but with swiping from the top edge to view the notification center.

fermoya commented 2 years ago

Hi @robinst thanks for the suggestion. I'll take a look, I'm aware of the issue of that exists when you combine gestures in (or views that implicitly have a gesture) in SwiftUI. In UIKit there are option to request a gesture to fail before being recognized but the same doesn't apply here.

I'll take a look at your suggestion. I can always create a beta version and get feedback from it.

@denizdogan have you tried the suggestion above?

fermoya commented 2 years ago

Out of curiosity, have you tried delaysTouches and pagingPriority?

denizdogan commented 2 years ago

@fermoya My use of the component is currently very "barebones", but I'm thinking maybe it would be simpler to just adjust the "gesturable area", so that it doesn't react when the touch starts outside of the safe area?

fermoya commented 2 years ago

That should be already possible, the library ships with swipeInteractionArea modifier. It's possible to limit the interaction to just the page and not the whole container.

Problem here is there's no such thing as a safe area here. Pagerexpands and takes up as much space as its container. If the the parent expands till the status bar, then so does Pager. In your case it would be a matter of ensuring this. I'm guessing wrapping Pager into a VStack.

Similarly, using swipeInteractionArea might help in case .page is selected, although I don't think it would make much of a difference if the page reaches the edges of the screen.

I'll take a look at coordinateSpace, it seems like a good start

robinst commented 2 years ago

Out of curiosity, have you tried delaysTouches and pagingPriority?

Yeah just tried that, it doesn't change anything.

I'll take a look at coordinateSpace, it seems like a good start

Note that that breaks the "More" example where swiping is vertical or right to left (because of rotation3DEffect). But that could be fixed by changing the code in onDragChanged to be aware of the direction instead.

fermoya commented 2 years ago

this should be fixed here in case you want to check it out: https://github.com/fermoya/SwiftUIPager/releases/tag/2.3.3-beta.3

robinst commented 2 years ago

Yess, that fixes it, thank you :)! Hopefully it fixes @denizdogan's problem too.

robinst commented 2 years ago

Thanks for the quick fix @fermoya, I've bought you a few coffees as a thank you :)

denizdogan commented 2 years ago

@fermoya So you just to circle back, swipeInteractionArea did nothing for me, as you expected. My issue is that the pager covers the entire screen, and when I swipe down from the very top of the screen to open the notification center, the pager also swipes up. Should I open another issue about this?

fermoya commented 2 years ago

@denizdogan try the mates version 2.3.3 if that helps but what you must ensure then is that Pager stats within the Safe Area. That's up to you, not the library, check out the examples in the repo.

denizdogan commented 2 years ago

@fermoya Sorry, I should have mentioned I was already using 2.3.3. That's unfortunate to hear, IMO the library would do good to allow us to define offsets from the edges where the pager should ignore gestures.

fermoya commented 2 years ago

@denizdogan please open a new issue and share your code or a small snippet to reproduce, maybe a video too. But please, first check out the examples in the repo. I’m using a Pager inside a NavigationView and it doesn’t use the safe area. What I’m saying is Pager doesn’t use the safe area unless you specify it so my guess is there’s something in your code that makes it use it.

please open a new thread