아이콘과 폰트는 앱의 첫인상을 결정합니다. 이미지 아이콘 대신 벡터 아이콘을, 시스템 폰트 대신 커스텀 폰트를 사용하면 훨씬 세련된 앱을 만들 수 있습니다.


벡터 아이콘 — react-native-vector-icons

설치

BASH
# Expo
npx expo install @expo/vector-icons

# CLI
npm install react-native-vector-icons
npm install -D @types/react-native-vector-icons
cd ios && pod install

기본 사용법

TSX
// Expo에서는 @expo/vector-icons 사용
import { Ionicons, MaterialIcons, FontAwesome } from '@expo/vector-icons';

// CLI에서는 react-native-vector-icons 사용
// import Ionicons from 'react-native-vector-icons/Ionicons';

function IconExample() {
  return (
    <View style={styles.row}>
      <Ionicons name="home" size={24} color="#007AFF" />
      <MaterialIcons name="search" size={24} color="#333" />
      <FontAwesome name="heart" size={24} color="red" />
      <Ionicons name="settings-outline" size={24} color="#666" />
    </View>
  );
}

아이콘 세트 종류

세트특징아이콘 수
IoniconsiOS/Android 스타일 통일1300+
MaterialIconsGoogle Material Design2000+
FontAwesome웹에서 가장 유명1600+
Feather깔끔한 라인 아이콘280+
MaterialCommunityIconsMaterial 확장판6000+

아이콘 버튼 만들기

TSX
import { Pressable, StyleSheet } from 'react-native';
import { Ionicons } from '@expo/vector-icons';

function IconButton({
  name,
  size = 24,
  color = '#007AFF',
  onPress,
}: {
  name: keyof typeof Ionicons.glyphMap;
  size?: number;
  color?: string;
  onPress: () => void;
}) {
  return (
    <Pressable
      onPress={onPress}
      hitSlop={12}
      style={({ pressed }) => [
        styles.iconButton,
        pressed && styles.pressed,
      ]}
    >
      <Ionicons name={name} size={size} color={color} />
    </Pressable>
  );
}

const styles = StyleSheet.create({
  iconButton: {
    padding: 8,
    borderRadius: 20,
  },
  pressed: {
    backgroundColor: 'rgba(0, 0, 0, 0.05)',
  },
});

탭 네비게이터에서 아이콘 사용

TSX
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';

const Tab = createBottomTabNavigator();

function TabNavigator() {
  return (
    <Tab.Navigator
      screenOptions={({ route }) => ({
        tabBarIcon: ({ focused, color, size }) => {
          // 포커스 상태에 따라 아이콘 변경
          const iconName = {
            Home: focused ? 'home' : 'home-outline',
            Search: focused ? 'search' : 'search-outline',
            Profile: focused ? 'person' : 'person-outline',
          }[route.name] as keyof typeof Ionicons.glyphMap;

          return <Ionicons name={iconName} size={size} color={color} />;
        },
        tabBarActiveTintColor: '#007AFF',
        tabBarInactiveTintColor: '#999',
      })}
    >
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="Search" component={SearchScreen} />
      <Tab.Screen name="Profile" component={ProfileScreen} />
    </Tab.Navigator>
  );
}

커스텀 폰트

Expo에서 폰트 로드

TSX
import { useFonts } from 'expo-font';
import * as SplashScreen from 'expo-splash-screen';
import { useEffect } from 'react';

// 앱 시작 시 스플래시 유지
SplashScreen.preventAutoHideAsync();

export default function App() {
  const [fontsLoaded] = useFonts({
    'Pretendard-Regular': require('./assets/fonts/Pretendard-Regular.otf'),
    'Pretendard-Bold': require('./assets/fonts/Pretendard-Bold.otf'),
    'Pretendard-SemiBold': require('./assets/fonts/Pretendard-SemiBold.otf'),
  });

  useEffect(() => {
    if (fontsLoaded) {
      // 폰트 로드 완료 후 스플래시 숨기기
      SplashScreen.hideAsync();
    }
  }, [fontsLoaded]);

  if (!fontsLoaded) {
    return null;
  }

  return <MainApp />;
}

CLI에서 폰트 로드

PLAINTEXT
assets/fonts/
├── Pretendard-Regular.otf
├── Pretendard-Bold.otf
└── Pretendard-SemiBold.otf
JS
// react-native.config.js
module.exports = {
  project: {
    ios: {},
    android: {},
  },
  assets: ['./assets/fonts/'],
};
BASH
# 폰트 링크
npx react-native-asset

폰트 사용

TSX
import { Text, StyleSheet } from 'react-native';

function Typography() {
  return (
    <>
      <Text style={styles.heading}>제목 텍스트</Text>
      <Text style={styles.body}>본문 텍스트입니다.</Text>
      <Text style={styles.caption}>캡션 텍스트</Text>
    </>
  );
}

const styles = StyleSheet.create({
  heading: {
    fontFamily: 'Pretendard-Bold',
    fontSize: 24,
    color: '#1a1a1a',
  },
  body: {
    fontFamily: 'Pretendard-Regular',
    fontSize: 16,
    lineHeight: 24,
    color: '#333',
  },
  caption: {
    fontFamily: 'Pretendard-Regular',
    fontSize: 12,
    color: '#999',
  },
});

타이포그래피 시스템 만들기

TSX
// theme/typography.ts
import { TextStyle } from 'react-native';

export const typography: Record<string, TextStyle> = {
  h1: {
    fontFamily: 'Pretendard-Bold',
    fontSize: 28,
    lineHeight: 36,
  },
  h2: {
    fontFamily: 'Pretendard-Bold',
    fontSize: 24,
    lineHeight: 32,
  },
  h3: {
    fontFamily: 'Pretendard-SemiBold',
    fontSize: 20,
    lineHeight: 28,
  },
  body1: {
    fontFamily: 'Pretendard-Regular',
    fontSize: 16,
    lineHeight: 24,
  },
  body2: {
    fontFamily: 'Pretendard-Regular',
    fontSize: 14,
    lineHeight: 20,
  },
  caption: {
    fontFamily: 'Pretendard-Regular',
    fontSize: 12,
    lineHeight: 16,
  },
};

// 사용
function Card() {
  return (
    <View>
      <Text style={[typography.h3, { color: '#1a1a1a' }]}>카드 제목</Text>
      <Text style={[typography.body2, { color: '#666' }]}>카드 설명</Text>
    </View>
  );
}

Google Fonts (Expo)

BASH
# Expo Google Fonts 패키지 설치
npx expo install @expo-google-fonts/noto-sans-kr expo-font
TSX
import {
  useFonts,
  NotoSansKR_400Regular,
  NotoSansKR_700Bold,
} from '@expo-google-fonts/noto-sans-kr';

export default function App() {
  const [fontsLoaded] = useFonts({
    NotoSansKR_400Regular,
    NotoSansKR_700Bold,
  });

  if (!fontsLoaded) return null;

  return (
    <Text style={{ fontFamily: 'NotoSansKR_400Regular' }}>
      한글 폰트 적용
    </Text>
  );
}

SVG 아이콘 사용

BASH
# SVG 지원 패키지
npm install react-native-svg
# Expo: npx expo install react-native-svg

# SVG 파일을 컴포넌트로 변환
npm install react-native-svg-transformer
TSX
// 커스텀 SVG 아이콘 컴포넌트
import Svg, { Path } from 'react-native-svg';

function CustomIcon({
  size = 24,
  color = '#000',
}: {
  size?: number;
  color?: string;
}) {
  return (
    <Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
      <Path
        d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z"
        fill={color}
      />
    </Svg>
  );
}

플랫폼별 폰트 주의사항

TSX
import { Platform, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  text: {
    // iOS와 Android에서 같은 폰트를 다른 이름으로 참조할 수 있음
    fontFamily: Platform.select({
      ios: 'Pretendard-Regular',
      android: 'Pretendard-Regular', // 보통 같지만 확인 필요
    }),
    // Android에서 fontWeight와 fontFamily 충돌 주의
    // fontWeight를 함께 쓰면 Android에서 무시될 수 있음
    // 대신 각 웨이트별 폰트 파일을 별도로 지정
  },
});

Android에서 fontFamilyfontWeight를 함께 사용하면 의도한 대로 동작하지 않는 경우가 있습니다. 각 웨이트(Regular, Bold, SemiBold)별로 별도의 폰트 파일을 등록하고, fontFamily로 직접 지정하는 것이 안전합니다.


정리

  • 벡터 아이콘은 이미지보다 가볍고 크기 조절이 자유 롭습니다
  • Expo에서는 @expo/vector-icons, CLI에서는 react-native-vector-icons를 사용하세요
  • 커스텀 폰트는 앱 시작 시 로딩 완료 후 화면을 보여줘야 합니다
  • 타이포그래피 시스템을 만들어두면 앱 전체의 텍스트 스타일을 일관되게 유지할 수 있습니다
  • Android에서는 fontWeight와 fontFamily 충돌에 주의하세요
댓글 로딩 중...