insong
← 위키

iOS SwiftUI 디자인 토큰 & 컴포넌트 패턴

SwiftUI 앱에서 일관된 UI를 만들기 위한 공유 디자인 토큰 및 컴포넌트 코드 패턴

GridLayout 상수 (iPhone / iPad)

속성iPhoneiPad
timeHeaderWidth48pt60pt
dayHeaderHeight36pt48pt
baseMinuteHeight1.21.6
minColumnWidth44pt80pt
fontSize scale1.0x1.2x

간격 스케일

4, 8, 12, 16, 20, 24, 28pt 단위만 사용. 중간값 혼용 금지.

사용처
섹션 간 간격24pt
카드 내부 패딩horizontal 16pt, vertical 12pt
버튼 vertical padding16pt
아이콘-텍스트 간격12pt

애니메이션 패턴

사용처
기본 전환.easeInOut(duration: 0.2)
화면 전환.easeInOut(duration: 0.3)
Floating button / handles.spring(response: 0.4, dampingFraction: 0.7)
Sub-toolbar.easeInOut(duration: 0.2) + .opacity.combined(with: .scale(scale: 0.95))
Edit panel slide.easeInOut(duration: 0.25) + .move(edge: .trailing).combined(with: .opacity)

컴포넌트 패턴

Toolbar Icon Button

Image(systemName: icon)
    .font(.system(size: 18))
    .frame(width: 36, height: 36)
    .background(RoundedRectangle(cornerRadius: 8).fill(
        isActive ? Color.accentColor.opacity(0.15) : .clear
    ))

Action CTA Button (전체 너비)

Text(label)
    .font(.title2.bold())
    .frame(maxWidth: .infinity)
    .padding(.vertical, 16)
    .background(color)
    .foregroundStyle(.white)
    .clipShape(RoundedRectangle(cornerRadius: 14))
    .buttonStyle(.plain)   // List 안에서 이중 탭 방지
  • cornerRadius: 14pt 고정
  • 긍정 CTA: .blue, 위험/중지: .red

Sub-toolbar Chip Button

HStack(spacing: 5) {
    Circle().fill(color).frame(width: 12, height: 12)
    Text(title)
}
.padding(.horizontal, 10).padding(.vertical, 6)
.background(RoundedRectangle(cornerRadius: 8).fill(
    isSelected ? Color.accentColor.opacity(0.2) : Color(.systemGray5).opacity(0.6)
))
.overlay(RoundedRectangle(cornerRadius: 8).stroke(
    isSelected ? Color.accentColor : .clear, lineWidth: 1.5
))

Sheet 표준 설정

.presentationDetents([.medium])          // 단순 확인용
.presentationDetents([.medium, .large])  // 내용이 길어질 수 있을 때
.presentationBackground(.thinMaterial)
.presentationDragIndicator(.visible)
// 반드시 NavigationStack 래핑

Card List Row

.listStyle(.plain)
.listRowSeparator(.hidden)
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets(top: 6, leading: 16, bottom: 6, trailing: 16))
// 카드 배경: Color(.systemGray6), cornerRadius: 14pt

FAB (Floating Action Button)

ZStack {
    listContent
    if editMode == .inactive {
        VStack {
            Spacer()
            Button(action: action) { /* Capsule 스타일 */ }
                .padding(.bottom, 32)
        }
        .transition(.opacity.combined(with: .scale(scale: 0.9, anchor: .bottom)))
    }
}
// List 마지막 행: Color.clear.frame(height: 80) — FAB 가림 방지

Circular Progress Ring

  • ring lineWidth: 18pt, 화면 너비의 72% 크기
  • track: accentColor.opacity(0.15), progress: accentColor, lineCap .round
  • 12시 방향에서 반시계로 줄어듦
  • 숫자: 64pt bold monospaced, .contentTransition(.numericText(countsDown: true))
  • 5% 미만이면 강제 .red 전환

스플래시 스크린 패턴 (2-layer)

Layer 1: LaunchScreen.storyboard (시스템 launch, 정적)
Layer 2: SwiftUI SplashView (앱 로드 후, 애니메이션)

SplashView 타이밍:

  • 타이틀 + 하단 fade-in: 0.5s easeOut
  • 요소 버블: staggered 0.12s, spring animation
  • 전체 duration: ~1.46s
ZStack {
    Color(UIColor.systemBackground)
    ContentView().opacity(showSplash ? 0 : 1)
    if showSplash { SplashView() }
}
.task {
    try? await Task.sleep(for: .seconds(1.4))
    showSplash = false
}

컬러 팔레트 인덱스 관례

테마 컬러 0–29, 3개 tone 그룹 (dark 0–9, medium 10–19, light 20–29):

  • 3-dot preview: indices [4, 14, 24] — 각 tone 그룹 중간값
  • 5-dot preview: indices [2, 7, 14, 19, 24]

디자인 원칙

  • Consistency: 같은 역할 UI = 같은 토큰/패턴
  • Platform-native: SwiftUI 네이티브 컴포넌트 우선, 커스텀은 꼭 필요할 때만
  • Adaptive: horizontalSizeClass 또는 UIDevice로 iPhone/iPad 분기
  • Accessibility: Dynamic Type 지원, 최소 탭 영역 44pt, VoiceOver 레이블

관련