노치, 다이나믹 아일랜드, 홈 인디케이터 — 이런 영역을 고려하지 않으면 콘텐츠가 잘리거나 가려집니다.

최근 스마트폰은 노치, 펀치홀, 둥근 모서리 등으로 화면 형태가 다양합니다. SafeAreaView를 사용하면 이런 영역을 피해서 콘텐츠를 배치할 수 있습니다.


기본 SafeAreaView의 한계

TSX
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 (권장)

BASH
# 설치
npm install react-native-safe-area-context
# Expo
npx expo install react-native-safe-area-context

Provider 설정

TSX
import { SafeAreaProvider } from 'react-native-safe-area-context';

export default function App() {
  return (
    <SafeAreaProvider>
      <NavigationContainer>
        {/* 앱 전체를 Provider로 감싸기 */}
        <MainNavigator />
      </NavigationContainer>
    </SafeAreaProvider>
  );
}

SafeAreaView 사용

TSX
import { SafeAreaView } from 'react-native-safe-area-context';

function HomeScreen() {
  return (
    // iOS와 Android 모두에서 동작
    <SafeAreaView style={{ flex: 1 }} edges={['top', 'bottom']}>
      <Text>안전 영역 안에 있습니다</Text>
    </SafeAreaView>
  );
}

edges 옵션

TSX
// 모든 방향에 안전 영역 패딩 (기본값)
<SafeAreaView edges={['top', 'right', 'bottom', 'left']}>

// 상단만 (네비게이션 헤더가 있는 화면)
<SafeAreaView edges={['top']}>

// 하단만 (탭 네비게이터가 있는 화면에서 하단 버튼)
<SafeAreaView edges={['bottom']}>

// 상하단 (헤더도 탭도 없는 전체 화면)
<SafeAreaView edges={['top', 'bottom']}>

React Navigation을 사용하면 헤더와 탭바가 이미 안전 영역을 처리합니다. 이 경우 화면 컴포넌트에서 추가로 SafeAreaView를 쓰면 이중 패딩 이 생길 수 있으니 주의하세요.


useSafeAreaInsets Hook

안전 영역 값을 직접 가져와서 세밀하게 제어할 수 있습니다.

TSX
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,
  },
});

실전 패턴: 전체 화면 레이아웃

TSX
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 처리

TSX
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>
    </>
  );
}

디바이스별 안전 영역 값

PLAINTEXT
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 옵션으로 필요한 방향만 안전 영역을 적용하세요
  • useSafeAreaInsets Hook으로 ** 커스텀 레이아웃에서 insets 값을 직접** 사용할 수 있습니다
  • React Navigation 사용 시 ** 이중 패딩에 주의 **하세요
  • 항상 다양한 디바이스에서 테스트하세요 — 노치, 홈 인디케이터 유무에 따라 레이아웃이 달라집니다
댓글 로딩 중...