2026 年如何构建 SwiftUI 项目

发布: (2026年2月2日 GMT+8 09:00)
4 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. 功能是自包含的

每个功能都拥有其所需的一切:

  • 视图(UI)
  • 视图模型(逻辑)
  • 模型(数据)

添加新功能?创建一个新文件夹。删除功能?删除相应的文件夹。

2. 核心是共享逻辑

跨功能使用的内容:

  • 网络
  • 存储
  • 扩展

只需在一个地方维护,所有地方都可使用。

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 组件
  • 使用 async/await 的 NetworkManager
  • 暗模式支持
  • MVVM 架构

如果你想跳过设置,请访问 查看。

TL;DR

  • Feature‑based structure – 按功能分组,而不是按文件类型。
  • Core – 共享的工具。
  • UI – 可复用的组件和主题。
  • MVVM – 使用 @MainActor 的 ViewModel。
  • Be consistent – 在所有地方都使用相同的模式。
Back to Blog

相关文章

阅读更多 »

[SUI] 搜索栏

NavigationStack 中的搜索栏 NavigationStack 可以通过 `searchable` 修饰符添加搜索栏。它的签名是:swift searchable t...

SwiftUI 暗模式:完整实现指南

检测当前颜色方案 SwiftUI 提供 `@Environment.colorScheme` 来检测应用是处于浅色模式还是深色模式。 ```swift struct ContentView: View ```