# 🎴 퀴즈 카드 디자인 가이드
## 📋 개요
퀴즈 화면을 카드 형식으로 재디자인하여 전체 화면에 떠있는 듯한 모던한 UI를 구현했습니다.
---
## 🎨 디자인 컨셉
### 핵심 아이디어
- **카드 메타포**: 실제 카드처럼 전체 화면에 떠있는 느낌
- **화이트 스페이스**: 카드 주변에 여백을 두어 시각적 집중
- **그림자 효과**: 깊이감을 주는 부드러운 그림자
- **유리 모피즘**: `.ultraThinMaterial`로 배경 블러 효과
---
## 📐 레이아웃 구조
### 전체 구조
```
GeometryReader
└─ ZStack
├─ BackgroundView (배경)
└─ 카드 컨테이너
├─ 헤더 (카테고리 + 난이도)
├─ 문제 텍스트
├─ 선택지
└─ 결과 영역 (조건부)
```
### 카드 사이즈
```swift
// 패딩
.padding(.horizontal, 20) // 좌우 여백
.padding(.vertical, 40) // 상하 여백
// 모서리
.clipShape(RoundedRectangle(cornerRadius: 24))
// 그림자
.shadow(color: .black.opacity(0.15), radius: 20, x: 0, y: 10)
```
---
## 🎯 카드 종류
### 1️⃣ 퀴즈 카드 (메인)
```swift
ScrollView {
VStack {
// 헤더
HStack {
카테고리 배지
Spacer()
난이도 (별 5개)
}
// 문제
Text(questionText)
.font(.system(size: 24, weight: .bold))
// 선택지
VStack(spacing: 14) {
ForEach(choices) { ... }
}
// 결과 (조건부)
if hasAnswered {
정답/오답 표시
해설
다음 문제 버튼
}
}
}
.background(.ultraThinMaterial)
```
**특징**:
- 📜 스크롤 가능 (긴 콘텐츠 대응)
- 🎨 유리 모피즘 배경
- ⚡ 부드러운 애니메이션
- 📱 반응형 디자인
### 2️⃣ 로딩 카드
```swift
VStack {
ProgressView()
.scaleEffect(1.5)
Text("퀴즈를 불러오는 중...")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.ultraThinMaterial)
```
**특징**:
- ⏳ 중앙 정렬
- 📏 전체 높이 활용
- 🔄 스케일된 ProgressView
### 3️⃣ 오류 카드
```swift
VStack {
Image(systemName: "exclamationmark.triangle")
.font(.system(size: 60))
.foregroundStyle(.orange)
Text("문제를 불러올 수 없습니다")
Text("네트워크 연결을 확인해주세요")
Button("다시 시도") { ... }
}
```
**특징**:
- ⚠️ 명확한 에러 표시
- 🔄 재시도 버튼
- 📝 도움말 텍스트
---
## 🎨 디자인 요소
### 헤더 (카테고리 배지)
```swift
Text(category.uppercased())
.font(.caption)
.fontWeight(.bold)
.foregroundStyle(.white)
.padding(.horizontal, 14)
.padding(.vertical, 7)
.background(
LinearGradient(
colors: [.blue, .purple],
startPoint: .leading,
endPoint: .trailing
)
)
.clipShape(Capsule())
.shadow(color: .blue.opacity(0.3), radius: 5, x: 0, y: 2)
```
**개선 사항**:
- 🌈 그라데이션 배경 (파란→보라)
- ✨ 그림자 효과
- 💫 캡슐 모양
### 난이도 표시
```swift
HStack(spacing: 3) {
ForEach(0..<5) { index in
Image(systemName: index < difficulty ? "star.fill" : "star")
.font(.caption2)
.foregroundStyle(.yellow)
}
}
```
**특징**:
- ⭐ 별 5개 (채워진/빈)
- 📏 작은 크기 (caption2)
- 💛 노란색
### 문제 텍스트
```swift
Text(questionText)
.font(.system(size: 24, weight: .bold))
.lineSpacing(10)
.multilineTextAlignment(.leading)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 24)
.fixedSize(horizontal: false, vertical: true)
```
**특징**:
- 📏 24pt 볼드 폰트
- 📖 10pt 줄 간격
- 📐 좌측 정렬
- 🔧 자동 높이 조정
### 결과 표시 (정답/오답)
```swift
HStack(spacing: 12) {
Image(systemName: isCorrect ? "checkmark.circle.fill" : "xmark.circle.fill")
.font(.title)
Text(isCorrect ? "정답입니다!" : "오답입니다")
.font(.title3)
.fontWeight(.bold)
}
.foregroundStyle(isCorrect ? .green : .red)
.padding(.vertical, 18)
.frame(maxWidth: .infinity)
.background(isCorrect ? .green.opacity(0.12) : .red.opacity(0.12))
.clipShape(RoundedRectangle(cornerRadius: 16))
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(isCorrect ? .green.opacity(0.3) : .red.opacity(0.3), lineWidth: 2)
)
```
**개선 사항**:
- 🎯 더 큰 아이콘 (.title)
- 🎨 테두리 추가 (.stroke)
- 📦 더 많은 패딩
### 해설 카드
```swift
VStack(alignment: .leading, spacing: 12) {
HStack(spacing: 8) {
Image(systemName: "lightbulb.fill")
.foregroundStyle(.yellow)
.font(.title3)
Text("해설")
.font(.title3)
.fontWeight(.bold)
}
Text(explanation)
.font(.body)
.lineSpacing(6)
.foregroundStyle(.secondary)
}
.padding(20)
.background(.ultraThinMaterial)
.clipShape(RoundedRectangle(cornerRadius: 16))
```
**특징**:
- 💡 전구 아이콘
- 📏 더 큰 헤딩
- 📖 줄 간격 추가
### 다음 문제 버튼
```swift
Button(action: loadNextQuestion) {
HStack(spacing: 8) {
Text("다음 문제")
.fontWeight(.bold)
Image(systemName: "arrow.right")
.font(.body.weight(.bold))
}
.frame(maxWidth: .infinity)
.frame(height: 56)
.background(
LinearGradient(
colors: [.blue, .purple],
startPoint: .leading,
endPoint: .trailing
)
)
.foregroundStyle(.white)
.clipShape(RoundedRectangle(cornerRadius: 16))
.shadow(color: .blue.opacity(0.3), radius: 10, x: 0, y: 5)
}
```
**개선 사항**:
- 🌈 그라데이션 배경
- ➡️ 화살표 아이콘 추가
- 📏 더 큰 높이 (56pt)
- ✨ 그림자 효과
---
## 📏 스페이싱 가이드
### 카드 외부
```swift
// 화면 가장자리에서
.padding(.horizontal, 20) // 좌우
.padding(.vertical, 40) // 상하
```
### 카드 내부
```swift
// 헤더
.padding(.horizontal, 24)
.padding(.top, 24)
.padding(.bottom, 16)
// 문제 텍스트
.padding(.horizontal, 24)
.padding(.bottom, 28)
// 선택지
.padding(.horizontal, 24)
// 결과 영역
.padding(.horizontal, 24)
.padding(.top, 24)
// 하단 여백
.padding(.bottom, 24)
```
### 요소 간격
```swift
VStack(spacing: 0) { // 카드 전체
VStack(spacing: 14) { // 선택지
VStack(spacing: 16) { // 결과 영역
VStack(spacing: 12) { // 해설 내부
```
---
## 🎭 애니메이션
### 카드 등장
```swift
// 자동으로 부드러운 트랜지션
.transition(.opacity)
.animation(.easeInOut, value: quizService.isLoading)
```
### 선택지 애니메이션
기존 `ChoiceButton`의 애니메이션 유지:
- 파티클 효과
- 링 확장
- 버튼 스케일
### 결과 애니메이션
```swift
.transition(.scale.combined(with: .opacity))
```
---
## 🎨 색상 팔레트
### 카드 배경
```swift
.background(.ultraThinMaterial) // 유리 모피즘
```
### 그림자
```swift
.shadow(color: .black.opacity(0.15), radius: 20, x: 0, y: 10)
```
### 액센트 색상
```swift
// 카테고리 배지
LinearGradient(colors: [.blue, .purple])
// 정답/오답
.green / .red
// 설문
.blue
// 해설
.yellow (전구)
```
### 투명도
```swift
// 배경
.green.opacity(0.12) // 정답
.red.opacity(0.12) // 오답
.blue.opacity(0.12) // 설문
// 테두리
.green.opacity(0.3)
.red.opacity(0.3)
.blue.opacity(0.3)
// 그림자
.black.opacity(0.15)
.blue.opacity(0.3)
```
---
## 📱 반응형 디자인
### GeometryReader 활용
```swift
GeometryReader { geometry in
// 전체 화면 크기에 맞춤
.frame(width: geometry.size.width, height: geometry.size.height)
}
```
### 스크롤
```swift
ScrollView {
VStack { ... }
}
// 긴 콘텐츠도 스크롤 가능
```
### 텍스트 자동 조정
```swift
.fixedSize(horizontal: false, vertical: true)
// 가로는 고정, 세로는 자동
```
---
## 🚀 사용자 경험
### 장점
1. **집중도 향상**: 카드에 시선 집중
2. **명확한 구조**: 정보 계층이 분명
3. **터치 영역**: 카드 전체가 인터랙션 가능
4. **시각적 피드백**: 그림자와 애니메이션
5. **공간 활용**: 화면을 효율적으로 사용
### 개선된 UX
- ✅ 카드 형태로 퀴즈가 더 눈에 띔
- ✅ 배경과 분리되어 가독성 향상
- ✅ 그림자로 깊이감 표현
- ✅ 스크롤 가능으로 긴 콘텐츠 대응
- ✅ 로딩/오류 상태도 일관된 카드 형태
---
## 🔮 향후 개선 방향
### 1. 스와이프 제스처
```swift
.gesture(
DragGesture()
.onEnded { value in
if value.translation.width < -100 {
// 왼쪽 스와이프 → 다음 문제
loadNextQuestion()
}
}
)
```
### 2. 카드 플립 애니메이션
```swift
.rotation3DEffect(
.degrees(isFlipped ? 180 : 0),
axis: (x: 0, y: 1, z: 0)
)
```
### 3. 카드 스택
여러 카드를 겹쳐서 표시:
```swift
ZStack {
ForEach(questions) { question in
QuizCard(question: question)
.offset(y: CGFloat(index * 10))
.scaleEffect(1.0 - CGFloat(index) * 0.05)
}
}
```
### 4. 진행률 표시
카드 상단에 진행 바:
```swift
ProgressView(value: currentIndex, total: totalQuestions)
.tint(.blue)
```
### 5. 햅틱 피드백
```swift
import CoreHaptics
// 정답/오답 시
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.success) // 정답
generator.notificationOccurred(.error) // 오답
```
---
## 🎯 베스트 프랙티스
### ✅ DO
- 카드 주변에 충분한 여백 유지
- 그림자로 깊이감 표현
- 일관된 모서리 반경 사용 (16-24pt)
- 유리 모피즘으로 현대적인 느낌
### ❌ DON'T
- 카드를 화면 끝까지 늘리지 말기
- 그림자를 너무 진하게 하지 말기
- 너무 많은 요소를 한 카드에 넣지 말기
- 카드 내부 패딩 일관성 유지
---
## 📊 성능 고려사항
### 최적화된 부분
```swift
// GeometryReader는 필요한 곳에만
GeometryReader { geometry in
// 한 번만 계산
.frame(width: geometry.size.width, height: geometry.size.height)
}
// 조건부 렌더링
if quizService.hasAnswered {
// 필요할 때만 렌더
}
// LazyVStack 대신 VStack (선택지 개수 적음)
VStack { ... }
```
---
## 🎬 Before & After
### Before (리스트 스타일)
```
전체 화면 스크롤뷰
├─ 문제 (상단)
├─ 선택지
├─ 해설
└─ 버튼
```
- ❌ 배경과 구분 없음
- ❌ 평면적인 느낌
- ❌ 집중도 낮음
### After (카드 스타일)
```
전체 화면 컨테이너
└─ 카드 (떠있는 느낌)
├─ 헤더
├─ 문제
├─ 선택지
└─ 결과
```
- ✅ 카드가 떠있는 느낌
- ✅ 입체적인 디자인
- ✅ 높은 집중도
- ✅ 프리미엄 UX
---
## 📚 관련 파일
```
moni/
└─ Views/
├─ Quiz/
│ └─ QuizView.swift # 메인 카드 구현
└─ Components/
├─ BackgroundView.swift
└─ SelectionAnimation.swift
```
---
## 🎨 디자인 시스템 통합
### 모서리 반경
```swift
24pt: 카드 전체
16pt: 내부 요소 (해설, 결과 등)
12pt: 선택지
```
### 패딩
```swift
24pt: 카드 내부 주요 영역
20pt: 카드 외부 (화면 가장자리)
14-18pt: 텍스트 요소
```
### 그림자
```swift
// 카드
.shadow(color: .black.opacity(0.15), radius: 20, x: 0, y: 10)
// 버튼
.shadow(color: .blue.opacity(0.3), radius: 10, x: 0, y: 5)
// 배지
.shadow(color: .blue.opacity(0.3), radius: 5, x: 0, y: 2)
```
---
**작성일**: 2025-11-06
**버전**: 1.0
**상태**: ✅ 완료
---
## 💡 핵심 정리
퀴즈 카드는 다음 3가지 원칙으로 디자인되었습니다:
1. **시각적 계층**: 카드 → 섹션 → 요소
2. **일관성**: 모든 상태(로딩, 퀴즈, 오류)가 카드 형태
3. **피드백**: 그림자, 애니메이션, 색상으로 상태 표현
이를 통해 사용자는 퀴즈에 더 집중할 수 있고, 앱이 더 프리미엄하게 느껴집니다. 🎉