React Native) 모달 안쪽에ScrollView / FlatList를 넣고 싶다면

2ast·2022년 10월 6일
2
post-custom-banner

모달 안쪽에서는 스크롤이 동작하지 않는다?

React Native에는 모달이라는 컴포넌트가 있다. 현재 페이지에서 추가적인 정보를 보여주거나 입력을 받을 때 사용하는 컴포넌트로, 다른 스크린으로 넘어가는 대신 현재 스크린 위에 조그만 창을 오버레이해서 보여주는 역할을 한다.

문제는 이 모달 안쪽에 ScrollView나 FlatList를 배치할 경우 스크롤에 반응하지 않는다는 것이다. 하지만 그 해결방법은 아주 간단한데, 바로 ScrollView 안쪽을 새로운 View로 감싸고 해당 View에 onStartShouldSetResponder 속성을 주면 된다.

<ScrollView>
	<View onStartShouldSetResponder={() => true}></View> 
</ScrollView>

FlatList의 경우에도 비슷한 방법으로 해결할 수 있다. 다만, FlatList의 경우 List 전체를 감싸는 컴포넌트를 설정할 수 없기 때문에 renderItem에서 각 아이템의 최상위를 View로 감싸고 해당 뷰에 onStartShouldSetResponder 속성을 주는 방법을 써야한다.

const renderItem = () => {
  return (
    <View ...somProps, onStartShouldSetResponder={() => true}>
      {list items...}
    </View>
  );
};

왜 문제가 발생했고, 왜 해결되었나?

사실 모든 경우에 모달 안쪽의 스크롤이 동작하지 않는 것은 아니다. 다만, 주로 앱에서 모달을 설정할 때 기본적으로 셋팅하는 코드들로부터 비롯된 문제였다. 무슨 말이냐면, 보통 앱에서 모달이 떴을 때 사람들은 모달의 바깥부분을 눌러서 창을 닫고 싶어한다. 실제로 예시로 첨부한 위 스크린샷을 보면 모달을 닫는 버튼 자체가 존재하지 않는다. 왜냐하면 모달의 바깥 부분을 누르면 모달이 사라지도록 설정해놨기 때문이다. 그리고 이런 사실은 모바일 생태계에 익숙한 사용자라면 모두가 자연스럽게 인지하고 있다. 이러한 코드를 구현하는 방식은 다음과 같다.

 <Modal visible={isVisible} transparent={true}>
  <TouchableOpacity style={{someStyles...}} onPress={() => setIsVisible(false)}>
    <TouchableWithoutFeedback onPress={() => {}}>
      {children} //모달 안쪽 컨텐츠
    </TouchableWithoutFeedback>
  </TouchableOpacity>
</Modal>

모달 안쪽에 화면 전체를 커버하는 TouchableOpacity가 하나 존재하고, TouchableOpacity 영역을 누르면 모달이 닫히도록 설정해놨다. 그리고 다시 그 안쪽에 눌러도 아무런 효과도 피드백도 없는 TouchableWithoutFeedback을 배치해놨다. 이렇게 하고TouchableWithoutFeedback 안쪽 영역에 원하는 컨텐츠를 배치한다면, 컨텐츠 영역은 아무리 터치해도 어떠한 이벤트도 발생하지 않으므로 평범한 View처럼 사용이 가능하지만, 바깥 영역을 터치하여 모달을 닫을 수도 있게 되는 것이다.

다만 이렇게 설계할 경우 본문의 주제와 마찬가지로 TouchableWithoutFeedback 안쪽의 ScrollView와 FlatList가 동작하지 않는 문제가 발생하는데, ScrollView가 감지해야할 터치 이벤트를 TouchableWithoutFeedback이 가져가 버리기 때문이다. 따라서 이때 사용하는 것이 바로 onStartShouldSetResponder 속성이다. 이 속성 값을 ture로 주면 해당 컴포넌트가 reponder로 지정되어 터치 이벤트에 응답을 줄 수 있게 된다. 실제로 모달 바깥쪽을 터치했을 때 사라지도록 하는 셋팅을 없애고 모두 View로 처리했을 때는 onStartShouldSetResponder 속성을 건드리지 않아도 정상적으로 스크롤 되는 것을 확인할 수 있다.

마지막으로 전체 Modal 구조를 정리하면 다음과 같다.

//ScrollView 사용했을 때
return <Modal visible={isVisible} transparent={true}>
  <TouchableOpacity style={{someStyles...}} onPress={() => setIsVisible(false)}>
    <TouchableWithoutFeedback onPress={() => {}}>
      <View style={{contentContainerStyle...}}>
        {...other content}
        <ScrollView>
          <View onStartShouldSetResponder={() => true}>
            {...scroll view content}
          </View>  
        </ScrollView>
        {...other content}
        <View>
    </TouchableWithoutFeedback>
  </TouchableOpacity>
</Modal>
    
---------------------------------------------------------
//FlatList 사용했을 때    
const renderItem =()=>{
	return <View style={{renderItemStyle...}} onStartShouldSetResponder={() => true}>
      {...List item}
    </View>  
}
    
return <Modal visible={isVisible} transparent={true}>
  <TouchableOpacity style={{someStyles...}} onPress={() => setIsVisible(false)}>
    <TouchableWithoutFeedback onPress={() => {}}>
      <View style={{contentContainerStyle...}}>
        {...other content}
	    <FlatList
        data={data}
        keyExtractor={()=>{}}
        renderItem={renderItem}
        />
        {...other content}
        <View>
    </TouchableWithoutFeedback>
  </TouchableOpacity>
</Modal>
profile
React-Native 개발블로그
post-custom-banner

2개의 댓글

comment-user-thumbnail
2024년 3월 22일

살려주셔서 감사합니다 ..... 큰 도움이 됐습니다 ..... 🥺

1개의 답글