모바일 디자인 — 완벽한 스킬
개요
iOS 및 Android 디자인을 다룹니다: 플랫폼별 패턴, 내비게이션, 제스처, 타이포그래피, 접근성, 현대적인 트렌드. building-native-ui, expo-ui-swiftui, expo-ui-jetpack-compose, engineering-mobile-app-builder, react-native-best-practices, expo-tailwind-setup, native-data-fetching 스킬을 통합합니다.
1. 플랫폼 가이드라인
1.1 iOS (Human Interface Guidelines)
- 내비게이션: push/pop (UINavigationController), 탭 바, 모달 시트
- 타이포그래피: SF Pro (San Francisco) — New York (serif, 읽기용)
- 그리드: 4pt 그리드, 여백: 16pt (표준), 20pt (큰 화면)
- 색상: 시스템 색상 (파랑, 초록, 빨강, 주황, 보라, 분홍, 청록, 남색, 회색)
- 터치 대상: 최소 44×44pt
- 뒤로 가기 제스처: 왼쪽 가장자리에서 스와이프
- 안전 영역: 노치, 홈 인디케이터 (
safe-area-inset 사용)
- 다크 모드:
UIColor { traitCollection } 시스템 적응
1.2 Android (Material Design 3)
- 내비게이션: 하단 내비게이션, 내비게이션 드로어, 상단 앱 바, 뒤로 가기 버튼
- 타이포그래피: Roboto (시스템), Google Sans (브랜딩)
- 그리드: 4dp 그리드, 여백: 16dp
- 색상: 동적 색상 (Material You), 톤 팔레트
- 터치 대상: 최소 48×48dp
- 뒤로 가기 버튼: 시스템 뒤로 가기 (Android 14에서 예측 뒤로 가기)
- 고도(elevation): 그림자는 깊이와 상호작용 우선순위를 나타냄
- 리플 효과: 모든 상호작용 요소에 터치 피드백
1.3 크로스 플랫폼 (React Native / Expo)
- 빠른 프로토타이핑을 위해 Expo Go를 먼저 사용하세요
Dimensions.get() 대신 useWindowDimensions() 사용
boxShadow를 사용한 크로스 플랫폼 그림자 (레거시 shadow/elevation 대체)
expo-image로 향상된 이미지 로딩 및 SF 심볼 사용
- 플랫폼별 코드:
.ios.tsx / .android.tsx
2. 모바일 내비게이션
2.1 내비게이션 패턴
| 패턴 | 사용 시기 | 플랫폼 |
|---|
| 탭 바 | 3-5개의 최상위 목적지 | iOS (하단), Android (하단) |
| 하단 내비게이션 | 3-5개의 최상위 목적지 | Material Design |
| 내비게이션 드로어 | 많은 목적지, 드문 접근 | Android |
| 스택 내비게이션 | 계층적 드릴다운 | 둘 다 |
| 모달 시트 | 집중된 작업, 빠른 동작 | iOS (폼 시트), Android (하단 시트) |
| 전체 화면 모달 | 다단계 흐름 | 둘 다 |
| 스와이프 동작 | 목록 항목 동작 (삭제, 보관) | 둘 다 |
2.2 탭 바 규칙
- 최대 5개 탭 (iOS), 5개 최대 (Material)
- 활성 상태: 채워진 아이콘 + 레이블 색상 변경
- 배지: 알림을 위한 빨간 점과 개수
- 안전 영역: iOS에서 홈 인디케이터 위
2.3 스택 내비게이션
- 푸시: 오른쪽에서 슬라이드 (iOS), 페이드+슬라이드 (Android)
- 팝: 왼쪽 가장자리에서 스와이프 (iOS), 뒤로 가기 버튼 (Android)
- 전환: 300ms (iOS), 200-300ms (Android)
2.4 제스처
- 스와이프: 목록 동작, 화면 간 내비게이션
- 당겨서 새로고침: 목록의 표준 패턴
- 길게 누르기: 컨텍스트 메뉴 (300-500ms 지연)
- 핀치 투 줌: 이미지, 지도
- 두 번 탭: 줌, 좋아요 (소셜 앱)
3. 모바일 타이포그래피
3.1 타입 스케일
| 스타일 | 크기 | 굵기 | 줄 높이 |
|---|
| Large Title | 34pt | 700 | 41pt |
| Title 1 | 28pt | 700 | 34pt |
| Title 2 | 22pt | 700 | 28pt |
| Title 3 | 20pt | 600 | 25pt |
| Headline | 17pt | 600 | 22pt |
| Body | 17pt | 400 | 22pt |
| Callout | 16pt | 400 | 21pt |
| Subheadline | 15pt | 400 | 20pt |
| Footnote | 13pt | 400 | 18pt |
| Caption 1 | 12pt | 400 | 16pt |
| Caption 2 | 11pt | 400 | 13pt |
3.2 규칙
- 최소 텍스트 크기: 12sp (Android), 11pt (iOS)
- 본문 텍스트: 16-17pt (편안한 읽기용)
- 줄 높이: 글꼴 크기의 1.3-1.5배
- 중요 텍스트(숫자, 이름, 날짜)는 절대 자르지 마세요
- 줄 길이: 최대 40-70자
- 동적 타입: 시스템 텍스트 크기 지원 (iOS:
UIFontMetrics, Android: sp)
4. 모바일 컴포넌트
4.1 일반 컴포넌트
| 컴포넌트 | iOS | Android (Material) |
|---|
| 버튼 | UIButton (채워짐, 착색, 회색) | Elevated, filled, tonal, outlined, text |
| 목록 | UITableView (그룹화, 삽입) | RecyclerView (목록, 카드) |
| 입력 | UITextField | TextField (채워짐, 윤곽선) |
| 토글 | UISwitch | Switch |
| 슬라이더 | UISlider | Slider |
| 피커 | Wheel picker / 드롭다운 | 드롭다운 메뉴 |
| 탭 바 | UITabBar | BottomNavigationView |
| 검색 | UISearchBar | SearchBar |
| 토스트 | N/A (사용자 정의 사용) | Snackbar |
4.2 목록 패턴
- 표준: 아이콘 + 제목 + 부제목 + 쉐브론
- 동작: 왼쪽으로 스와이프 → 삭제/보관/공유
- 그룹화: 섹션 헤더와 그룹화된 항목
- 카드: 이미지 + 제목 + 설명 + 동작
- 무한 스크롤: 스크롤 시 추가 로드 (로딩 표시기 포함)
4.3 폼 패턴
- 단일 열 항상 (모바일)
- 레이블: 입력 위에 (플로팅 전용 아님)
- 키보드 유형: 입력과 일치 (숫자, 이메일, 전화번호)
- 키보드 회피: 키보드가 나타날 때 폼 스크롤
- 유효성 검사: 필드 아래 인라인, 색상 변경
- 제출: 고정 하단 버튼 또는 폼 동작
5. 모바일 레이아웃
5.1 안전 영역
import { useSafeAreaInsets } from 'react-native-safe-area-context';
const insets = useSafeAreaInsets();
<View style={{ paddingTop: insets.top, paddingBottom: insets.bottom }} />
5.2 화면 밀도
- iPhone SE: 375×667pt (가장 작음)
- iPhone 14: 390×844pt
- iPhone 14 Pro Max: 430×932pt
- Android: 360×640dp (일반), 매우 다양함
5.3 터치 대상
- 최소: 44×44pt (iOS), 48×48dp (Android)
- 권장: 엄지 친화적 영역을 위해 48×48pt
- 간격: 인접한 터치 대상 사이 최소 8pt
5.4 엄지 영역
- 최적 영역: 화면 하단 절반 (자연스러운 엄지 도달 범위)
- 기본 동작: 하단 1/3 (특히 큰 화면에서)
- 파괴적 동작: 기본 영역에서 멀리
6. 모바일 디자인 시스템
6.1 색상 시스템
--primary: #007AFF;
--background: #FFFFFF;
--surface: #F2F2F7;
--text-primary: #000000;
--text-secondary: #8E8E93;
--separator: #C6C6C8;
--success: #34C759;
--error: #FF3B30;
--warning: #FF9500;
--primary: #0A84FF;
--background: #000000;
--surface: #1C1C1E;
--text-primary: #FFFFFF;
--text-secondary: #8E8E93;
6.2 다크 모드
- 시스템 전체 전환 (OS 설정 따름)
- OLED용 순수 검정(#000) (iOS) 또는 어두운 회색(#121212) (Android)
- 다크 모드에서 대비를 약간 낮춤
- 모든 화면에 대해 두 모드를 모두 테스트
6.3 아이콘
- iOS: SF Symbols (시스템), 사용자 정의 아이콘 (앱별)
- Android: Material Icons (시스템), 사용자 정의
- 크기: 24×24dp 표준, 18×18dp 작음, 28×28dp 큼
- 스타일: 채워짐 (활성), 윤곽선 (비활성)
7. 모션 및 애니메이션
7.1 원칙
- 지속 시간: 250-350ms (표준), 150-200ms (빠름), 500ms+ (복잡)
- 이징:
cubic-bezier(0.2, 0, 0, 1) (Material), ease-out (iOS)
- 목적: 내비게이션 표시, 동작 확인, 로딩 표시, 즐거움 제공
7.2 일반적인 패턴
| 애니메이션 | 지속 시간 | 사용 |
|---|
| 페이지 푸시/팝 | 300ms | 스택 내비게이션 |
| 모달 표시/해제 | 300ms | 모달 시트 |
| 페이드 인 | 200ms | 콘텐츠 로딩 |
| 슬라이드 업 | 250ms | 하단 시트 |
| 스케일 업 | 200ms | 버튼 누름 피드백 |
| 스켈레톤 | 1.5s 루프 | 콘텐츠 로딩 |
| 당겨서 새로고침 | 200ms | 목록 새로고침 |
7.3 플랫폼별
- iOS: 스프링 물리 (자연스럽고 탄력적)
- Android: 표준 이징 (예측 가능하고 부드러움)
- 햅틱: 가벼운 임팩트 (버튼 누름), 중간 (선택), 성공/실패 패턴
8. 모바일 UX 패턴
8.1 온보딩
- 최대 3-5개 화면: 가치 제안 → 기능 → 권한 → CTA
- 건너뛰기 옵션: 항상 사용 가능
- 진행 표시기: 점 또는 진행 바
- 권한 요청: 요청하기 전에 WHY를 설명
8.2 빈 상태
- 일러스트레이션 또는 아이콘
- 명확한 메시지
- 행동 유도 버튼
- 예: "결과 없음", "장바구니가 비었습니다", "알림 없음"
8.3 로딩 상태
- 스켈레톤 화면 (스피너보다 선호)
- 진행 바 (측정 가능한 작업용)
- 당겨서 새로고침 (목록용)
- 낙관적 UI (동작에 대해 즉시 업데이트, 오류 시 롤백)
8.4 오류 상태
- 인라인: 필드 수준 유효성 검사 오류
- 전체 화면: 재시도 버튼이 있는 심각한 오류
- 토스트/스낵바: 일시적 오류 (3-5초)
- 오프라인: 캐시된 콘텐츠 + "오프라인입니다" 배너
8.5 알림
- 푸시: 설명과 함께 권한 요청
- 인앱: 배지, 배너, 또는 받은 편지함
- 설정: 세분화된 제어 (카테고리별)
9. 접근성
9.1 체크리스트
9.2 화면 읽기
- iOS VoiceOver:
accessibilityLabel, accessibilityHint, accessibilityTraits
- Android TalkBack:
contentDescription, accessibilityRole
- 테스트: 화면 읽기를 활성화하고 전체 앱 탐색
10. 성능
10.1 주요 지표
- TTI (Time to Interactive): < 2s
- 프레임 속도: 60 FPS (프레임당 16.67ms)
- 앱 크기: 번들 최소화
- 메모리: 누수 없음, 적절한 정리
10.2 최적화
- 긴 목록에는
FlatList 사용 (ScrollView 대신)
- 비용이 많이 드는 컴포넌트 메모이제이션 (
React.memo, useMemo, useCallback)
- 화면 및 이미지 지연 로딩
- 리렌더링 감소 (적절한 key props, props에서 익명 함수 사용 피하기)
- 이미지 캐싱 및 압축
- React Native용 Hermes 엔진
11. Expo 네이티브 UI 컴포넌트
11.1 Expo UI — SwiftUI (iOS)
npx expo install @expo/ui
import { Host, SwiftUIButton } from '@expo/ui/swift-ui';
export default function Screen() {
return (
<Host> {/* 모든 SwiftUI 트리는 Host가 필요합니다 */}
<SwiftUIButton title="Tap me" action={onPress} />
</Host>
);
}
- ⚠️ 모든 SwiftUI 트리는 반드시
Host로 감싸야 합니다 — 그렇지 않으면 아무것도 렌더링되지 않습니다
- SwiftUI 트리 내부에 React Native 컴포넌트를 포함하려면
RNHostView를 사용하세요
- 사용 전에 패키지
.d.ts 및 컴포넌트 문서를 읽으세요 — SDK 버전 간에 수식어가 변경됩니다
11.2 Expo UI — Jetpack Compose (Android)
import { Host, Button } from '@expo/ui/jetpack-compose';
import { composeModifiers } from '@expo/ui/jetpack-compose/modifiers';
<Host>
<Button title="Tap me" modifiers={[composeModifiers.padding(16)]} onPress={onPress} />
</Host>
- ⚠️ 모든 Compose 트리를
Host로 감싸세요 (SwiftUI와 동일한 규칙)
- 긴 목록 →
LazyColumn; 아이콘 → 로컬 XML 파일과 함께 Icon 사용
- 수식어를 사용하기 전에 컴포넌트의
.d.ts를 읽으세요 — API는 SDK 버전에 따라 다릅니다
11.3 NativeWind v5 + Tailwind v4 (크로스 플랫폼 스타일링)
npm i nativewind react-native-css tailwindcss @tailwindcss/postcss
import { styled } from 'nativewind';
export const View = styled(NativeView);
babel.config.js 변경 불필요 — react-native-css가 런타임에 변환을 처리합니다
- 출력은
src/tw/에 위치합니다 (래핑된 View, Text, Image, animated)
- 모든 곳에서
className 사용; 일회성은 style로 대체
- 웹 타겟: 동일한
className이 React Native Web에서 작동합니다 — 하나의 코드베이스, 세 개의 플랫폼
11.4 언제 무엇을 사용해야 하나
| 필요 | 사용 |
|---|
| 네이티브 룩 (SwiftUI/Compose) | Host와 함께 @expo/ui |
| 크로스 플랫폼 + 빠른 반복 | NativeWind v5 |
| 사용자 정의 드로잉 / 애니메이션 | Reanimated + Skia |
| 표준 RN 컴포넌트 | 내장 View/Text/Pressable |
12. 검증 체크리스트
관련 스킬
$design-web — 웹 인터페이스 디자인용
$design-desktop — 데스크톱 애플리케이션 디자인용
$design-figma — Figma 디자인 작업용
$mobile-development — Expo/React Native 개발용
$testing-comprehensive — 모바일 및 데스크톱 앱 테스트용