Bottom Sheet
위 그림처럼, 아래에서 부터 위로 올라오는 일종의 보조 페이지를 Bottom Sheet
라고 한다.
react-native-bottom-sheet
내가 사용한 Bottom Sheet 라이브러리이다.
최근까지도 업데이트가 이루어지고, 내가 원하는 형태의 UI라서 사용해야겠다고 생각했다.
gothom Bottom Sheet 의 Document 사이트이다. 정리가 꽤 잘되어있는 것 같아 많아 참고했다.
설치 및 설정 방법
Getting Start 를 따라 따라하면 된다.
처음에는 적당히 설치 CLI만 보고 설치했다가, 실행이 안되어서;; 뭐가 문제인지 몰랐는데 제대로 읽지도 않고 설치했던 게 문제였다.
1. bottom sheet library 설치
$ yarn add @gorhom/bottom-sheet@^4
2. dependencies 추가
react-native-reanimated 와 react-native-gesture-handler 의존성을 추가해준다.
$ yarn add react-native-reanimated react-native-gesture-handler
3. React Native Gesture Handler 추가 설정
Gesture Handler 공식 문서에 보면, 의존성 추가 후에 엔트리 포인트에 <GestureHandlerRootView>
라는 컴포넌트를 추가하라고 명시되어있다.
이 컴포넌트는 일반 View
처럼 작동하기 때문에, 화면을 채우려면 flex:1
속성도 지정해야 한다.
$ yarn add react-native-gesture-handler
export default function App() {
return (
<!-- 최상단 -->
<GestureHandlerRootView style={{ flex: 1 }}>
{/* content */}
</GestureHandlerRootView>
);
}
4. React Native Reanimated v2 추가 설정
$ yarn add react-native-reanimated
의존성 추가 후, babel.config.js
파일에도 설정을 추가해야 한다.
reanimated 플러그인은 plugins 목록들 중 마지막줄에 추가해야 한다.
// babel.config.js
module.exports = {
...
plugins: [
...
// plungins 중 마지막에 추가되어야 한다.
'react-native-reanimated/plugin',
],
};
(error?)
플러그인 추가 후 "Reanimated 2 failed to create a worklet" error
에러가 날 수 있는데, 이는 대부분 앱 캐시를 삭제하면 해결된다.
$ yarn start --reset-cache
5. 변경내용 적용하기
$ npx pod-install
사용방법
나는 모달을 사용할 것이기 때문에, Bottom Sheet 공식 문서에 있는 예시코드를 복붙해왔다.
import React, { useCallback, useMemo, useRef } from 'react';
import { View, Text, StyleSheet, Button } from 'react-native';
import {
BottomSheetModal,
BottomSheetModalProvider,
} from '@gorhom/bottom-sheet';
const App = () => {
// ref
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
// variables
const snapPoints = useMemo(() => ['25%', '50%'], []);
// callbacks
const handlePresentModalPress = useCallback(() => {
bottomSheetModalRef.current?.present();
}, []);
const handleSheetChanges = useCallback((index: number) => {
console.log('handleSheetChanges', index);
}, []);
// renders
return (
<BottomSheetModalProvider>
<View style={styles.container}>
<Button
onPress={handlePresentModalPress}
title="Present Modal"
color="black"
/>
<BottomSheetModal
ref={bottomSheetModalRef}
index={1}
snapPoints={snapPoints}
onChange={handleSheetChanges}
>
<View style={styles.contentContainer}>
<Text>Awesome 🎉</Text>
</View>
</BottomSheetModal>
</View>
</BottomSheetModalProvider>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 24,
justifyContent: 'center',
backgroundColor: 'grey',
},
contentContainer: {
flex: 1,
alignItems: 'center',
},
});
export default App;
Android 터치 문제 발생
이렇게 복붙해오니 실행은 잘 되는데, Android 에서 모달창 스크롤이 안되는 것이었다.
구글링 해보니, 아까 의존성으로 추가했던 <GestureHandlerRootView> 이 렌더링 시 최상단으로 들어가 있어야 하는데, 예제 코드에서는 빠져있었다. <GestureHandlerRootView> 를 컴포넌트 최상단에다 넣어주자.
...
// renders
return (
<GestureHandlerRootView>
<BottomSheetModalProvider>
... ... ...
</BottomSheetModalProvider>
</GestureHandlerRootView>
);
...
뒷배경 누르면 모달창 닫기
이제 잘 움직이는 것 까지 확인했다.
뒷배경을 누르면 모달창이 사라지게 하는 코드는 아래와 같다. 추가해주자.
const renderBackdrop = useCallback(
(props: any) => <BottomSheetBackdrop {...props} pressBehavior="close" />,
[],
);
...
<BottomSheetModal
ref={bottomSheetModalRef}
index={1}
backdropComponent={renderBackdrop} // 추가
... >
Bottom Sheet 의 공식문서 중 BottomSheetBackdrop 부분을 참고하여 작성했는데 성공!
2022.8.3 Backdrop 추가
// variables
const snapPoints = useMemo(() => ['25%','65%'], []);
성공인줄 알았는데,, 바텀 시트가 멈추게 될 지점을 뜻하는 snap points가 25%, 65% 로 두군데가 설정되어있다 보니 중간에 멈추는 것이었다. 나는 바텀시트 65%로 한번 켜졌다가, 내리면 한번에 닫히길 원했다. 즉 내릴 때 멈추는 곳 없이 한번에 사라졌으면 했다.
근데 25% 로 걸리는 지점이 있어 스크롤해서 내릴 경우 다 닫히지 않는 것이다. 심지어 저 때는 Backdrop 먹히지도 않음. ㅋㅋ;
['0%', '65%'] 도 해봤는데 0값은 입력받을 수 없다며 오류가 났다. 최소 1% 부터 시작인 것 같다.
삽질결과!!!!! 진짜 찐으로 성공했다 ... ㅠ
1. 우선 snapPoints는 원하는 지점으로 하나만 적어준다.
// variables
const snapPoints = useMemo(() => ['65%'], []);
2. BottomSheet 컴포넌트에서 인덱스를 0으로 수정해준다.
snapPoints 로 지정해준 배열의 첫번째(0번째 인덱스)값만큼 바텀시트 높이를 올리겠다는 의미이다.
<BottomSheetModal
ref={bottomSheetModalRef}
index={0} // snapPoint 배열의 값 (65%)
snapPoints={snapPoints}
backdropComponent={renderBackdrop}
onChange={handleSheetChanges}>
...
3. backdropComponent props 로 가서 pressBehavior="close" 로 되어있는지 확인한다.
const renderBackdrop = useCallback(
props => (
<BottomSheetBackdrop
{...props}
pressBehavior="close"
/>
),
[],
);
여기까지 하면, 뒷배경을 아무리 눌러도 꺼지지 않는 바텀시트가 완성된다. 짜잔 ~~ ^^ㅗ
이다음부터 해결하느라 엄청난 삽질 ...
4. Backdrop 컴포넌트에 옵션을 추가한다!!
const renderBackdrop = useCallback(
props => (
<BottomSheetBackdrop
{...props}
pressBehavior="close"
appearsOnIndex={0} // 이거 추가
disappearsOnIndex={-1} // 이거 추가
/>
),
[],
);
나도 정확하게 이해한건 아니지만, 대충 이해한 걸로는 ...
bottomSheet의 index와, backdrop의 index가 별개로 있어서,
우리가 bottom sheet의 snap points 를 한개로 수정해버려 index값이 달라졌기 때문에,
bottom sheet와 backdrop의 index가 달라져서 나타나지 않은 것이다.
예를 들자면,
처음에 snap points 배열이 2개(25%, 65%)일 때는 index가 0, 1 이 가능했다.
위 사진의 backdrop 속성을 보면, 뒷배경이 나타나는 appearsOnIndex 속성의 default가 1이다. 즉, snap points가 index[1] 을 가르키고 있을 때 backdrop이 나타나는 것이다.
그래서 65% 를 가르키고 있을 때 뒷배경이 나와 뒷배경 터치가 가능했고,
0번째 인덱스인 25% 지점으로 내리면, disappearsOnIndex 속성의 default값 때문에 뒷배경이 사라지는 것이었다.
snap point를 하나 지웠을 때는 가능한 index가 0 뿐인데,
appearsOnIndex 는 default가 1이라서 1번째 인덱스일 때만 나타나기 때문에, 0번째 인덱스에서 놀고 있으니 아무리 삽질해도 나타나지 않는 것이었다 ㅠㅠ
위에 코드 적어놓은 것 처럼 appearsOnIndex 를 0으로 바꾸고, disapearsOnIndex를 -1 로 (바텀시트를 아예 닫았을때 배경이 사라지게) 수정하면 된다!!
도움이 되셨다면 좋아요나 댓글 부탁드립니다 :)!!!!!
'Study > React&ReactNative' 카테고리의 다른 글
React Native가 동작하는 원리 - RN의 Thread (0) | 2023.07.18 |
---|---|
React Native Patch Package 사용하기(라이브러리 패키지 패치, 수정하기) (0) | 2022.12.05 |
React Native Bottom Sheet 테두리 Radius 변경 (0) | 2022.08.03 |
React Native 버전과 TypeScript 동시에 다운그레이드하여 프로젝트 만들기 (2) | 2022.07.28 |
[React Native] Mac M1 개발환경 세팅 (22.12.05수정) (0) | 2022.07.21 |