Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: eliminate one frame delay in
onMove
handler on iOS (#412)
## 馃摐 Description Improved precision of `onMove` handler to drive animation frame-in-frame using this handler. ## 馃挕 Motivation and Context <!-- Why is this change required? What problem does it solve? --> <!-- If it fixes an open issue, please link to the issue here. --> This problem was introduced since #87 was merged. The thing is that we are reading `layer` properties using `CADisplayLink`. The problem is that in this case we read always out-of-date values, because `CADisplayLink` fires a callback before the next frame - in this case we'll always read a value for a previous frame. Thus we have always one frame delay. The idea of this PR is to have an ability to read future values and use them. For that I had to replicate how `SpringAnimation` calculates its values (i. e. the math behind it, because default `CASpringAnimation` class doesn't give such ability). Now we know all variables - current timestamp, beginning of the animation, next scheduled timestamp etc. so we can calculate the next frame. However it's also tricky, because in my observation: - the algorithm how next frame will be rendered is different if we compare simulator vs actual device (on a real device we have a bigger delay); - if debugger from XCode is attached, then frames are also calcualted slightly different. So the formula like `targetTimestamp - beginTime + duration` may work good if XCode debugger is not attached, but will produce junky animation when debugger is attached. I struggled a lot and found that `CADisplayLink` can fire its updates with monothonic intervals (i. e. 16ms) but the keyboard position can be at this point of time in a different values (it can be 12ms or 20ms or any other vlaue comparing to the previous frame) - see the image below as an example of values captured on simulator: <img width="1492" alt="image" src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/5fe49043-5410-479b-925b-98df77fe1426"> So in the end I decided to stick to slightly different approach - when `CADisplayLink` callback is fired, then we read prev frame keyboard position, and instead of calulation `duration` value based on absolute time intervals we are doing reversing of current keyboard frame, i. e. for a give keyboard position value we are caluclating the duration, and only then calculate next frame as `durationFromKeyboardPosition + frameDuration`. Such approach gives a decent result on a real device (debug/release, debugger attached/detached etc.). Last but not least - this PR improves precision for plain keyboard show/hide movements. However on iOS in certain cases animation can be not only `CASpringAniamtion` - right now I handle it in a PR in a fallback way, i. e. if it's not a spring animation we fallback to the mechanism introduced in #87 and simply read `layer` properties. Later on I'll cover this case as well and will synchronize animations for all tye of animations. > [!WARNING] > I can not test this PR on `ProMotion` device because I don't have it at the moment. However I'll get it back after **10.05.24**, so I'll test it there and will see if any code adjustments are needed. ## 馃摙 Changelog <!-- High level overview of important changes --> <!-- For example: fixed status bar manipulation; added new types declarations; --> <!-- If your changes don't affect one of platform/language below - then remove this platform/language --> ### Docs - remove a reference about not perfectly synchronized `onMove` handler; ### E2E - updated assets that were using `KeyboardAnimation` component; ### JS - added `gray` circle in `KeyboardAnimation` component which is driven by `onMove` handler; - in `FlatList` example -> use `onMove` handler to handle a case when layout animation is not schedule (when keyboard closed via `keyboardShouldPersistTaps`) ### iOS - added `core` folder; - added `SpringAnimation` class; - read animation from layer and try to cast it to `CASpringAnimation`; - if internal `SpringAnimation` class is available, then calculate keyboard position using this class; - don't schedule global layout animations anymore since in this case `onMove` will not be perfectly synchronized in any way (layout animation will override our calculations); ## 馃 How Has This Been Tested? Tested manually on: - iPhone 11 (iOS 17.4.1) - iPhone 6s (iOS 15.8) - iPhone 14 Pro (iOS 17.4) <- I tested it during development, however I made some changes in final code and can not test it again because this device is not with me at the moment - I'll test later when device comes back to me - iPhone 15 Pro (iOS 17.4, simulator) ## 馃摳 Screenshots (if appropriate): iPhone 11, iPhone 14, iPhone 6s ### KeyboardAnimation |When|Simulator|iPhone 11|iPhone 6s| |------|---------|----------|---------| |Before|<video src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/7ffa98ef-ef6b-400b-b9b9-3152a7e50dba">|<video src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/24730eb5-4d7a-48bd-8ca8-991d20a109d3">|<video src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/58977da1-4857-4740-8f05-c34bbbb83df4">| |After|<video src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/8e076557-8d36-41be-8981-b7343709a90b">|<video src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/63aa154b-2995-4e68-8c96-5bfef4e13127">|<video src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/2d0332b3-4c84-4692-9303-2e3108a04f53">| ### KeyboardAvoidingView |When|Simulator|iPhone 11|iPhone 6s| |------|---------|----------|---------| |Before|<video src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/1d3cbb2c-b614-4c70-a5f1-a0d1478207a6">|<video src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/aa2e597a-5d37-4450-8523-65d94bfd3c9e">|<video src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/388a4798-b42d-4cf2-bdd7-be12523a6601">| |After|<video src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/907dd2bd-df9a-4972-86c4-f75c29de476e">|<video src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/00eb7f89-bc55-4271-bb43-a54b9dfa86f8">|<video src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/8a137694-39ab-4bc2-98bf-21d492708f12">| ### KeyboardAwareScrollView |When|Simulator|iPhone 11|iPhone 6s| |------|---------|----------|---------| |Before|<video src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/bddeb811-fc12-4d5b-8f85-a0e33924b0b7">|<video src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/96e46247-300a-43e0-8c75-5aca6e1d941f">|<video src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/d7311494-d88a-4b50-bdc0-2ca097e3919f">| |After|<video src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/f9b63624-f1a2-4576-97e8-ecb58d870c61">|<video src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/f8178b53-1276-4404-ae9d-efa77b4f5492">|<video src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/b535a0ee-2385-4397-97f5-5cad3eb0535c">| ## 馃摑 Checklist - [x] CI successfully passed - [x] I added new mocks and corresponding unit-tests if library API was changed
- Loading branch information