SwiftUI iOS 16+ 开发:声明式界面与数据流

FreeGuideOnline 最新 2026-06-12

环境准备与 SwiftUI 基础概念

SwiftUI 是 Apple 推出的声明式 UI 框架,iOS 16 带来了导航体系、数据流 API 与组件能力的全面进化。本指南将带你从零搭建现代 iOS 应用,深入理解声明式界面与数据流的核心范式。你需要使用 Xcode 14.0 或更高版本,创建项目时选择 iOS App 模板,Interface 选择 “SwiftUI”,Lifecycle 选择 “SwiftUI App”,编程语言使用 Swift。

理解声明式编程

在 SwiftUI 中,你只需要描述界面应该是什么样子,而不用书写每个更新步骤。当数据发生变化时,框架会自动高效地重新计算受影响的视图。

struct GreetingView: View {
    var isLoggedIn: Bool
    
    var body: some View {
        if isLoggedIn {
            Text("欢迎回来")
        } else {
            Text("请登录")
        }
    }
}

视图是状态的函数——输入相同的数据,始终渲染出相同的界面。这种可预测性让代码更易维护与测试。

视图组合与布局系统

一切皆为 View 协议。通过组合原子视图(TextImageButton 等)并使用容器(VStackHStackZStack)构建复杂界面。

struct ProfileCard: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Image("avatar")
                .resizable()
                .frame(width: 60, height: 60)
                .clipShape(Circle())
            Text("张三")
                .font(.headline)
            Text("iOS 开发者")
                .font(.subheadline)
                .foregroundColor(.secondary)
        }
        .padding()
        .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12))
    }
}

修改器链(modifier)会返回新视图,注意顺序可能影响最终效果。所有布局遵循尺寸协商规则:父视图提供可用空间,子视图上报自身尺寸。

SwiftUI 数据流:从状态到架构

现代 iOS 开发的核心挑战是让界面与数据保持同步。SwiftUI 提供了分层清晰的数据管理工具,iOS 16 对其进行了关键增强。

@State 与视图私有状态

@State 用于管理视图内部的值,当其发生改变时,视图会重新渲染。它应被声明为 private

struct CounterView: View {
    @State private var count = 0
    
    var body: some View {
        VStack {
            Text("计数: \(count)")
            Button("增加") { count += 1 }
        }
    }
}

@Binding 实现父子视图数据同步

当子视图需要读写父视图持有的状态时,使用 @Binding。它不会持有数据,而是一个指向数据源的引用。

struct ToggleChild: View {
    @Binding var isOn: Bool
    
    var body: some View {
        Toggle("开关", isOn: $isOn)
    }
}

struct ParentView: View {
    @State private var enabled = false
    var body: some View {
        ToggleChild(isOn: $enabled)
    }
}

$ 语法将 @State 投影为一个 Binding<Value>

@StateObject 与 @ObservedObject

当状态逻辑需要封装在独立类中时,使用遵循 ObservableObject 协议的类。属性前标记 @Published 将自动通知视图刷新。

  • @StateObject:视图自己创建并持有该对象,生命周期与视图一致。在 iOS 16+ 中强制使用,避免在视图中重新创建导致状态丢失。
  • @ObservedObject:数据由外部传入,视图不负责对象的生命周期。
class UserModel: ObservableObject {
    @Published var name = "访客"
}

struct ContentView: View {
    @StateObject private var user = UserModel()
    
    var body: some View {
        VStack {
            Text("用户名: \(user.name)")
            TextField("输入新名字", text: $user.name)
                .textFieldStyle(.roundedBorder)
        }
        .padding()
    }
}

iOS 16 最佳实践:始终用 @StateObject 创建模型对象;跨视图传递时改用 @ObservedObject。需要全局共享的场景使用 @EnvironmentObject

@EnvironmentObject 全局依赖注入

适用于多个层级都需要访问的共享数据(如用户登录状态、主题设置)。在根视图通过 .environmentObject() 注入,任何子视图都能通过 @EnvironmentObject 获取。

final class AppSettings: ObservableObject {
    @Published var accentColor: Color = .blue
}

@main
struct MyApp: App {
    @StateObject private var settings = AppSettings()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(settings)
        }
    }
}

struct SomeChildView: View {
    @EnvironmentObject var settings: AppSettings
    
    var body: some View {
        Circle()
            .fill(settings.accentColor)
            .frame(width: 50, height: 50)
    }
}

iOS 17 前瞻:@Observable 宏

从 iOS 17 开始,SwiftUI 引入 @Observable 宏,可与 SwiftData 深度集成。它基于观察追踪而不是 @Published 属性包装器,性能更好且语法更简洁。但本指南专注于 iOS 16+ 稳定的 API,如需兼容旧系统,仍建议使用 ObservableObject

iOS 16 现代导航:NavigationStack 与路径管理

NavigationView 已被弃用,NavigationStack 配合 .navigationDestination 带来了类型安全的声明式路由。

基本栈导航

struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                NavigationLink("跳转到详情") {
                    Text("详情界面")
                }
            }
            .navigationTitle("主页")
        }
    }
}

以数据驱动导航

更强大的模式是使用导航路径。定义一个枚举表示所有可能的页面,然后交给 NavigationPath 或自定义集合管理。

enum Route: Hashable {
    case detail(id: Int)
    case profile(String)
}

struct ModrenNavigaton: View {
    @State private var path = NavigationPath()
    
    var body: some View {
        NavigationStack(path: $path) {
            List(1...5, id: \.self) { item in
                NavigationLink(value: Route.detail(id: item)) {
                    Text("项目 \(item)")
                }
            }
            .navigationDestination(for: Route.self) { route in
                switch route {
                case .detail(let id):
                    DetailView(id: id, path: $path)
                case .profile(let name):
                    ProfileView(name: name)
                }
            }
            .navigationTitle("数据驱动导航")
        }
    }
}

这种模式将导航状态集中管理,便于实现深层链接、状态恢复及编程式回退。

实战:构建一个待办清单应用

我们将结合上述概念,创建一个完整的待办清单,展示声明式界面与数据流的协作。

定义数据模型

import Foundation

struct TodoItem: Identifiable, Codable {
    let id: UUID
    var title: String
    var isCompleted: Bool
    
    init(title: String, isCompleted: Bool = false) {
        self.id = UUID()
        self.title = title
        self.isCompleted = isCompleted
    }
}

@MainActor
final class TodoViewModel: ObservableObject {
    @Published var items: [TodoItem] = []
    
    func addItem(title: String) {
        let newItem = TodoItem(title: title)
        items.append(newItem)
    }
    
    func toggleItem(id: UUID) {
        guard let index = items.firstIndex(where: { $0.id == id }) else { return }
        items[index].isCompleted.toggle()
    }
    
    func deleteItems(at offsets: IndexSet) {
        items.remove(atOffsets: offsets)
    }
}

ViewModel 标记为 @MainActor 确保所有 UI 更新在主线程执行,这在并发环境下尤为重要。

编写视图

struct TodoListView: View {
    @StateObject private var viewModel = TodoViewModel()
    @State private var newItemTitle = ""
    
    var body: some View {
        NavigationStack {
            VStack {
                HStack {
                    TextField("新待办事项", text: $newItemTitle)
                        .textFieldStyle(.roundedBorder)
                    Button("添加") {
                        viewModel.addItem(title: newItemTitle)
                        newItemTitle = ""
                    }
                    .disabled(newItemTitle.isEmpty)
                }
                .padding()
                
                List {
                    ForEach(viewModel.items) { item in
                        HStack {
                            Button {
                                viewModel.toggleItem(id: item.id)
                            } label: {
                                Image(systemName: item.isCompleted ? "checkmark.circle.fill" : "circle")
                                    .foregroundColor(item.isCompleted ? .green : .gray)
                            }
                            .buttonStyle(.plain)
                            
                            Text(item.title)
                                .strikethrough(item.isCompleted)
                        }
                    }
                    .onDelete(perform: viewModel.deleteItems)
                }
            }
            .navigationTitle("待办清单")
        }
    }
}

数据持久化(可选增强)

可以将 TodoViewModelCodableUserDefaults 结合实现本地存储。此处省略详细实现,但核心依然是:持久化操作不影响 @Published 数据流,界面自动响应变化。

总结与最佳实践

  • 状态归属原则:状态能够被声明在最局部的地方,就绝不向上提升。避免不必要的全局共享。
  • 使用 iOS 16+ 导航栈:彻底抛弃 NavigationView,用 NavigationStack 和路径实现灵活的路由。
  • 数据流分层:视图内用 @State,父子传递用 @Binding,独立模型用 @StateObject,全局依赖用 @EnvironmentObject
  • 性能要点:注意视图的身份(Identity),避免在循环中使用不稳定的 ID;合理利用 EquatableView.equatable() 减少不必要的重绘;善用 LazyVStackLazyHGrid 优化长列表。
  • 拥抱并发:配合 async/awaitMainActor 编写安全的异步数据获取。

课程结束时,你已经掌握了 SwiftUI iOS 16+ 开发中最重要的声明式思维与数据流机制。建议立即动手,将图片浏览器、天气应用或记账本等小项目用这些概念重新实现,巩固理解。