Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: keyboard animation if cross-fade transitions enabled on iOS (#570)
## 📜 Description Fixed keyboard animation when cross-fade transitions enabled on iOS. ## 💡 Motivation and Context That was a pretty interesting question on how to handle that in the library. Because from the one side the height of the keyboard becomes "static" (i. e. it's shown instantly, but we animate only opacity), so properties such as `height` should be a static and only `progress` should be changed. From the other side it would be reasonable to add `opacity` property in this case. But I think it would significantly increase the complexity of the library and before implementing a fix I decided to have a look how default iOS apps handles that (such as iMessages and Calendar): |iMessage|Calendar| |----------|---------| |<video src="https://github.com/user-attachments/assets/e745c594-2bb0-4632-88e7-054fbeeffa87">|<video src="https://github.com/user-attachments/assets/78262914-0478-471b-a6f6-4f6d9db341b3">| So as you can see we in both cases we still have smooth vertical transitions so it made me thinking that we still can animate `height` and `progress` as before. If we want to apply reduced animation, then we still can do that based on `progress`: ```tsx import {AccessibilityInfo} from 'react-native'; const useCrossFadeKeyboardTransition = () => { const [areCrossFadeTransitionsEnabled, setCrossFadeTransitionsEnabled] = useState(false); const keyboardHeight = useSharedValue(0); const transition = useSharedValue(0); const opacity = useSharedValue(1); useEffect(() => { AccessibilityInfo.prefersCrossFadeTransitions() .then(enabled => setCrossFadeTransitionsEnabled(enabled)); }, []); useKeyboardHandler({ onStart: (e) => { "worklet"; keyboardHeight.value = e.height; }, onMove: (e) => { "worklet"; if (areCrossFadeTransitionsEnabled) { opacity.value = progress.value; } else { transition.value = height.value; } }, }, [areCrossFadeTransitionsEnabled]); const style = useAnimatedStyle(() => ({ opacity: opacity.value, transform: [{ translateY: transition.value }] }), []); // ... }; ``` The other thing was the algorithm for detection cross fade transitions and usage specific code for that. The first perspective idea was the usage of this extension: ```swift import Foundation public extension UIAccessibility { static var areCrossFadeTransitionsEnabled: Bool { if #available(iOS 14.0, *) { return UIAccessibility.prefersCrossFadeTransitions } else { return false } } } ``` While it may seem working this code has several disadvantages: - the `prefersCrossFadeTransitions` property is available only from iOS `14+` (but the setting was introduced in iOS 13); - even if `prefersCrossFadeTransitions` enabled keyboard can be animated vertically 😲 (after interactive gesture) - even if `prefersCrossFadeTransitions` enabled keyboard can be animated vertically on older iOS versions (iOS 15 for example) So I started to look for other approaches. The thing is that even with `.prefersCrossFadeTransitions` we would just read other property of the layer (`opacity` vs `position`), so I decided to try this approach without `.prefersCrossFadeTransitions` (we check for different animation keys - initially we check `position`, if animation is not found then we check `opacity`, if not found then we don't initialize animation). Closes #569 ## 📢 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 --> ### iOS - rename `framePositionInWindow` -> `frameTransitionInWindow` (to make it clear that we read isomorphic transition instead of particular position/opacity/etc.); - read `opacity` animation from layer in `framePositionInWindow` extension and if animation is found, then interpolate `transition` based on the value of the opacity; - try initialize animation from 2 layers (position, opacity) instead of 1 layer (position); ## 🤔 How Has This Been Tested? Tested on: - iPhone 15 Pro (iOS 17.5, simulator); - iPhone 13 Pro (iOS 15.5, simulator); - iPhone 11 (iOS 17.5, real device); - iPhone 6s (iOS 15.8, real device); ## 📸 Screenshots (if appropriate): ### Interactive keyboard https://github.com/user-attachments/assets/dd9592a8-c616-4af8-8710-9409ac0b3c06 ### Plain transitions (keyboard animation with keyboard resize) https://github.com/user-attachments/assets/adcca246-dd82-4c14-976e-f95689c51f1a ### KeyboardAvoidingView https://github.com/user-attachments/assets/5c1f1b72-5e94-478e-8a5c-a314104da7ea ### KeyboardAwareScrollView https://github.com/user-attachments/assets/946a71dd-6b59-4124-9b9e-a0fad1618e5f ### Modal https://github.com/user-attachments/assets/ff362b38-f0f4-44a9-8dac-866a5233006d ## 📝 Checklist - [x] CI successfully passed - [x] I added new mocks and corresponding unit-tests if library API was changed
- Loading branch information