SwiftUI 快速上手:从 UIKit 到声明式 UI

2026.07.25· 约 1 分钟 ·tech·
#iOS
#SwiftUI
#移动开发

UIKit 的痛,SwiftUI 的答案

UIKit 是 iOS 开发的基石,但它的设计哲学诞生于 2008 年——比第一代 iPhone 只晚一年。经过十多年的积累,它有三大顽疾:

  1. 命令式思维 — 你得手动管理 View 的创建、更新、销毁,稍不留神就内存泄漏
  2. Auto Layout 灾难 — 约束系统功能强大但极其啰嗦,debug 起来想砸键盘
  3. 状态与 UI 不同步 — UI 更新散落在 delegate / callback / notification 各处,很容易出现「数据是新的,界面上是旧的」

SwiftUI(2019 年发布)用声明式解决了这些问题:

// SwiftUI:描述你想要什么
struct ProfileView: View {
    @State var name = "Naruto"

    var body: some View {
        VStack {
            Text("こんにちは、\(name)")
            TextField("输入名字", text: $name)
                .textFieldStyle(.roundedBorder)
        }
        .padding()
    }
}

不用写 frame、不用 Auto Layout、不用 viewDidLoad——你描述 UI 的结构,框架负责渲染。

View 协议:一切 UI 的基石

在 SwiftUI 中,每个 UI 都是一个遵守 View 协议的 struct:

protocol View {
    associatedtype Body: View
    @ViewBuilder var body: Self.Body { get }
}

一个 View 只做一件事:定义它的 body 属性。body 不是返回 UIView,而是返回另一个 View——形成了一个 View 树。

struct NinjaCard: View {
    let name: String
    let rank: String

    var body: some View {
        HStack {
            Image(systemName: "person.circle.fill")
                .font(.largeTitle)
                .foregroundColor(.orange)

            VStack(alignment: .leading) {
                Text(name)
                    .font(.headline)
                Text(rank)
                    .font(.subheadline)
                    .foregroundColor(.secondary)
            }
        }
        .padding()
        .background(Color(.systemGray6))
        .cornerRadius(12)
    }
}

💡 View 是 struct 而不是 class——这意味着它是值类型,在内存中被迅速创建和销毁。SwiftUI 的 diff 机制只更新变化的部分,所以不用担心性能。

状态属性包装器:四大天王

这是 SwiftUI 最核心的概念。你怎么声明一个值,决定了它如何驱动 UI 更新。

@State:本地状态

struct CounterView: View {
    @State var count = 0  // 这个 View 拥有这个状态

    var body: some View {
        VStack {
            Text("计数: \(count)")
            Button("+1") { count += 1 }
        }
    }
}

@State 用于当前 View 私有的简单状态。当值改变时,body 自动重新计算。建议用 private 修饰。

@Binding:共享状态

struct ToggleSwitch: View {
    @Binding var isOn: Bool  // 不拥有,只是借用

    var body: some View {
        Toggle("开关", isOn: $isOn)  // $ 取 binding 引用
    }
}

// 使用时:
struct ParentView: View {
    @State var isEnabled = false

    var body: some View {
        ToggleSwitch(isOn: $isEnabled)
    }
}

@Binding 是两个 View 之间双向传递状态的管道。parent 用 $ 取 child 的 @Binding。

@StateObject 和 @ObservedObject:引用类型状态

// ViewModel 必须遵守 ObservableObject
class TaskViewModel: ObservableObject {
    @Published var tasks: [Task] = []   // @Published 触发 UI 更新
    @Published var isLoading = false

    func fetchTasks() async {
        isLoading = true
        // 模拟网络请求
        try? await Task.sleep(for: .seconds(1))
        tasks = [
            Task(title: "修炼螺旋丸", isDone: false),
            Task(title: "执行S级任务", isDone: true)
        ]
        isLoading = false
    }
}

struct TaskListView: View {
    @StateObject var viewModel = TaskViewModel()  // View 拥有 ViewModel

    var body: some View {
        List(viewModel.tasks) { task in
            TaskRow(task: task)
        }
        .refreshable { await viewModel.fetchTasks() }
        .task { await viewModel.fetchTasks() }
    }
}

区分 @StateObject 与 @ObservedObject:

@StateObject@ObservedObject
谁创建当前 View 创建并拥有从外部传入
生命周期跟随 View,配置变更不丢失不由当前 View 管理
典型场景ViewModel 初始化子 View 接收父 View 的 ViewModel

布局三件套:VStack / HStack / ZStack

// VStack = 垂直
VStack(alignment: .leading, spacing: 12) {
    Text("标题")
    Text("副标题")
}

// HStack = 水平
HStack(alignment: .center, spacing: 8) {
    Image(systemName: "star.fill")
    Text("4.9 分")
}

// ZStack = 层叠(类似 z-index)
ZStack(alignment: .bottomTrailing) {
    Image("background")  // 底层
    Text("火の意志")     // 顶层
}

配合 Spacer() 可以实现各种弹性布局:

HStack {
    Text("左边")
    Spacer()          // 把 "右边" 推到最右
    Text("右边")
}
struct ContentView: View {
    var body: some View {
        NavigationStack {
            List(1...10, id: \.self) { i in
                NavigationLink {
                    DetailView(number: i)  // 目标页面
                } label: {
                    Text("任务 #\(i)")
                }
            }
            .navigationTitle("任务列表")
        }
    }
}

NavigationStack 替代了老旧的 NavigationView,支持更灵活的路径管理。

MVVM 在 SwiftUI 中的实战

SwiftUI 天然适合 MVVM:

┌─────────────┐     ┌─────────────────────┐     ┌──────────┐
│   View      │ ──→ │   ViewModel         │ ──→ │  Model   │
│  (struct)   │ ←── │  (class,            │ ←── │  (struct) │
│  @State,    │     │   @Published)       │     │          │
│  @Bindable  │     │                     │     │          │
└─────────────┘     └─────────────────────┘     └──────────┘

完整示例:

// Model
struct Mission: Identifiable {
    let id = UUID()
    let title: String
    let rank: String
    var isCompleted: Bool
}

// ViewModel
class MissionViewModel: ObservableObject {
    @Published var missions: [Mission] = []
    @Published var filterRank: String = "全部"

    var filteredMissions: [Mission] {
        guard filterRank != "全部" else { return missions }
        return missions.filter { $0.rank == filterRank }
    }

    func toggleComplete(_ mission: Mission) {
        if let idx = missions.firstIndex(where: { $0.id == mission.id }) {
            missions[idx].isCompleted.toggle()
        }
    }
}

// View
struct MissionListView: View {
    @StateObject var viewModel = MissionViewModel()

    var body: some View {
        NavigationStack {
            List(viewModel.filteredMissions) { mission in
                HStack {
                    Text(mission.title)
                    Spacer()
                    if mission.isCompleted {
                        Image(systemName: "checkmark.circle.fill")
                            .foregroundColor(.green)
                    }
                }
                .onTapGesture { viewModel.toggleComplete(mission) }
            }
            .navigationTitle("忍务")
        }
    }
}

SwiftUI vs Jetpack Compose vs Vue

SwiftUIComposeVue 3
创建 UIstruct MyView: View@Composable fun<template> + <script setup>
私有状态@Stateremember { mutableStateOf() }ref()
共享状态@Binding传 lambda + stateprops + emit
外部对象@StateObject / @ObservedObjectcollectAsState() (Flow)reactive()
列表List / ForEachLazyColumnv-for
导航NavigationStackNavHostvue-router

🎯 三者的核心思想完全相同——声明式 UI = f(state)。学好一个框架,理解另外两个只需要掌握语法糖的对应关系。

总结

SwiftUI 不是 UIKit 的包装,它是从头重新设计的 UI 框架。拥抱它需要的心态调整:

  • 放弃「手动控制 View 生命周期」→ 交给框架的 diff 机制
  • 放弃「一套 UI 代码对应一套交互逻辑」→ 状态驱动 UI
  • 放弃「UIView 层级树」→ View 协议的组合

起初可能会因为不熟悉而觉得「写个简单的页面怎么这么弯弯绕」,但一旦你的代码量上来——复杂列表、多状态切换、跨页面通信——你会发现 SwiftUI 让你少写了太多 boilerplate。

🔗相关忍术

00:00:00
系统已稳定运行:
Nuxt 3Vue 3Tailwind 3
粤ICP备2026111544号
🍥木葉忍村影分身之术!在代码、设计与写作间穿梭的技术忍者。这里记录着技...

© 2025-2026 木葉忍村 · Powered by Nuxt 3 & Tailwind CSS