Skip to content

Commit 4a2838b

Browse files
authored
Fix Layout Animations iOS crash on reload (software-mansion#4404)
## 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. -->
1 parent eafb345 commit 4a2838b

3 files changed

Lines changed: 82 additions & 1 deletion

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import React, { useEffect } from 'react';
2+
import { Platform, StyleSheet, View } from 'react-native';
3+
import Animated, { FadeIn, FadeOut, Layout } from 'react-native-reanimated';
4+
5+
function splitLetters(text: string) {
6+
const map = new Map();
7+
return text.split('').map((char) => {
8+
const nth = map.get(char) || 0;
9+
map.set(char, nth + 1);
10+
return { char, key: `${char}${nth}` };
11+
});
12+
}
13+
14+
interface LettersProps {
15+
text: string;
16+
}
17+
18+
function Letters({ text }: LettersProps) {
19+
return (
20+
<Animated.View style={styles.line}>
21+
{splitLetters(text).map(({ char, key }, index) => (
22+
<Animated.Text
23+
key={key}
24+
style={styles.text}
25+
entering={FadeIn.duration(500).delay(index)}
26+
exiting={FadeOut.duration(500).delay(index)}
27+
layout={Layout.duration(Math.random() * 700 + 200).delay(index)}>
28+
{char}
29+
</Animated.Text>
30+
))}
31+
</Animated.View>
32+
);
33+
}
34+
35+
const LOREM_IPSUM_1 =
36+
'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.';
37+
38+
const LOREM_IPSUM_2 =
39+
'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.';
40+
41+
export default function LettersExample() {
42+
const [state, setState] = React.useState(true);
43+
44+
useEffect(() => {
45+
const id = setInterval(() => {
46+
setState((s) => !s);
47+
}, 2000);
48+
return () => clearInterval(id);
49+
}, []);
50+
51+
return (
52+
<View style={styles.container}>
53+
<Letters text={state ? LOREM_IPSUM_1 : LOREM_IPSUM_2} />
54+
</View>
55+
);
56+
}
57+
58+
const styles = StyleSheet.create({
59+
container: {
60+
flex: 1,
61+
alignItems: 'center',
62+
justifyContent: 'center',
63+
},
64+
line: {
65+
flexDirection: 'row',
66+
flexWrap: 'wrap',
67+
maxWidth: 325,
68+
},
69+
text: {
70+
fontFamily: Platform.OS === 'ios' ? 'Palatino' : 'serif',
71+
fontWeight: '500',
72+
fontSize: 23,
73+
},
74+
});

app/src/examples/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import ImageStackExample from './SharedElementTransitions/ImageStack';
3939
import InvertedFlatListExample from './InvertedFlatListExample';
4040
import KeyframeAnimation from './LayoutAnimations/KeyframeAnimation';
4141
import LayoutAnimationExample from './SharedElementTransitions/LayoutAnimation';
42+
import LettersExample from './LettersExample';
4243
import LightBoxExample from './LightBoxExample';
4344
import LiquidSwipe from './LiquidSwipe/LiquidSwipe';
4445
import ManyScreensExample from './SharedElementTransitions/ManyScreens';
@@ -144,6 +145,11 @@ export const EXAMPLES: Record<string, Example> = {
144145
title: 'Article progress',
145146
screen: ArticleProgressExample,
146147
},
148+
LettersExample: {
149+
icon: '📖',
150+
title: 'Letters',
151+
screen: LettersExample,
152+
},
147153

148154
// Basic examples
149155

ios/LayoutReanimation/REAAnimationsManager.m

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,8 @@ - (BOOL)startAnimationsRecursive:(UIView *)view
396396
return NO;
397397
}
398398

399-
BOOL hasExitAnimation = _hasAnimationForTag(view.reactTag, EXITING) || [_exitingViews objectForKey:view.reactTag];
399+
BOOL hasExitAnimation =
400+
[self hasAnimationForTag:view.reactTag type:EXITING] || [_exitingViews objectForKey:view.reactTag];
400401
BOOL hasAnimatedChildren = NO;
401402
shouldRemoveSubviewsWithoutAnimations = shouldRemoveSubviewsWithoutAnimations && !hasExitAnimation;
402403
NSMutableArray *toBeRemoved = [[NSMutableArray alloc] init];

0 commit comments

Comments
 (0)