띠오니 개발자 성장일지
article thumbnail
반응형

Bottom Sheet

enter image description here

위 그림처럼, 아래에서 부터 위로 올라오는 일종의 보조 페이지를 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가 달라져서 나타나지 않은 것이다.

 

react native bottom sheet 문서 - BottomSheetBackdrop 속성값

 

예를 들자면,

처음에 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 로 (바텀시트를 아예 닫았을때 배경이 사라지게) 수정하면 된다!!

 

성공!!

 

 

도움이 되셨다면 좋아요나 댓글 부탁드립니다 :)!!!!!

반응형
profile

띠오니 개발자 성장일지

@띠오니

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!