2026년에 SwiftUI 프로젝트를 구조화하는 방법

발행: (2026년 2월 2일 오전 10:00 GMT+9)
5 min read
원문: Dev.to

Source: Dev.to

문제

Xcode는 ContentView.swift만 제공하고 그게 전부입니다. 앱이 커지면서 다음과 같은 상황이 발생합니다:

  • 한 폴더에 50개의 파일
  • ViewModel이 View와 섞여 있음
  • 명확한 관심사의 분리 없음
  • 무언가를 찾아야 할 때마다 고통

해결책: 기능 기반 아키텍처

MyApp/
├── App/
│   ├── MyAppApp.swift
│   ├── AppDelegate.swift (if needed)
│   └── AppConfiguration.swift
├── Features/
│   ├── Auth/
│   │   ├── Views/
│   │   │   ├── LoginView.swift
│   │   │   └── RegisterView.swift
│   │   ├── ViewModels/
│   │   │   └── AuthViewModel.swift
│   │   └── Models/
│   │       └── User.swift
│   ├── Home/
│   │   ├── Views/
│   │   ├── ViewModels/
│   │   └── Models/
│   └── Profile/
│       ├── Views/
│       ├── ViewModels/
│       └── Models/
├── Core/
│   ├── Network/
│   │   ├── NetworkManager.swift
│   │   ├── APIEndpoint.swift
│   │   └── APIError.swift
│   ├── Storage/
│   │   ├── StorageManager.swift
│   │   └── KeychainManager.swift
│   └── Extensions/
│       ├── View+Extensions.swift
│       └── String+Extensions.swift
├── UI/
│   ├── Components/
│   │   ├── PrimaryButton.swift
│   │   ├── LoadingView.swift
│   │   └── ErrorView.swift
│   └── Theme/
│       ├── Colors.swift
│       ├── Fonts.swift
│       └── Spacing.swift
└── Resources/
    ├── Assets.xcassets
    └── Localizable.strings

왜 이렇게 작동하는가

1. 기능은 자체 포함형

각 기능은 필요한 모든 것을 가지고 있습니다:

  • Views (UI)
  • ViewModels (로직)
  • Models (데이터)

새로운 기능을 추가하나요? 새 폴더를 만들면 됩니다. 기능을 제거하나요? 폴더를 삭제하면 됩니다.

2. Core는 공유 로직

기능들 사이에서 사용되는 것들:

  • 네트워킹
  • 스토리지
  • 확장(Extensions)

한 곳에서 관리하고, 어디서든 사용합니다.

3. UI는 재사용 가능

모든 기능이 사용하는 컴포넌트와 테마:

// UI/Theme/Colors.swift
import SwiftUI

enum AppColors {
    static let primary = Color("Primary")
    static let secondary = Color("Secondary")
    static let background = Color("Background")
    static let text = Color("Text")
}
// UI/Components/PrimaryButton.swift
import SwiftUI

struct PrimaryButton: View {
    let title: String
    let action: () -> Void

    var body: some View {
        Button(action: action) {
            Text(title)
                .font(.headline)
                .foregroundColor(.white)
                .frame(maxWidth: .infinity)
                .padding()
                .background(AppColors.primary)
                .cornerRadius(12)
        }
    }
}

ViewModel 패턴

모든 기능의 ViewModel은 동일한 패턴을 따릅니다:

@MainActor
final class HomeViewModel: ObservableObject {
    // MARK: - Published State
    @Published private(set) var items: [Item] = []
    @Published private(set) var isLoading = false
    @Published private(set) var error: Error?

    // MARK: - Dependencies
    private let networkManager: NetworkManager

    init(networkManager: NetworkManager = .shared) {
        self.networkManager = networkManager
    }

    // MARK: - Public Methods
    func loadItems() async {
        isLoading = true
        error = nil

        do {
            items = try await networkManager.fetch(ItemsEndpoint())
        } catch {
            self.error = error
        }

        isLoading = false
    }
}

핵심 포인트

  • @MainActor는 UI 업데이트가 메인 스레드에서 이루어지도록 보장합니다.
  • private(set)은 외부에서의 변경을 방지합니다.
  • 의존성 주입을 통해 테스트가 가능해집니다.

탐색

간단한 앱 – 네이티브 SwiftUI 탐색

NavigationStack {
    HomeView()
        .navigationDestination(for: Item.self) { item in
            DetailView(item: item)
        }
}

복잡한 앱 – 코디네이터 패턴

@MainActor
final class AppCoordinator: ObservableObject {
    @Published var path = NavigationPath()

    enum Destination: Hashable {
        case detail(Item)
        case settings
        case profile
    }

    func navigate(to destination: Destination) {
        path.append(destination)
    }

    func pop() {
        path.removeLast()
    }

    func popToRoot() {
        path.removeLast(path.count)
    }
}

빠른 설정 스크립트

구조를 자동으로 생성합니다:

#!/bin/bash
mkdir -p App
mkdir -p Features/{Auth,Home,Profile}/{Views,ViewModels,Models}
mkdir -p Core/{Network,Storage,Extensions}
mkdir -p UI/{Components,Theme}
mkdir -p Resources

내 전체 템플릿

이 모든 것(실제 코드 포함)을 시작 템플릿으로 패키징했습니다. 이 템플릿에는 다음이 포함됩니다:

  • 5개의 준비된 화면
  • 20개 이상의 UI 구성 요소
  • NetworkManager와 async/await 지원
  • 다크 모드 지원
  • MVVM 아키텍처

설정을 건너뛰고 싶다면 여기에서 확인하세요.

TL;DR

  • Feature‑based structure – 기능별로 그룹화하고 파일 유형별이 아니라.
  • Core – 공유 유틸리티.
  • UI – 재사용 가능한 컴포넌트와 테마.
  • MVVM@MainActor가 적용된 ViewModel.
  • Be consistent – 모든 곳에 동일한 패턴을 적용하세요.
Back to Blog

관련 글

더 보기 »

[SUI] 검색 바

NavigationStack의 검색 바 NavigationStack은 `searchable` 수식어를 사용하여 검색 바를 포함할 수 있습니다. 그 서명은 다음과 같습니다: ```swift searchable(...) ```

SwiftUI Dark Mode: 완전 구현 가이드

현재 색상 스킴 감지 SwiftUI는 `@Environment.colorScheme`를 제공하여 앱이 라이트 모드인지 다크 모드인지 감지합니다. ```swift struct ContentView: View { // ... } ```