Intro
- iPhone에서 ActionSheet가 홈 인디케이터와 겹치고, Android에서는 취소 버튼이 없을 때 닫히지 않는 문제가 있었습니다.
- 저는 Expo ActionSheet를 감싸는 커스텀 훅을 만들어 안전 영역과 플랫폼별 닫기 패턴을 맞췄습니다.
핵심 아이디어 요약
- Safe Area 값을 가져와 컨테이너 하단 패딩을 자동으로 추가했습니다.
- iOS에서 취소 버튼이 없을 경우 자동으로 '취소' 옵션을 붙였습니다.
- Android에서는 취소 버튼 없이도 외부 터치/백버튼으로 닫히도록
cancelButtonIndex를 -1로 지정했습니다.
준비와 선택
- 이미
@expo/react-native-action-sheet를 사용하고 있었기 때문에 머티리얼 디자인을 위한 별도 라이브러리를 추가하지 않았습니다.
- 훅을 통해
showActionSheetWithOptions만 감싸 기존 코드 변경을 최소화했습니다.
- 타입 안정성을 위해 ActionSheetOptions를 그대로 받아 전달했습니다.
구현 여정
- Safe Area 적용:
useSafeAreaInsets로 bottom inset을 받은 뒤 컨테이너 스타일에 병합했습니다.
- 취소 버튼 주입: iOS에서 기본 옵션 배열에 취소 버튼이 없으면 '취소'를 자동으로 추가했습니다.
- Cancel Button Index 조정: 플랫폼마다 다른 기본값을 설정해 닫기 동작이 일관되게 했습니다.
- 콜백 래핑: index가 undefined일 때를 대비해 0 이상일 때만 콜백에 넘겼습니다.
- 메모이제이션:
useMemo로 래핑된 함수를 재사용해 렌더링마다 새로운 함수를 생성하지 않게 했습니다.
export function useSafeActionSheet() {
const { showActionSheetWithOptions } = useActionSheet();
const insets = useSafeAreaInsets();
const safeShowActionSheetWithOptions = useMemo(() => {
return (
options: ActionSheetOptions,
callback: (i?: number) => void | Promise<void>,
) => {
const containerStyle: ViewStyle = {
paddingBottom: insets.bottom,
...(options.containerStyle ?? {}),
};
const baseOptions = options.options ?? [];
let nextOptions = baseOptions;
let nextCancelButtonIndex = options.cancelButtonIndex;
if (
Platform.OS === 'ios' &&
(typeof nextCancelButtonIndex === 'undefined' || nextCancelButtonIndex < 0)
) {
nextOptions = [...baseOptions, '취소'];
nextCancelButtonIndex = baseOptions.length;
}
const normalizedOptions: ActionSheetOptions = {
...options,
options: nextOptions,
cancelButtonIndex:
Platform.OS === 'ios'
? nextCancelButtonIndex
: typeof options.cancelButtonIndex === 'undefined'
? -1
: options.cancelButtonIndex,
containerStyle,
};
showActionSheetWithOptions(normalizedOptions, index => {
const normalizedIndex =
typeof index === 'number' && index >= 0 ? index : undefined;
return callback(normalizedIndex);
});
};
}, [insets.bottom, showActionSheetWithOptions]);
return {
showActionSheetWithOptions: safeShowActionSheetWithOptions,
};
}
결과와 회고
- iPhone에서 액션시트가 더 이상 홈 인디케이터를 가리지 않고, Android에서도 취소 동작이 자연스러워졌습니다.
- 새 훅을 적용한 이후 기존 코드 변경이 한 줄이라 유지보수가 쉬웠습니다.
- 다음에는 접근성(VoiceOver/토크백) 테스트를 추가해 읽기 순서를 개선할 계획입니다.
- 여러분은 액션시트를 어떻게 커스터마이즈하고 계신가요? 팁이 있다면 댓글로 나눠주세요.
Reference