# 🎴 퀴즈 카드 디자인 가이드 ## 📋 개요 퀴즈 화면을 카드 형식으로 재디자인하여 전체 화면에 떠있는 듯한 모던한 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. **피드백**: 그림자, 애니메이션, 색상으로 상태 표현 이를 통해 사용자는 퀴즈에 더 집중할 수 있고, 앱이 더 프리미엄하게 느껴집니다. 🎉