Skip to content

Commit

Permalink
Fix Layout Animations iOS crash on reload (#4404)
Browse files Browse the repository at this point in the history
## Summary

This PR adds a benchmark example for layout animations as well as fixes
the following crash on iOS during reload when exiting animation is used:

<img width="1229" alt="Zrzut ekranu 2023-04-24 o 19 08 05"
src="https://user-images.githubusercontent.com/20516055/234343744-85ea9db2-14c7-4c0f-ae99-4c880677ca8f.png">

Q: Should we replace all `_hasAnimationForTag` calls with
`hasAnimationForTag`?

## Test plan

<!-- Provide a minimal but complete code snippet that can be used to
test out this change along with instructions how to run it and a
description of the expected behavior. -->
  • Loading branch information
tomekzaw committed Apr 26, 2023
1 parent eafb345 commit 4a2838b
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 1 deletion.
74 changes: 74 additions & 0 deletions app/src/examples/LettersExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { useEffect } from 'react';
import { Platform, StyleSheet, View } from 'react-native';
import Animated, { FadeIn, FadeOut, Layout } from 'react-native-reanimated';

function splitLetters(text: string) {
const map = new Map();
return text.split('').map((char) => {
const nth = map.get(char) || 0;
map.set(char, nth + 1);
return { char, key: `${char}${nth}` };
});
}

interface LettersProps {
text: string;
}

function Letters({ text }: LettersProps) {
return (
<Animated.View style={styles.line}>
{splitLetters(text).map(({ char, key }, index) => (
<Animated.Text
key={key}
style={styles.text}
entering={FadeIn.duration(500).delay(index)}
exiting={FadeOut.duration(500).delay(index)}
layout={Layout.duration(Math.random() * 700 + 200).delay(index)}>
{char}
</Animated.Text>
))}
</Animated.View>
);
}

const LOREM_IPSUM_1 =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque vel consequat urna, facilisis tincidunt massa. Fusce viverra leo non mi lacinia dictum. Sed cursus feugiat dui, quis malesuada sapien auctor vitae. Nulla ut erat ac leo posuere suscipit. Vivamus eleifend placerat elit, ut efficitur ligula semper nec. Aenean dictum volutpat sapien eget sollicitudin. Praesent non ultricies mauris, sit amet gravida ante. Morbi iaculis elit quis libero pretium dapibus. Vivamus ullamcorper leo id dapibus tempus. Aenean malesuada eleifend justo, at eleifend lectus varius ac. Donec et nibh dignissim, mollis est tincidunt, dignissim justo. Sed laoreet tempor mi, sit amet fringilla tellus varius nec. Pellentesque ut mi orci. Donec in ultrices metus.';

const LOREM_IPSUM_2 =
'Suspendisse eleifend et orci in vestibulum. In ut porttitor tortor. Vivamus dignissim mollis metus, sit amet scelerisque nunc porta quis. Mauris velit arcu, feugiat ac libero vel, commodo laoreet neque. In eu odio non nunc luctus lobortis. Duis scelerisque nec velit sit amet pretium. Quisque ac odio ac leo auctor dapibus at et justo. Sed est metus, commodo vitae elit at, porta interdum quam. Aenean porta nunc risus, vitae viverra massa pretium vitae. Donec feugiat placerat lectus, ac laoreet ligula. Vivamus scelerisque rhoncus nisi eu aliquet. Nunc quis aliquam nulla, et convallis mauris. Sed egestas nunc facilisis, tempor leo ut, lacinia quam. Donec tincidunt nulla eu velit aliquam posuere. Duis vestibulum placerat sodales. Quisque dignissim.';

export default function LettersExample() {
const [state, setState] = React.useState(true);

useEffect(() => {
const id = setInterval(() => {
setState((s) => !s);
}, 2000);
return () => clearInterval(id);
}, []);

return (
<View style={styles.container}>
<Letters text={state ? LOREM_IPSUM_1 : LOREM_IPSUM_2} />
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
line: {
flexDirection: 'row',
flexWrap: 'wrap',
maxWidth: 325,
},
text: {
fontFamily: Platform.OS === 'ios' ? 'Palatino' : 'serif',
fontWeight: '500',
fontSize: 23,
},
});
6 changes: 6 additions & 0 deletions app/src/examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import ImageStackExample from './SharedElementTransitions/ImageStack';
import InvertedFlatListExample from './InvertedFlatListExample';
import KeyframeAnimation from './LayoutAnimations/KeyframeAnimation';
import LayoutAnimationExample from './SharedElementTransitions/LayoutAnimation';
import LettersExample from './LettersExample';
import LightBoxExample from './LightBoxExample';
import LiquidSwipe from './LiquidSwipe/LiquidSwipe';
import ManyScreensExample from './SharedElementTransitions/ManyScreens';
Expand Down Expand Up @@ -144,6 +145,11 @@ export const EXAMPLES: Record<string, Example> = {
title: 'Article progress',
screen: ArticleProgressExample,
},
LettersExample: {
icon: '📖',
title: 'Letters',
screen: LettersExample,
},

// Basic examples

Expand Down
3 changes: 2 additions & 1 deletion ios/LayoutReanimation/REAAnimationsManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,8 @@ - (BOOL)startAnimationsRecursive:(UIView *)view
return NO;
}

BOOL hasExitAnimation = _hasAnimationForTag(view.reactTag, EXITING) || [_exitingViews objectForKey:view.reactTag];
BOOL hasExitAnimation =
[self hasAnimationForTag:view.reactTag type:EXITING] || [_exitingViews objectForKey:view.reactTag];
BOOL hasAnimatedChildren = NO;
shouldRemoveSubviewsWithoutAnimations = shouldRemoveSubviewsWithoutAnimations && !hasExitAnimation;
NSMutableArray *toBeRemoved = [[NSMutableArray alloc] init];
Expand Down

0 comments on commit 4a2838b

Please sign in to comment.