2.1 声明式 UI vs 命令式 UI
传统的 UIKit 是命令式编程:你告诉系统"怎么做"——先创建视图,设置属性,添加到父视图,注册回调……每一步都需要明确指令。
SwiftUI 是声明式编程:你只需描述"界面应该是什么样子",框架负责计算差异并更新 UI。当数据变化时,SwiftUI 自动重新渲染相关视图。
UIKit(命令式)
let label = UILabel()
label.text = "Hello"
label.font = .systemFont(ofSize: 24)
label.textColor = .blue
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
// ...添加约束...
SwiftUI(声明式)
Text("Hello")
.font(.system(size: 24))
.foregroundColor(.blue)
// 就这些!
2.2 View 协议
在 SwiftUI 中,一切皆 View。View 是一个协议,要求实现一个计算属性 body,返回视图的内容描述。
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, SwiftUI!")
}
}
some View 是"不透明类型"(Opaque Type),表示"某个具体的 View 类型,但调用者不需要知道是哪种"。这让 SwiftUI 能在编译时确定视图类型,提升性能。
2.3 基础视图组件
Text — 文本
Text("普通文本")
Text("**粗体** 和 *斜体*") // Markdown 支持
Text(Date(), style: .time) // 动态时间显示
Text("很长的文本...")
.lineLimit(2) // 最多2行
.truncationMode(.middle) // 中间截断
Image — 图片
Image("photo") // Assets 中的图片
.resizable()
.scaledToFit()
.frame(width: 200)
Image(systemName: "heart.fill") // SF Symbols 图标
.font(.system(size: 30))
.foregroundColor(.red)
Button — 按钮
Button("点我") {
print("被点击了")
}
Button {
// 操作
} label: {
HStack {
Image(systemName: "plus")
Text("添加")
}
}
TextField — 输入框
@State private var inputText = ""
TextField("请输入...", text: $inputText)
.textFieldStyle(.roundedBorder)
.padding()
2.4 修饰符(Modifiers)
修饰符是 SwiftUI 的核心机制之一。每个修饰符接收一个视图,返回一个新的视图(装饰器模式)。多个修饰符可以链式调用,顺序非常重要。
Text("标题")
.font(.largeTitle) // 字体大小
.fontWeight(.bold) // 字重
.foregroundColor(.white) // 文字颜色
.padding() // 内边距(所有方向)
.padding(.horizontal, 20) // 水平方向额外内边距
.background(.blue) // 背景色
.cornerRadius(12) // 圆角
.shadow(radius: 4) // 阴影
.padding().background(.blue) 与 .background(.blue).padding() 结果不同:前者蓝色背景包含 padding,后者 padding 在蓝色背景外侧。
常用修饰符一览
| 类别 | 修饰符 | 说明 |
|---|---|---|
| 布局 | .frame(width:height:) | 设置尺寸 |
.padding() | 内边距 | |
.offset(x:y:) | 偏移位置 | |
.position(x:y:) | 绝对定位 | |
| 外观 | .background() | 背景 |
.foregroundColor() | 前景色(文字/图标) | |
.opacity() | 透明度 0.0~1.0 | |
| 文字 | .font() | 字体 |
.multilineTextAlignment() | 多行对齐方式 | |
| 交互 | .onTapGesture {} | 点击手势 |
.disabled() | 禁用交互 |
2.5 组合视图
SwiftUI 鼓励将大视图拆分成小的、可复用的子视图。提取子视图不仅提升可读性,还能加速编译。
// ❌ 臃肿的单一视图
struct ContentView: View {
var body: some View {
VStack {
HStack {
Image(systemName: "person")
VStack(alignment: .leading) {
Text("小明").font(.headline)
Text("iOS 开发者").font(.caption)
}
}
// ...更多内容
}
}
}
// ✅ 提取为子视图
struct UserCard: View {
let name: String
let role: String
var body: some View {
HStack {
Image(systemName: "person.circle.fill")
.font(.system(size: 40))
.foregroundColor(.blue)
VStack(alignment: .leading) {
Text(name).font(.headline)
Text(role).font(.caption).foregroundColor(.secondary)
}
}
.padding()
.background(.regularMaterial)
.cornerRadius(12)
}
}
struct ContentView: View {
var body: some View {
VStack {
UserCard(name: "小明", role: "iOS 开发者")
UserCard(name: "小红", role: "设计师")
}
}
}
2.6 ViewBuilder 与条件视图
SwiftUI 使用 @ViewBuilder 属性包装器支持在 body 中使用条件判断和循环,这些都会被编译成视图层级。
struct StatusView: View {
let isOnline: Bool
var body: some View {
HStack {
Circle()
.fill(isOnline ? Color.green : .gray)
.frame(width: 10, height: 10)
Text(isOnline ? "在线" : "离线")
.foregroundColor(isOnline ? .green : .secondary)
// if-else 也可以
if isOnline {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
}
}
}
}
2.7 预览(Preview)
Xcode 提供实时预览功能,让你无需编译运行就能看到界面效果。Swift 5.9 引入了 #Preview 宏,语法更简洁,支持多个命名预览和不同外观特性。
#Preview {
ContentView()
}
// 预览多种状态
#Preview("在线状态") {
StatusView(isOnline: true)
}
#Preview("离线状态") {
StatusView(isOnline: false)
}
// 指定暗色模式预览(iOS 17+)
#Preview("暗色模式", traits: .colorScheme(.dark)) {
ContentView()
}
// 在 NavigationStack 中预览(模拟真实导航环境)
#Preview("导航中") {
NavigationStack {
UserCard(name: "小明", role: "iOS 开发者")
}
}
2.8 关键名词定义
some View 表示"某种具体的、符合 View 协议的类型,调用者不需要知道具体类型"。每个视图的 body 返回 some View,让编译器在编译期确定具体类型,避免类型擦除的运行时开销,同时隐藏内部实现细节。_ConditionalContent),用户无需手动处理分支。Image(systemName:) 使用。SF Symbols 图标随系统字体自动缩放,iOS 17 起支持 Animate Symbols 动效,与 SwiftUI 深度集成,是构建 Apple 原生界面的标准图标方案。.regularMaterial、.thinMaterial、.ultraThinMaterial 等提供不同透明度的磨砂效果,自动适配深色/浅色模式,能够模糊其后方内容,营造系统原生的层次感。2.9 常见误区
SwiftUI 可能多次调用 body 进行渲染,不要在其中执行网络请求、写文件等副作用。这类逻辑应放在 .task(异步)或 .onAppear 中。
// ❌ 错误:body 可能被反复调用
var body: some View {
fetchDataFromNetwork() // 危险!
return Text("...")
}
// ✅ 正确:使用 .task 修饰符,视图出现时执行一次
var body: some View {
Text(data ?? "加载中...")
.task { data = await fetchData() }
}
若数据模型未遵循 Identifiable,ForEach 必须手动指定 id: 参数,否则 SwiftUI 无法正确追踪元素的插入/删除动画。
// ❌ 字符串数组缺少 id → 编译错误
ForEach(["苹果", "香蕉"]) { item in Text(item) }
// ✅ 指定 \.self(字符串值唯一时可用)
ForEach(["苹果", "香蕉"], id: \.self) { item in Text(item) }
// ✅ 最佳方案:遵循 Identifiable
struct Fruit: Identifiable {
let id = UUID(); let name: String
}
2.10 实战:文章卡片列表
综合运用本章知识,构建一个完整的文章列表界面:
import SwiftUI
// 1. 数据模型(遵循 Identifiable 让 ForEach 无需手动 id)
struct Article: Identifiable {
let id = UUID()
let title: String
let summary: String
let category: String
}
// 2. 单张卡片(提取为独立视图,便于复用)
struct ArticleCard: View {
let article: Article
var body: some View {
VStack(alignment: .leading, spacing: 8) {
// 分类标签:背景色 → 圆角 → 裁剪为胶囊
Text(article.category)
.font(.caption.weight(.semibold))
.padding(.horizontal, 8).padding(.vertical, 4)
.background(.blue.opacity(0.15))
.foregroundStyle(.blue)
.clipShape(Capsule())
Text(article.title)
.font(.headline)
.lineLimit(2) // 最多2行,超出省略号结尾
Text(article.summary)
.font(.subheadline)
.foregroundStyle(.secondary) // 系统次要颜色,自适应暗色模式
.lineLimit(3)
}
.padding()
.background(.regularMaterial) // 毛玻璃材质,自适应亮/暗色
.clipShape(RoundedRectangle(cornerRadius: 12))
.shadow(color: .black.opacity(0.08), radius: 8, y: 2)
}
}
// 3. 列表主视图
struct ArticleListView: View {
let articles = [
Article(title: "SwiftUI 声明式革命",
summary: "探索声明式 UI 如何彻底改变 iOS 开发...",
category: "SwiftUI"),
Article(title: "从 UIKit 迁移到 SwiftUI",
summary: "大型项目的渐进式迁移策略与实战经验分享...",
category: "迁移"),
]
var body: some View {
NavigationStack {
ScrollView {
LazyVStack(spacing: 12) { // LazyVStack:按需渲染,性能优于 VStack
ForEach(articles) { article in
ArticleCard(article: article)
.padding(.horizontal)
}
}
.padding(.top)
}
.navigationTitle("技术文章")
}
}
}
#Preview { ArticleListView() }
本章全面介绍了 SwiftUI 基础架构:声明式 UI 范式、View 协议与 some View、Text/Image/Button/TextField 等核心组件、修饰符链(顺序至关重要)、子视图提取与 @ViewBuilder。
三条重要记忆点:① 修饰符包裹视图,顺序影响结果;② 不要在 body 中执行副作用;③ ForEach 需要每个元素有唯一标识。下一章深入布局系统:Stack、Grid 与 ScrollView。