문제 상황
android와 IOS는 같은 속성이라도 다르게 적용되는 값들이 존재한다.
대표적 예시가 keyboard와 관련된 순간!
position absolute의 bottom 0인 element의 경우
android에서는 키보드가 올라올 경우 자동적으로 키보드를 제외한 화면 높이를 100%로 계산하지만(adjustResize),
IOS는 키보드가 올라오더라도 전체 높이가 조절되지 않는다.
이러한 상황을 해결하기 위해 React Native에서 제공하는 기본 컴포넌트가
→ KeyboardAvoidingView 되시겠다.
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={styles.container}
>
...
</>
현 앱에서도 KeyboardAvoidingView를 사용해 input이 있는 페이지의 UI를 구성해주고 있었다.
그런데 문제..!
bottom 0에 위치하는 absolute한 버튼이...
키보드가 올라오니 숨어버리는 것 아닌가???
해당 페이지는 KeyboardAvoidingView를 최상위에 감싼 상태였고,
파란색 버튼은 position abolute , bottom 0px
→ 즉 (이론상) 키보드가 올라올 경우, 해당 페이지의 padding이 키보드 높이 만큼 생기면서, bottom 0인 버튼은 키보드에 딱 붙게 될 터였다!
하지만 왠지 모르게 bottom -60px 정도에 버튼이 위치하는 상황이 발생했다. 뭐지?
KeyboardAvoidingView - keyboardVerticalOffset
알 수가 없으니 공식 문서를 보는 수 밖에!
keyboardVerticalOffset
This is the distance between the top of the user screen and the react native view, may be non-zero in some use cases.
keyboardVerticalOffset 라는 속성이 존재했다!
screen top과 react native view사이의 거리...라는데,
기본값은 0이지만, 몇 몇 상황에서는 0이 아니라는 것 아닌가?
screen top과 react native view 사이 거리는 당연히 0 아닌가...라 생각했지만
떡 하니 screen top과 react native view 사이 공간이 있다!
왜? → 노치나 모서리까지 앱의 view가 차지할 경우, 해당 부분이 가려버리기 때문에,
SafeAreaView(역시 React Native에서 제공)로 앱 전체 screen을 감싸주었다.
아하!
즉 일반적인 상황에서 KeyboardAvoidingView의 keyboardVerticalOffset 속성값은 0이며,
이는 screen top과 react native view 사이의 거리를 의미하기 때문에,
해당 거리가 멀거나 가까워진 경우, keyboardVerticalOffset으로 해당 거리를 설정해줘야,
KeyboardAvoidingView의 정확한 screen height - keyboard height 값 계산이 가능해지는 것!!!
StatusBarHeight
이는 screen top과 react native view 사이의 거리를 의미하기 때문 → 그럼 그 거리는 어떻게 알지?
screen top과 view사의의 safe area는 status bar만큼 존재할테니...
현 기기의 status bar의 높이가 바로 해당 거리와 동일할테다!
https://github.com/ovr/react-native-status-bar-height
React Native에서 status bar의 높이를 알 수 있는
react-native-status-bar-height 라이브러리를 사용했다!
해당 패키지의 getStatusBarHeight 함수를 통해 IOS 기종의 status bar 높이를 구할 수 있다.
(android의 경우 항상 0이 리턴되는데, 이 경우 직접 react-native에서 StatusBar를 import해, StatusBar.currentHeight 속성으로 상태바 높이를 구할 수 있다고 한다)
(라고 적었는데, 그럼 IOS에서도 StatusBar.currentHeight로 상태바 높이를 구하면 되는거 아닌가?하는 생각이 들어 콘솔을 찍어봤더니... 일단 시뮬레이터에서는 해당 값이 null인 상황. 추가적인 확인이 필요하다)
그렇다면 이제 할 일은..!
const statusBarHeight = getStatusBarHeight()
...
return (
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={styles.container}
keyboardVerticalOffset={statusBarHeight}
>
...
)
keyboardVerticalOffset에 screen top과 react native view만큼의 간격을 지정해주면 되겠지!
저...저기요..?
DYNAMIC_ISLAND
이론상 키보드 위에 딱 붙어야 할 버튼이 살짝 아래로 내려가버렸다.
하지만 여러 삽질과 테스트 결과...
- KeyboardAvoidingView 문제 없음
- keyboardVerticalOffset 속성 문제 없음(SafeAreaView를 빼거나,특정 상수값을 넣어주면 정상적으로 버튼이 위치했다)
그렇다면? → statusBar height 계산이 잘못된 것 아닐까?
https://github.com/ovr/react-native-status-bar-height/issues/47
해당 라이브러리의 issues를 탐색한 결과... 아하!
iphone14 pro, ipone14 pro max에 적용된 dynamic island로 인해,
해당 라이브러리에서 정확한 상태바 높이 계산이 이뤄지지 않은 문제가 발생 중이었다..!
(실제로 위 코드를 dynamic island가 없는 기종에서 테스트 했을 때는,
의도한 바와 같이 키보드 바로 위에 버튼이 위치함을 확인할 수 있었다)
따라서...
dynamic island가 존재하는 두 기종(iphone14 pro, ipone14 pro max)의
너비와 높이(pro = 393x852 , pro max = 430x932),
그리고 상태바의 높이(pro, max 둘 다 59)는 제품 명세 상 명시되어 있으므로..!
현 디바이스의 window width와 height가
dynamic island가 존재하는 기종에 포함될 경우 → statusBar를 59로,
그 이외의 경우 → getStatusBar()로 상태바 높이를 지정할 수 있었다!
즉
// App.js
const {statusBarHeight} = useGetCustomStatusBarHeightHooks()
...
return (
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={styles.container}
keyboardVerticalOffset={statusBarHeight}
>
...
)
// useGetCustomStatusBarHeightHooks
const useGetCustomStatusBarHooks = () => {
const [statusBarHeight, setStatusBarHeight] = useState(getStatusBarHeight()); // 기본적으로 getStatusBarHeight 값을 statusBarHeight로
const { width, height } = useWindowDimensions(); // 디바이스 너비, 높이(React Native 제공 hooks)
const checkIsDynamicIslandExistDevice = (width, height) =>
// DYNAMIC_ISLAND가 존재하는 기종인지, width와 height를 통해 판단
Platform.OS === "ios" &&
((width === IP14_PRO_WIDTH && height === IP14_RPO_HEIGHT) ||
(width === IP14_MAX_WIDTH && height === IP14_MAX_HEIGHT))
useEffect(() => {
const isDynamicIslandExist = checkIsDynamicIslandExistDevice(width, height);
isDynamicIslandExist &&
setStatusBarHeight(DYNAMIC_ISLAND_STATUS_BAR_HEIGHT);
// DYNAMIC_ISLAND가 존재하는 기종일 경우 statusBarHeight를 상수(59)로 업데이트
}, []);
return { statusBarHeight };
};
다음과 같이 작성해줄 수 있었고,
성공적으로 구현되었음을 볼 수 있었다! 와!
고민
React Native를 통한 앱 개발은 큰 장점이 있다.
환경 세팅, 기본 제공 tool, OTA 업데이트 등!
하지만 이런 troubleshooting을 겪을 때마다, 큰 아쉬움이 남는 건 어쩔 수 없다.
얼핏 생각했을 때는 가장 기본적이고 간단한 로직(키보드 높이를 제외한 화면 높이 구하기)인데...
native적인 접근이 필요한 순간부터 갑자기 바보가 되는 듯한 느낌.
이번 troubleshooting도 구현은 성공했지만 근본적인 문제는 여전하니 말이다.
(라이브러리에 의존적, 상수로 해당 값 관리, dynamic island 기종 하드코딩)
사용자들의 최고의 경험을 하게 만들고 싶은데, native적인 요소에 접근해야하는 앱에서는
반드시 native 언어로 앱을 개발하는 것이 정답이겠구나...하는 생각이 들었다.
추가적으로, 프론트엔드 개발의 희망편과 절망편을 (또 한번) 느꼈달까.
dynamic island라니! 시뮬레이터로 밖에 경험해보지 못한 기능인데!
각종 라이브러리나 프레임워크 생태의 변화 뿐만 아니라,
하드웨어의 변화 까지 대응해야한다는 점이 한숨을 푹 내쉬게 하면서도...
10년 뒤? 20년 뒤? 50년 뒤에는
어떤 환경에서, 어떤 모습으로 프론트엔드 개발을 하고 있을지 설렘 반 기대 반!
'source-code > React Native' 카테고리의 다른 글
Cannot find module 'expo/bin/cli.js' 에러 해결 (0) | 2024.01.22 |
---|---|
react-native-webview ↔ web 데이터 송수신 (0) | 2023.08.16 |
React Native TextInput (0) | 2023.08.16 |
RN _ App Development (1) | 2021.08.02 |
RN _ Core Components (3) | 2021.07.30 |