React-native-pager-view: [iOS] Changing swipe direction flickers

Created on 14 Apr 2021  路  13Comments  路  Source: callstack/react-native-pager-view

Environment

iPhone 11
iOS: 14.4
Example app

Description

When swiping on Android back and fort the position stays the same and the offset is changing only.
Example:

[Wed Apr 14 2021 08:27:02.615]  LOG      Position: 1 Offset: 0.023148147389292717  // swiping left 
[Wed Apr 14 2021 08:27:02.787]  LOG      Position: 1 Offset: 0.02500000037252903 // swiping left
[Wed Apr 14 2021 08:27:02.820]  LOG      Position: 1 Offset: 0.02777777798473835 // swiping left
[Wed Apr 14 2021 08:27:05.399]  LOG      Position: 1 Offset: 0.02500000037252903 // swiping right
[Wed Apr 14 2021 08:27:05.433]  LOG      Position: 1 Offset: 0.023148147389292717 // swiping right

When swiping on iOS back and fort both the position and the offset is changing.
Example:

[Wed Apr 14 2021 08:30:40.408]  LOG      Position: 1 Offset: 0.047101449221372604 // swiping left 
[Wed Apr 14 2021 08:30:40.435]  LOG      Position: 1 Offset: 0.050724636763334274 // swiping left 
[Wed Apr 14 2021 08:30:41.305]  LOG      Position: 0 Offset: 0.9504830837249756  // swiping right
[Wed Apr 14 2021 08:30:41.326]  LOG      Position: 0 Offset: 0.9528985619544983 // swiping right
[Wed Apr 14 2021 08:30:41.345]  LOG      Position: 0 Offset: 0.95652174949646 // swiping right

This behaviour messes up the animation. iOS should behave like android
Heres a video that shows the bug, just watch the bottom paginating circles.

https://user-images.githubusercontent.com/50402950/114665627-8f505e80-9cfd-11eb-8dd0-eb90c59e44d0.mp4

Reproducible Demo

Just run the example app of this repo.
Start swiping left/right then change the direction.

Most helpful comment

I would keep the overdrag enabled by default on iOS, because that's a default behaviour on iOS.

I theory, returning only position -1 on the onPageScroll when is dragging to the left from the first page makes sense, since dragging to the any other page, from any page different than 0, will return the "upcoming" position.

All 13 comments

Same here, swipe back giving inconsistent position on iOS. I've tried change the version since 5.1.0 but always got the same behaviour.

Hi!

I think I am having the same problem here https://github.com/callstack/react-native-pager-view/issues/355

Did you have any workarounds?

Original author: @punksta

Environment

"react-native-pager-view": "5.1.8",

Description

I am trying to implement page indicator when you swipe pages in the pager using Animated, connected to onPageScroll.

add the following listener to your pager and see console log.

onPageScroll={Animated.event(
          [
            {
              nativeEvent: {
                position: pageScrollPosition,
                offset: pageScrollOffset
              }
            }
          ],
          {
            listener: event => {
              event.persist()
              console.log(event.nativeEvent)
            },
            useNativeDriver: false
          }
        )}

I start dragging to right, I don't release your finger and change direction of the drag to back left.

  1. current index is 0 {"offset": 0, "position": 0}
  2. drag to right.
    LOG {"offset": 0.011111111380159855, "position": 0} LOG {"offset": 0.021367521956562996, "position": 0} LOG {"offset": 0.03076923079788685, "position": 0} LOG {"offset": 0.04017094150185585, "position": 0} ... LOG {"offset": 0.26923078298568726, "position": 0} LOG {"offset": 0.27008548378944397, "position": 0}
  3. change direction of your drag back to left.
 LOG  {"offset": 0.7316238880157471, "position": -1}
 LOG  {"offset": 0.7333333492279053, "position": -1}
 LOG  {"offset": 0.7350426912307739, "position": -1}
 LOG  {"offset": 0.7367521524429321, "position": -1}

On UI it results in jumping of the page indicator between pages.

The same behavior arises for other pages too.

Expected behavior

position should stay on "position": 0 and offset should decrease from 0.2 to 0.

Reproducible Demo

https://user-images.githubusercontent.com/13404754/118443090-c5617380-b6eb-11eb-9a99-0ac045075f63.mov

```
import React from 'react'
import { Animated, Dimensions, Pressable, StyleSheet, View } from 'react-native'
import ViewPager from 'react-native-pager-view'
import { Text } from 'react-native'

const AnimatedViewPager = Animated.createAnimatedComponent(ViewPager)

export const SwapMealScreen: React.FC<{}> = React.memo(() => {
const ref = React.useRef(null)

const pageScrollPosition = React.useMemo(() => new Animated.Value(0), [])
const pageScrollOffset = React.useMemo(() => new Animated.Value(0), [])

const [pageIndex, setPageIndex] = React.useState(0)

const pageIndicatorTransactionX = Animated.add(
Animated.multiply(pageScrollPosition, Dimensions.get('window').width / 3),
Animated.multiply(pageScrollOffset, Dimensions.get('window').width / 3)
)

return (
<>
{[1, 2, 3].map((pageName, index) => {
const isSelected = index === pageIndex

      return (
        <Pressable
          collapsable={false}
          onPress={() => {
            jumpToPage(index)
            setImmediate(() => {
              ref.current?.setPage?.(index)
            })
          }}
          key={pageName}
          style={styles.tabContainer}
        >
          <Text
            style={{
              fontSize: 32,
              color: isSelected ? '#090' : '#000'
            }}
          >
            {pageName}
          </Text>
        </Pressable>
      )
    })}
    <Animated.View
      style={[
        styles.indicator,
        {
          transform: [
            {
              translateX: pageIndicatorTransactionX
            }
          ]
        }
      ]}
    />
  </View>
  <AnimatedViewPager
    ref={ref}
    transitionStyle={'scroll'}
    overScrollMode={'always'}
    initialPage={0}
    onPageScroll={Animated.event(
      [
        {
          nativeEvent: {
            position: pageScrollPosition,
            offset: pageScrollOffset
          }
        }
      ],
      {
        listener: event => {
          event.persist()
          console.log(event.nativeEvent)
        },
        useNativeDriver: false
      }
    )}
    onPageSelected={event => setPageIndex(event.nativeEvent.position)}
    style={styles.pager}
  >
    {[1, 2, 3].map(page => {
      return (
        <View
          style={{
            backgroundColor: `rgba(0, 0, 0, ${(page - 1) * 0.5})`,
            width: Dimensions.get('window').width,
            height: '100%'
          }}
        />
      )
    })}
  </AnimatedViewPager>
</>

)
})

const styles = StyleSheet.create({
tabContainer: {
flex: 1,
paddingBottom: 8,
marginBottom: 6,
paddingHorizontal: 8,
marginHorizontal: 16
},
indicator: {
left: 24,
position: 'absolute',
bottom: 0,
height: 3,
width: 30,
backgroundColor: 'green'
},
titles: {
marginTop: 60,
flexDirection: 'row'
},
pager: {
overflow: 'visible',
flex: 1
}
})

```

Currently onPageScroll returns position and positionOffset value. I was trying to implement it on iOS , but it seems like does not work very well. There are two options to fix it:

  • Fix offet calculation function on iOS side
  • Change return param on android using positionOffsetPixels (BREAKING CHANGE) doc, but in this case you need to compare, if both values are the same on iOS and Android

position should stay the same when changing directions, however offset should increase or decrease until 1 or 0 and only then change the position.

position should stay the same when changing directions, however offset should increase or decrease until 1 or 0 and only then change the position.

Here you can find all calculations https://github.com/callstack/react-native-pager-view/blob/master/ios/ReactNativePageView.m#L374.

The latest release introduced a new issue, over-dragging the first page to the left, move the pagination and the onPageScroll event to the oposite direction. [see attached video]

Is there a way to fix this?

https://user-images.githubusercontent.com/2472594/119741301-0de70280-be4b-11eb-986b-a5d205cb690b.mov

I see it. On Android it just ignores the overscroll and returns 0 for offset.
I will create another PR for that soon.

@troZee
What do you think about setting overdrag prop to false by default?

For the bug mentioned by @elkinjosetm a possible fix would be enabling negative offset or position, but then #310 would come back.

@troZee
What do you think about setting overdrag prop to false by default?

For the bug mentioned by @elkinjosetm a possible fix would be enabling negative offset or position, but then #310 would come back.

I think, we can set this prop to false by default

Yeah, Android doesn't have overscroll, so that's why there is no issue there. Thanks, I'm looking forward for that release.

I would keep the overdrag enabled by default on iOS, because that's a default behaviour on iOS.

I theory, returning only position -1 on the onPageScroll when is dragging to the left from the first page makes sense, since dragging to the any other page, from any page different than 0, will return the "upcoming" position.

I noticed that next code works good and on iOS on the current PagerView version (5.2.1) (it worked bad on some previous version).

```
const scrollAnimatedValue = useMemo(
() => Animated.add(positionAnimatedValue, offsetAnimatedValue),
[positionAnimatedValue, offsetAnimatedValue],
);

const onScroll = useMemo(() => Animated.event(
[{
nativeEvent: {
position: positionAnimatedValue,
offset: offsetAnimatedValue,
},
}],
{ useNativeDriver: true },
), [positionAnimatedValue, offsetAnimatedValue]);
```

So I switched from ScrollView on iOS to PagerView.

Ofcourse I enabled overdrag.

But unfortunately now I see that left overdrag works wrong. scrollAnimatedValue should be negative when overdrag to the left.

Have to go back to ScrollView on iOS until it will fixed. :(

Was this page helpful?
0 / 5 - 0 ratings