728x90
반응형

Flatlist 나 scrollview에서 pagingEnabled 를 하면, snap 하는 느낌은 들지만, scroll의 offset 이 맞지않는 경우가 많다..

 

이를 위해

 

const offset = heightScale(600) + 2 * cardVerticalMargin;
const snapToOffsets = useMemo(
() =>
Array.from(Array(DUMMY.length)).map(
(_, index) => index * offset - headerHeight,
),
[DUMMY],
);
 

이런식으로 사용하면,

 

어느 offset들에게 걸릴지가 snapToOffesets props 에 의해 flatlist / scrollview 에 전달된다.

 

pagingEnabled
decelerationRate="fast"
scrollEventThrottle={16}
snapToOffsets={snapToOffsets}

주로 같이쓰는 옵션들이다.

728x90
반응형
728x90
반응형

 

이번 앱 디자인에는 animated 를 많이 사용한 UI 가 들어간다.

 

animated view 의 핵심은, state로 ui를 변화시키는 것을 지양하고, 

 

const animatedY = useRef(new Animated.Value(0)).current;

처럼 ref로 관리해줘야 리랜더링을 방지하여 매끄러운 애니메이션 효과가 나온다.

 

기본 사용은 

 

const translateY = animatedY.interpolate({
inputRange: [0, bannerHeight - headerHeight - safeAreaTop],
outputRange: [0, -bannerHeight + headerHeight + safeAreaTop],
extrapolate: 'clamp',
});

이런 형태로, translate를 한번해주어, 스크롤 input과 output 의 레인지를 계산해주고(해당 코드는 헤더가 스크롤할때 아래로 이동하도록 하는 코드이다)

 

clamp로 묶어주어, input range 의 밖의 값이 입력되었을때, output range의 큰 값이 출력되도록 해준다.

 

애니메이션 효과를 입히고 싶은 컴포넌트에 

 

<Animated.View
style={{
flexDirection: 'column',
transform: [{ translateY: animatedY }],
}}
>

해당 형태로 추가해준다.

 

animated.timing 으로 시작한 애니메이션도 있고, scroll을 통한 offset으로 해당하는 애니메이션도 있어 풀 소스코드를 올려놓는다.

 

const safeAreaTop = useSafeAreaInsets().top;

를 통해 ios safeArea에도 대처했다.

 

import { useCallback, useRef } from 'react';
import {
Animated,
Dimensions,
NativeScrollEvent,
NativeSyntheticEvent,
Platform,
Text,
UIManager,
View
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { heightScale } from '../../util/Layout';
import { HomeHeader } from './HomeHeader';

const DUMMY = [
{
index: 0,
},
{
index: 1,
},
{
index: 2,
},
{
index: 3,
},
{
index: 4,
},
{
index: 5,
},
{
index: 6,
},
{
index: 7,
},
];

const deviceWidth = Dimensions.get('window').width;
const bannerHeight = (deviceWidth * 600) / 480;
const headerHeight = heightScale(75);

if (
Platform.OS === 'android' &&
UIManager.setLayoutAnimationEnabledExperimental
) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}

export const HomeScreen = () => {
let currentOffset = 0;
const animatedY = useRef(new Animated.Value(0)).current;
const animatedBackground = useRef(new Animated.Value(0)).current;
const animatedOpacity = useRef(new Animated.Value(0)).current;

const safeAreaTop = useSafeAreaInsets().top;

const animationStart = useCallback(
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
let direction =
event.nativeEvent.contentOffset.y > currentOffset ? 'down' : 'up';
currentOffset = event.nativeEvent.contentOffset.y;

if (
direction === 'down' &&
event.nativeEvent.contentOffset.y > bannerHeight / 3
) {
console.log('down');
Animated.parallel([
Animated.timing(animatedBackground, {
useNativeDriver: true,
duration: 300,
toValue: 1,
}),
Animated.timing(animatedOpacity, {
useNativeDriver: true,
duration: 300,
toValue: 1,
}),
]).start();
}
if (
direction === 'up' &&
event.nativeEvent.contentOffset.y < bannerHeight / 3
) {
console.log('up');
Animated.parallel([
Animated.timing(animatedBackground, {
useNativeDriver: true,
duration: 300,
toValue: 0,
}),
Animated.timing(animatedOpacity, {
useNativeDriver: true,
duration: 300,
toValue: 0,
}),
]).start();
}
},
[],
);

// const transY = Animated.
const translateY = animatedY.interpolate({
inputRange: [0, bannerHeight - headerHeight - safeAreaTop],
outputRange: [0, -bannerHeight + headerHeight + safeAreaTop],
extrapolate: 'clamp',
});

const translateHeaderY = animatedY.interpolate({
inputRange: [0, bannerHeight - headerHeight - safeAreaTop],
outputRange: [0, bannerHeight - headerHeight - safeAreaTop],
extrapolate: 'clamp',
});

const onScroll = Animated.event(
[{ nativeEvent: { contentOffset: { y: animatedY } } }],
{
useNativeDriver: true,
listener: (event) => {
animationStart(event);
},
},
);

return (
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'white',
}}
>
<Animated.FlatList
style={{ flex: 1 }}
data={DUMMY}
ListHeaderComponent={() => {
return (
<HomeHeader
animatedY={translateY}
translateHeaderY={translateHeaderY}
safeAreaTop={safeAreaTop}
animatedBackground={animatedBackground}
bannerHeight={bannerHeight}
animatedOpacity={animatedOpacity}
headerHeight={headerHeight}
/>
);
}}
onScroll={onScroll}
// pagingEnabled
stickyHeaderIndices={[0]}
renderItem={({ item, index }) => {
return (
<View
style={{
width: 100,
height: heightScale(600),
borderWidth: 1,
backgroundColor: 'pink',
}}
>
<Text style={{ fontSize: 40 }}>{index}</Text>
</View>
);
}}
/>
</View>
);
};
import React from 'react';
import {
Animated,
Dimensions,
Image,
TouchableOpacity,
View,
} from 'react-native';
import AlarmBlack from '../../assets/icons/alarmBlack.svg';
import AlarmWhite from '../../assets/icons/alarmWhite.svg';
import { heightScale, widthScale } from '../../util/Layout';

type HomeHeaderProps = {
animatedY: Animated.AnimatedInterpolation<string | number>;
translateHeaderY: Animated.AnimatedInterpolation<string | number>;
safeAreaTop: number;
animatedBackground: Animated.Value;
bannerHeight: number;
animatedOpacity: Animated.Value;
headerHeight: number;
};
const deviceWidth = Dimensions.get('window').width;

export const HomeHeader = ({
animatedY,
translateHeaderY,
safeAreaTop,
animatedBackground,
bannerHeight,
animatedOpacity,
headerHeight,
}: HomeHeaderProps) => {
return (
<Animated.View
style={{
flexDirection: 'column',
transform: [{ translateY: animatedY }],
}}
>
<Animated.View
style={{
position: 'absolute',
top: safeAreaTop,
width: deviceWidth,
height: headerHeight,
zIndex: 1,
transform: [{ translateY: translateHeaderY }],
backgroundColor: animatedBackground.interpolate({
inputRange: [0, 1],
outputRange: ['#FFFFFF00', '#FFFFFF'],
}),
alignItems: 'center',
justifyContent: 'center',
}}
>
<Image
source={require('../../assets/images/headerLogo.png')}
style={{
width: widthScale(50),
height: heightScale(35),
}}
resizeMode="contain"
/>
<TouchableOpacity
style={{
width: headerHeight,
height: headerHeight,
position: 'absolute',
right: 0,
justifyContent: 'center',
alignItems: 'center',
top: 0,
}}
>
<Animated.View
style={{
zIndex: 2,
position: 'absolute',
opacity: animatedOpacity,
}}
>
<AlarmBlack width={widthScale(40)} height={widthScale(40)} />
</Animated.View>
<AlarmWhite width={widthScale(40)} height={widthScale(40)} />
</TouchableOpacity>
</Animated.View>

<TouchableOpacity
style={{
width: deviceWidth,
height: bannerHeight,
backgroundColor: 'orange',
}}
></TouchableOpacity>
<View
style={{
width: '100%',
height: headerHeight,
backgroundColor: 'blue',
}}
></View>
<View
style={{
width: '100%',
height: headerHeight,
backgroundColor: 'red',
}}
></View>
</Animated.View>
);
};
728x90
반응형
728x90
반응형
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: positionY } } }],
{
useNativeDriver: true,
listener: (event) => {
animationStart(event);
},
},
)}

기존 리스너함수를 listener 로 명시하여 사용하면 된다.

728x90
반응형
728x90
반응형

ios에서 노치디자인에서 헤더를 투명화 하기위해

 

헤더의 높이를 조절해야 할 일이 생겼다.

 

animated view로 헤더의 position을

transform: [
{
translateY: safeAreaTopPosition.interpolate({
inputRange: [0, 1],
outputRange: [0, insets.top],
}),
},
],

로 옮겨주는데,

 

그렇게되면 status bar 에 겹치는 현상이 발생하기때문에, height 값조정과 내용들의 정렬화 애니메이션이 필요하게되었다.

 

height 값은 state값으로 조절해주고

 

setState에서 값을 변경 한뒤

 

LayoutAnimation.configureNext(LayoutAnimation.Presets.linear);

를 통하면 자연스럽게 늘어나는 UI를 입혀준다.

 

최종 소스는

 

import { useCallback, useRef, useState } from 'react';
import {
Animated,
Dimensions,
FlatList,
LayoutAnimation,
NativeScrollEvent,
NativeSyntheticEvent,
Platform,
View,
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { heightScale } from '../../util/Layout';

const DUMMY = [
{
index: 0,
},
{
index: 1,
},
{
index: 2,
},
{
index: 3,
},
{
index: 4,
},
{
index: 5,
},
{
index: 6,
},
{
index: 7,
},
];

const deviceWidth = Dimensions.get('window').width;
const bannerHeight = (deviceWidth * 600) / 480;

const ListHeader = () => {
return (
<View style={{ flexDirection: 'column' }}>
<View
style={{
width: deviceWidth,
height: bannerHeight,
backgroundColor: 'orange',
}}
></View>
<View
style={{
width: '100%',
height: heightScale(90),
backgroundColor: 'blue',
}}
></View>
<View
style={{
width: '100%',
height: heightScale(90),
backgroundColor: 'red',
}}
></View>
</View>
);
};

export const HomeScreen = () => {
const viewRef = useRef(null);
var currentOffset = 0;
const backgroundColor = useRef(new Animated.Value(0)).current;
const safeAreaTopPosition = useRef(new Animated.Value(1)).current;
const safeAreaTopPadding = useRef(new Animated.Value(0)).current;
const [animatedHeaderHeight, setAnimatedHeaderHeight] = useState(
heightScale(75),
);
const insets = useSafeAreaInsets();
const fadingIn = useCallback(() => {
Animated.timing(backgroundColor, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}).start();
if (Platform.OS === 'ios') {
Animated.timing(safeAreaTopPosition, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}).start();
Animated.timing(safeAreaTopPadding, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}).start();
setAnimatedHeaderHeight(heightScale(75) + insets.top);
LayoutAnimation.configureNext(LayoutAnimation.Presets.linear);
}
}, []);
const fadingOut = useCallback(() => {
Animated.timing(backgroundColor, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}).start();
if (Platform.OS === 'ios') {
Animated.timing(safeAreaTopPosition, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}).start();
Animated.timing(safeAreaTopPadding, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}).start();
setAnimatedHeaderHeight(heightScale(75));
LayoutAnimation.configureNext(LayoutAnimation.Presets.linear);
}
}, []);

const animationStart = useCallback(
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
let direction =
event.nativeEvent.contentOffset.y > currentOffset ? 'down' : 'up';
currentOffset = event.nativeEvent.contentOffset.y;

if (
direction === 'down' &&
event.nativeEvent.contentOffset.y > bannerHeight / 2
) {
fadingIn();
}
if (
direction === 'up' &&
event.nativeEvent.contentOffset.y < bannerHeight / 2
) {
fadingOut();
}
},
[],
);

return (
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'white',
}}
>
<Animated.View
ref={viewRef}
style={{
width: '100%',
borderWidth: 1,
height: animatedHeaderHeight,
position: 'absolute',
top: 0,
zIndex: 1,
backgroundColor: backgroundColor.interpolate({
inputRange: [0, 1],
outputRange: ['#FFFFFF00', '#FFFFFF'],
}),
transform: [
{
translateY: safeAreaTopPosition.interpolate({
inputRange: [0, 1],
outputRange: [0, insets.top],
}),
},
],
}}
>
<Animated.Text
style={[
{ color: 'black' },
{
transform: [
{
translateY: safeAreaTopPosition.interpolate({
inputRange: [0, 1],
outputRange: [insets.top, 0],
}),
},
],
},
]}
>
headerheaderheaderheaderheader
</Animated.Text>
</Animated.View>
<FlatList
style={{ flex: 1 }}
data={DUMMY}
ListHeaderComponent={ListHeader}
onScroll={(event) => animationStart(event)}
renderItem={() => {
return (
<View
style={{
width: 100,
height: 100,
borderWidth: 1,
backgroundColor: 'pink',
}}
></View>
);
}}
/>
</View>
);
};
728x90
반응형
728x90
반응형

이번에 애니메이션이 많이 들어간 앱을 개발하면서, 작업에 재미가 붙고있다.

 

 

 

해당 코드는

 

import { useCallback, useRef } from 'react';
import {
Animated,
Dimensions,
FlatList,
NativeScrollEvent,
NativeSyntheticEvent,
Text,
View,
} from 'react-native';
import { heightScale } from '../../util/Layout';

const DUMMY = [
{
index: 0,
},
{
index: 1,
},
{
index: 2,
},
{
index: 3,
},
{
index: 4,
},
{
index: 5,
},
{
index: 6,
},
{
index: 7,
},
];

const deviceWidth = Dimensions.get('window').width;

const ListHeader = () => {
return (
<View>
<View
style={{
width: deviceWidth,
height: heightScale(630),
backgroundColor: 'orange',
}}
></View>
</View>
);
};

export const HomeScreen = () => {
const viewRef = useRef(null);
var currentOffset = 0;
const backgroundColor = useRef(new Animated.Value(0)).current;
const fadingIn = useCallback(() => {
Animated.timing(backgroundColor, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}).start();
}, []);
const fadingOut = useCallback(() => {
Animated.timing(backgroundColor, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}).start();
}, []);

const animationStart = useCallback(
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
let direction =
event.nativeEvent.contentOffset.y > currentOffset ? 'down' : 'up';
currentOffset = event.nativeEvent.contentOffset.y;

if (
direction === 'down' &&
event.nativeEvent.contentOffset.y > heightScale(630) / 2
) {
fadingIn();
}
if (
direction === 'up' &&
event.nativeEvent.contentOffset.y < heightScale(630) / 2
) {
fadingOut();
}
},
[],
);

return (
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'white',
}}
>
<Animated.View
ref={viewRef}
style={{
width: '100%',
height: heightScale(75),
borderWidth: 1,
position: 'absolute',
top: 0,
zIndex: 1,
backgroundColor: backgroundColor.interpolate({
inputRange: [0, 1],
outputRange: ['#FFFFFF00', '#FFFFFF'],
}),
}}
>
<Text style={{ color: 'black' }}>headerheaderheaderheaderheader</Text>
</Animated.View>
<FlatList
style={{ flex: 1 }}
data={DUMMY}
ListHeaderComponent={ListHeader}
onScroll={(event) => animationStart(event)}
renderItem={() => {
return (
<View
style={{
width: 100,
height: 100,
borderWidth: 1,
backgroundColor: 'pink',
}}
></View>
);
}}
/>
</View>
);
};
728x90
반응형
728x90
반응형

 

 

 

앱을 만드는 중에, 바텀탭버튼에 애니메이션 효과를 부탁받았다.

늘 Lottie파일로 구현했는데, 이번에는 

react-native-svg 로 아이콘을 보이고, 감싸는 view에 애니메이션 효과를 주면서 해결했다. 코드는

 

// import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import {
BottomTabBarButtonProps,
createBottomTabNavigator,
} from '@react-navigation/bottom-tabs';
import { StackScreenProps } from '@react-navigation/stack';
import React, { useEffect, useRef } from 'react';
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import SplashScreen from 'react-native-splash-screen';
import CommunityOff from '../../assets/icons/bottomtab/communityOff.svg';
import CommunityOn from '../../assets/icons/bottomtab/communityOn.svg';
import HomeOff from '../../assets/icons/bottomtab/homeOff.svg';
import HomeOn from '../../assets/icons/bottomtab/homeOn.svg';
import MyOff from '../../assets/icons/bottomtab/myOff.svg';
import MyOn from '../../assets/icons/bottomtab/myOn.svg';
import SupportOff from '../../assets/icons/bottomtab/supportOff.svg';
import SupportOn from '../../assets/icons/bottomtab/supportOn.svg';
import TvOff from '../../assets/icons/bottomtab/tvOff.svg';
import TvOn from '../../assets/icons/bottomtab/tvOn.svg';
import { WowplanetHeader } from '../../components/WowplanetHeader';
import { RootStackParamList } from '../../navigation/RootNavigator';
import { CommunityScreen } from '../CommunityScreen';
import { HomeScreen } from '../HomeScreen';
import { MyScreen } from '../MyScreen';
import { SupportScreen } from '../SupportScreen';
import { TvScreen } from '../TvScreen';
import * as Animatable from 'react-native-animatable';
import { heightScale } from '../../util/Layout';

export type BottomTabParamList = {
HomeScreen: undefined;
SupportScreen: { userId: string };
CommunityScreen: { sort: 'latest' | 'top' } | undefined;
TvScreen: { sort: 'latest' | 'top' } | undefined;
MyScreen: undefined;
};

const TabBarIcon = ({ name, focused }: any) => {
console.log('route ::', name);
switch (name) {
case 'SupportScreen':
return (
<View
style={[
{
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
margin: 0,
},
]}
>
{focused ? (
<SupportOn width={35} height={35} />
) : (
<SupportOff width={35} height={35} />
)}
</View>
);
case 'CommunityScreen':
return (
<View
style={{
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
}}
>
{focused ? (
<CommunityOn width={35} height={35} />
) : (
<CommunityOff width={35} height={35} />
)}
</View>
);
case 'HomeScreen':
return (
<View
style={{
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
}}
>
{focused ? (
<HomeOn width={35} height={35} />
) : (
<HomeOff width={35} height={35} />
)}
</View>
);
case 'TvScreen':
return (
<View
style={{
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
}}
>
{focused ? (
<TvOn width={35} height={35} />
) : (
<TvOff width={35} height={35} />
)}
</View>
);
case 'MyScreen':
return (
<View
style={{
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
}}
>
{focused ? (
<MyOn width={35} height={35} />
) : (
<MyOff width={35} height={35} />
)}
</View>
);
}
};

const TabArr = [
{
route: 'SupportScreen',
label: 'SupportScreen',
component: SupportScreen,
},
{
route: 'CommunityScreen',
label: 'CommunityScreen',
component: CommunityScreen,
},
{
route: 'HomeScreen',
label: 'HomeScreen',
component: HomeScreen,
},
{
route: 'TvScreen',
label: 'TvScreen',
component: TvScreen,
},
{
route: 'MyScreen',
label: 'MyScreen',
component: MyScreen,
},
];

const TabButton = (props: BottomTabBarButtonProps) => {
const { item, onPress, accessibilityState } = props;
const focused = accessibilityState.selected;
const viewRef = useRef(null);
console.log('item :::', item);

useEffect(() => {
if (focused) {
viewRef.current.animate({
0: { scale: 0.5, rotate: '360deg' },
1: { scale: 1.0, rotate: '0deg' },
});
} else {
viewRef.current.animate({
0: { scale: 1.0, rotate: '-360deg' },
1: { scale: 1, rotate: '0deg' },
});
}
}, [focused]);

return (
<TouchableOpacity
onPress={onPress}
activeOpacity={1}
style={styles.container}
>
<Animatable.View ref={viewRef} duration={1000} style={styles.container}>
<TabBarIcon focused={focused} name={item.route} />
</Animatable.View>
</TouchableOpacity>
);
};

export const BottomTabNavigator = (
props: StackScreenProps<RootStackParamList, 'BottomTab'>,
) => {
const Tab = createBottomTabNavigator<BottomTabParamList>();
console.log('rendering BottomTabNavigator :::::::::::');

useEffect(() => {
SplashScreen.hide();
}, []);

return (
<View style={{ flex: 1 }}>
<WowplanetHeader navigation={props.navigation} route={props.route} />
<Tab.Navigator
screenOptions={() => ({
headerShown: false,
tabBarShowLabel: false,
lazy: true,
tabBarStyle: {
height: heightScale(75),
},
})}
screenListeners={({ route }) => ({
tabPress: () => {
console.log(route.name);
},
})}
initialRouteName="HomeScreen"
>
{TabArr.map((item, index) => {
return (
<Tab.Screen
key={index}
name={item.route}
component={item.component}
options={{
tabBarShowLabel: false,
tabBarButton: (props) => <TabButton {...props} item={item} />,
}}
/>
);
})}
</Tab.Navigator>
</View>
);
};
728x90
반응형
728x90
반응형

firebase ios 를 세팅하다가 해당 이슈가 계속 뜨기 시작했다.

The operation couldn’t be completed. No APNS token specified before fetching FCM Token 

 

import notifee from '@notifee/react-native';
 
const res = await notifee.requestPermission();

로 해결했다.

 

ios같은 경우 authorizationStatus 가 1이어야 정상 작동했다.

 

해당 코드에서 res.ios.authorizationStatus === 1 이 아닌경우, requestPermission을 작동시켰다.

728x90
반응형
728x90
반응형

https://www.npmjs.com/package/react-native-lottie-splash-screen

 

react-native-lottie-splash-screen

A lottie splash screen for react-native, hide when application loaded ,it works on iOS and Android.. Latest version: 1.1.2, last published: 3 months ago. Start using react-native-lottie-splash-screen in your project by running `npm i react-native-lottie-sp

www.npmjs.com

 

해당 npm package 를 적용할때, ios에서 몇가지 이벤트가 있어서 글을 작성한다.

 

안드로이드같은경우는, 해당 안내에 따라 한번에 적용이 되었는데,

 

IOS 같은 경우는 자동으로 헤더가 생기지 않아서 어려움을 겪었다.

 

최종 프로젝트 트리는

이다. xcode 에서 Dynamic.swift를 추가해줘야 자동으로 코드가 생긴다.

 

Dynamic.swfit 코드는

 

import UIKit
import Foundation
import Lottie

@objc class Dynamic: NSObject {

@objc func createAnimationView(rootView: UIView, lottieName: String) -> LottieAnimationView {
let animationView = LottieAnimationView(name: lottieName)
animationView.frame = rootView.frame
animationView.center = rootView.center
animationView.backgroundColor = UIColor.white;
return animationView;
}

@objc func play(animationView: LottieAnimationView) {
animationView.play(
completion: { (success) in
RNSplashScreen.setAnimationFinished(true)
}
);
}
}

로, 기존 AnimationView를 LottieAnimationView로 바꿔줘야한다는 npm 개발자의 코멘트가 있었다.

 

AppDelegate.mm 의 코드중 일부는

 

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([FIRApp defaultApp] == nil) {
[FIRApp configure];
}
 .
 .
 .

// return [super application:application didFinishLaunchingWithOptions:launchOptions];
BOOL success = [super application:application didFinishLaunchingWithOptions:launchOptions];
 
if (success) {
//This is where we will put the logic to get access to rootview
UIView *rootView = self.window.rootViewController.view;
 
rootView.backgroundColor = [UIColor whiteColor]; // change with your desired backgroundColor
 
Dynamic *t = [Dynamic new];
UIView *animationUIView = (UIView *)[t createAnimationViewWithRootView:rootView lottieName:@"loading"]; // change lottieName to your lottie files name
 
// register LottieSplashScreen to RNSplashScreen
[RNSplashScreen showLottieSplash:animationUIView inRootView:rootView];
// casting UIView type to AnimationView type
LottieAnimationView *animationView = (LottieAnimationView *) animationUIView;
// play
[t playWithAnimationView:animationView];
// If you want the animation layout to be forced to remove when hide is called, use this code
[RNSplashScreen setAnimationFinished:true];
}
 
return success;
 
}

로 수정되어야한다. rn 0.71버전 상위 버전에 바뀌어야하는 코드와 AnimationView를 LottieAnimationView 로 변환시켜줘야한다.

lottieName도 해당 json파일로 변경 시켜줘야한다.

 

 

728x90
반응형
728x90
반응형

https://www.npmjs.com/package/react-native-signup-checkbox

 

react-native-signup-checkbox

checkbox list when signup for react native. Latest version: 1.1.0, last published: an hour ago. Start using react-native-signup-checkbox in your project by running `npm i react-native-signup-checkbox`. There are no other projects in the npm registry using

www.npmjs.com

 

이번에 앱을 만들면서, 시간이 좀 남아서 npm package 를 한번 만들어보았다.

 

 

728x90
반응형
728x90
반응형

ImageBackground 에 이미지를 받고 apk 를 생성하니, 해당 에러가 튀어나왔다.

 

stackoverflow 검색결과 확장자를 바꾸면 해결된다는 이야기가 있었다.

 

디자인 팀에서 받은 png 파일을 jpg 로 바꾼뒤, duplicated 에러를 확인하여

 

./gradlew clean 후 다시 ./gradlew assembleRelease 로 apk 를 생성해줬더니 문제가 해결되었다.

728x90
반응형

+ Recent posts