Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jittery behaviour? #59

Closed
alexpchin opened this issue Jan 29, 2021 · 21 comments · Fixed by #67
Closed

Jittery behaviour? #59

alexpchin opened this issue Jan 29, 2021 · 21 comments · Fixed by #67
Labels
bug Something isn't working ios

Comments

@alexpchin
Copy link
Contributor

alexpchin commented Jan 29, 2021

Hi @PedroBern, I've finally gotten around to have a look at the new v3 implementation.

On running the example, I see: You specified onScrollon a <ScrollView> but notscrollEventThrottle. You will only receive one event. Using 16 you get all the events but be aware that it may cause frame drops, use a bigger number if you don't need as much precision.

I don't know if it's related yet, but I'm also seeing quite jittery behaviour. Please see video:

simulator1.mov.zip

I haven't fully investigated this and will add more information if/when I find out more.

@alexpchin alexpchin added the bug Something isn't working label Jan 29, 2021
@PedroBern PedroBern added the ios label Jan 29, 2021
@PedroBern
Copy link
Owner

PedroBern commented Jan 29, 2021

Hi @PedroBern, I've finally gotten around to have a look at the new v3 implementation.

🎉

Hi @alexpchin thanks for running the example. Looks like it's iOS-specific. It's perfect on android here. Can you please do me a favor? Experiment with scrollEventThrottle on the examples, and tell me the result. You can either add it directly in the ScrollView / FlatList on the examples folder or by adding a default value in the src/createCollapsibleTabs.

@alexpchin
Copy link
Contributor Author

@PedroBern Will do! I haven't gotten the whole way through reading the updates yet! You've definitely been busy. I need to experiment with adding a different TabBarComponent. The one I'm using has badge icons rather than labels and Dot indicators for new messages etc.

@PedroBern
Copy link
Owner

@alexpchin thanks! yes quite a work, but was fun! The default tab bar isn't documented yet, but it's easy to add a custom TabItem, and leverage the default indicator and scrollable tabs behavior, here is an example in the upcoming react-navigation integration.

@PedroBern
Copy link
Owner

@alexpchin I added scrollEventThrottle={16} on #60, not sure if it will fix this, but just because I was also doing other things

@alexpchin
Copy link
Contributor Author

alexpchin commented Jan 29, 2021

Just working through integrating into my project which does not use expo. Here are my WIP thoughts:

I am just going through everything at the moment...

@PedroBern
Copy link
Owner

PedroBern commented Jan 29, 2021

  • Perhaps we should make it clearer that the project uses v2 of react-native-reanimated?

I will add it to the readme

  • On the first setup, I'm getting Encountered two children with the same key, undefined`.

Where? On the quick start code?

  • Currently with snapEnabled the content is not animating.
  • You can't click on the tabs once you have started to scroll but have not scrolled the whole way up (Content is not snapping, so you can be mid-scroll).
  • Even though you can't click on the tabs, you can swipe

This is weird, here is all working 😕 Not sure if I understand it. As the snap works for me, I can't see this bug.

  • Unusual scenario, I know but, if you only have one key/value in refMap, I believe this would throw an error?

Good question, I didn't test it but should work

  • How to navigate programmatically to a tab (previously used to be able to update the index of navigationState).

I added a ref in #58 to control the back behavior when navigating back. See it here. This ref can be used to programmatically navigate between tabs, like below. It's not documented/released yet.

const ref = React.useRef<RefType>()

const goToTab = () => {
    ref.current?.setIndex(name)
}

<Tabs.Container ref={ref} {...} />

@alexpchin
Copy link
Contributor Author

@PedroBern Interesting. Snap isn't working for me.

Can you uninstall expo-cli and do a fresh install from main? On running the new version, I am seeing:

Some of your project's dependencies are not compatible with currently installed expo package version:
 - react-native-gesture-handler - expected version range: ~1.8.0 - actual version installed: ^1.9.0
 - react-native-reanimated - expected version range: ~1.13.0 - actual version installed: 2.0.0-rc.0
Your project may not work correctly until you install the correct versions of the packages.
To install the correct versions of these packages, please run: expo install [package-name ...]

I might be missing something really obvious... I've re-installed expo-cli and re-pulled main?

@PedroBern
Copy link
Owner

It's correct, I get the same warning, it's because SDK 40 is not supposed to use reanimated v2 (yet) but expo says it's ok

I've just done a fresh install, everything is working, including the snap. Does the snap work on android for you?

@alexpchin
Copy link
Contributor Author

@PedroBern I just ran the example on the Android emulator and can confirm that the snap does work. Does it work for you on iOS at your end?

@andreialecu
Copy link
Collaborator

I have left some related comments here: #58 (comment)

Unfortunately there are a lot of problems on iOS with v3. Going to take a look at the implementation myself today.

@alexpchin
Copy link
Contributor Author

alexpchin commented Jan 30, 2021

Also, just another few thoughts:

@andreialecu
Copy link
Collaborator

andreialecu commented Jan 30, 2021

Here are some of the issues I found:

Scroll position doesn't sync between tabs:

RPReplay_Final1611995320.MP4

Snap only works when momentum scrolling rests, but not if you lift the finger while scrolling (zip because it's over the 10 mb limit for videos):

RPReplay_Final1611995394.MP4.zip

Tapping the tab bar to switch tabs only works when first opening a screen, as soon as you scroll a bit, they don't respond any more:

RPReplay_Final1611995710.MP4

Elastic scrolling via momentum, or by overscrolling with the finger doesn't work. It just stops abruptly at either edge (example of what I mean here: https://medium.com/@wcandillon/ios-bounce-list-effect-with-react-native-5102e3a83999):

RPReplay_Final1611995774.MP4

@andreialecu
Copy link
Collaborator

On iOS onMomentumEnd only triggers when scrolling eventually rests after a bigger swipe:

},
onMomentumEnd: () => {
if (snapEnabled) {

It doesn't trigger for touches that drag the view.

@alexpchin
Copy link
Contributor Author

alexpchin commented Jan 30, 2021

Side point, potentially, we should ensure that children exists here?

Moved to #68

@andreialecu
Copy link
Collaborator

andreialecu commented Jan 30, 2021

It appears that most of the issues I mentioned above are related to the onMomentumEnd event.

The reason is that isGliding.value = true is being set in onEndDrag, but because momentum doesn't start, it's never reset. Thus breaking everything else.

One way to potentially solve it is to set isGliding to true on onMomentumBegin. Then start a timer in onEndDrag, and if isGliding wasn't set to true after a few frames (16, 32ms) then force the snap effect manually. There would need to be some debouncing going on as well, and seems finicky to get right. It was pretty much the same v2 was doing.

Also, the overscroll situation seems to be resolved if this is omitted or set to true:

Not yet sure why it was forced to false

@andreialecu
Copy link
Collaborator

diff --git a/src/createCollapsibleTabs.tsx b/src/createCollapsibleTabs.tsx
index 08f97d8..8c1c560 100644
--- a/src/createCollapsibleTabs.tsx
+++ b/src/createCollapsibleTabs.tsx
@@ -15,6 +15,7 @@ import Animated, {
   scrollTo,
   withTiming,
   runOnJS,
+  runOnUI,
 } from 'react-native-reanimated'
 
 import MaterialTabBar, {
@@ -509,6 +510,56 @@ const createCollapsibleTabs = <
       tabNames.value.findIndex((n) => n === name)
     )
 
+    const onMomentumEnd = () => {
+      'worklet'
+      if (snapEnabled) {
+        if (diffClampEnabled && accDiffClamp.value > 0) {
+          if (scrollYCurrent.value > headerHeight) {
+            if (accDiffClamp.value <= headerHeight * snapThreshold) {
+              // snap down
+              isSnapping.value = true
+              accDiffClamp.value = withTiming(0, undefined, () => {
+                isSnapping.value = false
+              })
+            } else if (accDiffClamp.value < headerHeight) {
+              // snap up
+              isSnapping.value = true
+              accDiffClamp.value = withTiming(headerHeight, undefined, () => {
+                isSnapping.value = false
+              })
+            }
+          } else {
+            isSnapping.value = true
+            accDiffClamp.value = withTiming(0, undefined, () => {
+              isSnapping.value = false
+            })
+          }
+        } else {
+          if (scrollYCurrent.value <= headerHeight * snapThreshold) {
+            // snap down
+            snappingTo.value = 0
+            // @ts-ignore
+            scrollTo(refMap[name], 0, 0, true)
+          } else if (scrollYCurrent.value <= headerHeight) {
+            // snap up
+            snappingTo.value = headerHeight
+            // @ts-ignore
+            scrollTo(refMap[name], 0, headerHeight, true)
+          }
+          isSnapping.value = false
+        }
+      }
+      isGliding.value = false
+    }
+
+    const maybeGlide = () => {
+      setTimeout(() => {
+        if (isGliding.value !== true) {
+          runOnUI(onMomentumEnd)()
+        }
+      }, 50)
+    }
+
     const scrollHandler = useAnimatedScrollHandler(
       {
         onScroll: (event) => {
@@ -524,53 +575,13 @@ const createCollapsibleTabs = <
           isScrolling.value = true
         },
         onEndDrag: () => {
-          isGliding.value = true
           isScrolling.value = false
+          runOnJS(maybeGlide)()
         },
-        onMomentumEnd: () => {
-          if (snapEnabled) {
-            if (diffClampEnabled && accDiffClamp.value > 0) {
-              if (scrollYCurrent.value > headerHeight) {
-                if (accDiffClamp.value <= headerHeight * snapThreshold) {
-                  // snap down
-                  isSnapping.value = true
-                  accDiffClamp.value = withTiming(0, undefined, () => {
-                    isSnapping.value = false
-                  })
-                } else if (accDiffClamp.value < headerHeight) {
-                  // snap up
-                  isSnapping.value = true
-                  accDiffClamp.value = withTiming(
-                    headerHeight,
-                    undefined,
-                    () => {
-                      isSnapping.value = false
-                    }
-                  )
-                }
-              } else {
-                isSnapping.value = true
-                accDiffClamp.value = withTiming(0, undefined, () => {
-                  isSnapping.value = false
-                })
-              }
-            } else {
-              if (scrollYCurrent.value <= headerHeight * snapThreshold) {
-                // snap down
-                snappingTo.value = 0
-                // @ts-ignore
-                scrollTo(refMap[name], 0, 0, true)
-              } else if (scrollYCurrent.value <= headerHeight) {
-                // snap up
-                snappingTo.value = headerHeight
-                // @ts-ignore
-                scrollTo(refMap[name], 0, headerHeight, true)
-              }
-              isSnapping.value = false
-            }
-          }
-          isGliding.value = false
+        onMomentumBegin: () => {
+          isGliding.value = true
         },
+        onMomentumEnd,
       },
       [headerHeight, name, diffClampEnabled, snapEnabled]
     )
@@ -677,7 +688,7 @@ const createCollapsibleTabs = <
     return (
       <Animated.ScrollView
         ref={refMap[name] as any}
-        bounces={false}
+        //bounces={false}
         bouncesZoom={false}
         style={[_style, style]}
         contentContainerStyle={[

This diff seems to fix all of the issues I mentioned above, but it needs some cleanup for the setTimeout handling.

I notice that the DiffClamp example gets broken though, and keeps pushing the header up a bit every time. This is probably related to bounces. Here's a longer recording with these changes:

https://streamable.com/5a4aeo

@PedroBern
Copy link
Owner

Im not at home right now, when I get there I will iterate over all comments. Just one thought, I saw some issues on reaninated, related to scroll on iOS that were fixed in rc.1 and rc.2, but the example folder is using rc.0

@andreialecu
Copy link
Collaborator

I opened #65 which fixes all of the issues I reported from my initial testing.

@alexpchin if you could test it as well it would be great. I'm not sure about the jittery behaviour, seems to work fine on my physical device (iPhone 12 Pro Max).

@andreialecu
Copy link
Collaborator

Oh, and good job @PedroBern on the rewrite 🏆 . The implementation looks much better now with Reanimated.

@PedroBern
Copy link
Owner

Oh, and good job @PedroBern on the rewrite 🏆 . The implementation looks much better now with Reanimated.

@andreialecu thanks man 👍

Also, just another few thoughts:

How to get the currently focused tab from refMap, previously you could use navigationState.index?
How to listen to the change of the tab? (Connected to above)
Is it possible to disable swipeEnabled ?
Currently, the refMap uses the keys to create the labels. If we changed the structure of refMap, it could take a label or component/icon it might allow for more customisation.
{
screen1: { ref: ref, label: label },
screen2: { ref: ref, label: label }
}

@alexpchin better to open a new issue so we can discuss it without noise... But you can disable swiping with pagerProps={{scrollEnabled: false}}.

@alexpchin
Copy link
Contributor Author

@PedroBern @andreialecu I've gone through and moved unrelated comments to their own issues and updated my comments to reduce noise and make them easier to fix. I will now start looking at them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working ios
Projects
None yet
3 participants