SafeAreaView — 노치와 상태바 대응하기
노치, 다이나믹 아일랜드, 홈 인디케이터 — 이런 영역을 고려하지 않으면 콘텐츠가 잘리거나 가려집니다.
최근 스마트폰은 노치, 펀치홀, 둥근 모서리 등으로 화면 형태가 다양합니다. SafeAreaView를 사용하면 이런 영역을 피해서 콘텐츠를 배치할 수 있습니다.
기본 SafeAreaView의 한계
import { SafeAreaView, Text, StyleSheet } from 'react-native';
// React Native 내장 SafeAreaView — iOS만 지원!
function BasicSafeArea() {
return (
<SafeAreaView style={styles.container}>
<Text>iOS에서만 안전 영역 적용됨</Text>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: { flex: 1 },
});
내장 SafeAreaView의 문제점:
- iOS에서만 동작 (Android에서는 무시됨)
- 패딩 값을 직접 제어할 수 없음
- 중첩 시 예상치 못한 동작
react-native-safe-area-context (권장)
# 설치
npm install react-native-safe-area-context
# Expo
npx expo install react-native-safe-area-context
Provider 설정
import { SafeAreaProvider } from 'react-native-safe-area-context';
export default function App() {
return (
<SafeAreaProvider>
<NavigationContainer>
{/* 앱 전체를 Provider로 감싸기 */}
<MainNavigator />
</NavigationContainer>
</SafeAreaProvider>
);
}
SafeAreaView 사용
import { SafeAreaView } from 'react-native-safe-area-context';
function HomeScreen() {
return (
// iOS와 Android 모두에서 동작
<SafeAreaView style={{ flex: 1 }} edges={['top', 'bottom']}>
<Text>안전 영역 안에 있습니다</Text>
</SafeAreaView>
);
}
edges 옵션
// 모든 방향에 안전 영역 패딩 (기본값)
<SafeAreaView edges={['top', 'right', 'bottom', 'left']}>
// 상단만 (네비게이션 헤더가 있는 화면)
<SafeAreaView edges={['top']}>
// 하단만 (탭 네비게이터가 있는 화면에서 하단 버튼)
<SafeAreaView edges={['bottom']}>
// 상하단 (헤더도 탭도 없는 전체 화면)
<SafeAreaView edges={['top', 'bottom']}>
React Navigation을 사용하면 헤더와 탭바가 이미 안전 영역을 처리합니다. 이 경우 화면 컴포넌트에서 추가로 SafeAreaView를 쓰면 이중 패딩 이 생길 수 있으니 주의하세요.
useSafeAreaInsets Hook
안전 영역 값을 직접 가져와서 세밀하게 제어할 수 있습니다.
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { View, Text, StyleSheet } from 'react-native';
function CustomHeader() {
const insets = useSafeAreaInsets();
return (
<View style={[
styles.header,
{ paddingTop: insets.top + 10 }, // 안전 영역 + 추가 패딩
]}>
<Text style={styles.headerTitle}>커스텀 헤더</Text>
</View>
);
}
function BottomButton() {
const insets = useSafeAreaInsets();
return (
<View style={[
styles.bottomButton,
{ paddingBottom: Math.max(insets.bottom, 16) },
]}>
<Pressable style={styles.button}>
<Text style={styles.buttonText}>다음</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
header: {
backgroundColor: '#007AFF',
paddingHorizontal: 16,
paddingBottom: 12,
},
headerTitle: {
color: 'white',
fontSize: 18,
fontWeight: 'bold',
},
bottomButton: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
paddingHorizontal: 16,
paddingTop: 12,
backgroundColor: 'white',
borderTopWidth: 1,
borderTopColor: '#eee',
},
button: {
backgroundColor: '#007AFF',
padding: 16,
borderRadius: 8,
alignItems: 'center',
},
buttonText: {
color: 'white',
fontWeight: 'bold',
fontSize: 16,
},
});
실전 패턴: 전체 화면 레이아웃
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { View, FlatList, StyleSheet } from 'react-native';
function FullScreenWithSafeArea() {
const insets = useSafeAreaInsets();
return (
<View style={styles.container}>
{/* 상태바 영역 배경색 */}
<View style={[styles.statusBar, { height: insets.top }]} />
{/* 헤더 */}
<View style={styles.header}>
<Text style={styles.title}>피드</Text>
</View>
{/* 콘텐츠 */}
<FlatList
data={data}
renderItem={renderItem}
contentContainerStyle={{
paddingBottom: insets.bottom + 80, // 하단 버튼 공간 확보
}}
/>
{/* 하단 고정 버튼 */}
<View style={[
styles.bottomBar,
{ paddingBottom: insets.bottom || 16 },
]}>
<Pressable style={styles.fab}>
<Text style={styles.fabText}>+</Text>
</Pressable>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: 'white' },
statusBar: { backgroundColor: '#007AFF' },
header: { padding: 16, backgroundColor: '#007AFF' },
title: { color: 'white', fontSize: 20, fontWeight: 'bold' },
bottomBar: {
position: 'absolute',
bottom: 0,
right: 16,
},
fab: {
width: 56,
height: 56,
borderRadius: 28,
backgroundColor: '#007AFF',
justifyContent: 'center',
alignItems: 'center',
},
fabText: { color: 'white', fontSize: 24, fontWeight: 'bold' },
});
StatusBar 처리
import { StatusBar, Platform } from 'react-native';
function App() {
return (
<>
<StatusBar
barStyle="dark-content" // 'light-content' | 'dark-content'
backgroundColor="transparent" // Android만
translucent={true} // Android: 상태바 아래로 콘텐츠 확장
/>
<MainContent />
</>
);
}
// 화면별 StatusBar 변경
function DarkScreen() {
return (
<>
<StatusBar barStyle="light-content" />
<View style={{ flex: 1, backgroundColor: '#1a1a1a' }}>
<Text style={{ color: 'white' }}>다크 화면</Text>
</View>
</>
);
}
디바이스별 안전 영역 값
iPhone SE (홈 버튼 있음):
top: 20, bottom: 0
iPhone 14 (노치):
top: 47, bottom: 34
iPhone 14 Pro (다이나믹 아일랜드):
top: 59, bottom: 34
Android (일반):
top: 24~48 (상태바), bottom: 0~48 (네비게이션 바)
정리
- 내장
SafeAreaView는 iOS만 지원하므로react-native-safe-area-context를 사용 하세요 edges옵션으로 필요한 방향만 안전 영역을 적용하세요useSafeAreaInsetsHook으로 ** 커스텀 레이아웃에서 insets 값을 직접** 사용할 수 있습니다- React Navigation 사용 시 ** 이중 패딩에 주의 **하세요
- 항상 다양한 디바이스에서 테스트하세요 — 노치, 홈 인디케이터 유무에 따라 레이아웃이 달라집니다
댓글 로딩 중...