2.1 声明式 vs 命令式 UI 对比
命令式 UI(UIKit)
命令式编程是一种传统的编程范式,开发者需要明确告诉计算机“如何”完成任务。在 UIKit 中,你需要:
- 创建视图对象
- 配置视图属性
- 添加视图到视图层级
- 设置布局约束
- 手动更新视图状态
示例代码(UIKit):
import UIKit
class ProfileViewController: UIViewController {
private let nameLabel = UILabel()
private let avatarImageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
// 1. 创建视图
view.backgroundColor = .white
// 2. 配置 nameLabel
nameLabel.text = "张三"
nameLabel.font = UIFont.systemFont(ofSize: 18, weight: .medium)
nameLabel.textColor = .black
nameLabel.textAlignment = .center
// 3. 配置 avatarImageView
avatarImageView.image = UIImage(named: "avatar")
avatarImageView.contentMode = .scaleAspectFill
avatarImageView.layer.cornerRadius = 40
avatarImageView.clipsToBounds = true
// 4. 添加到视图层级
view.addSubview(avatarImageView)
view.addSubview(nameLabel)
// 5. 设置约束
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
nameLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
avatarImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
avatarImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 100),
avatarImageView.widthAnchor.constraint(equalToConstant: 80),
avatarImageView.heightAnchor.constraint(equalToConstant: 80),
nameLabel.topAnchor.constraint(equalTo: avatarImageView.bottomAnchor, constant: 20),
nameLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
}
// 6. 更新数据时需要手动刷新 UI
func updateProfile(name: String, avatar: String) {
nameLabel.text = name
avatarImageView.image = UIImage(named: avatar)
}
}
声明式 UI(SwiftUI)
声明式编程是一种现代的编程范式,开发者只需要描述“是什么”,而不需要关心“如何”实现。在 SwiftUI 中,你只需要:
- 描述界面的结构
- 绑定状态
- 系统自动处理更新
示例代码(SwiftUI):
import SwiftUI
struct ProfileView: View {
let name: String
let avatar: String
var body: some View {
VStack(spacing: 20) {
// 1. 头像
Image(avatar)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 80, height: 80)
.clipShape(Circle())
// 2. 姓名
Text(name)
.font(.system(size: 18, weight: .medium))
.foregroundStyle(.primary)
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(.systemBackground))
}
}
// 使用示例
ProfileView(name: "张三", avatar: "avatar")
核心差异
| 维度 | 命令式 UI(UIKit) | 声明式 UI(SwiftUI) |
|---|---|---|
| 代码量 | 多(需要手动管理所有细节) | 少(只描述结果) |
| 状态同步 | 手动更新 UI | 自动同步状态 |
| 可读性 | 较低(逻辑分散) | 高(逻辑集中) |
| 维护性 | 较低(容易遗漏更新) | 高(状态驱动自动更新) |
| 错误率 | 较高(手动操作容易出错) | 较低(框架保证一致性) |
| 开发效率 | 低(重复代码多) | 高(简洁明了) |
2.2 View 协议与 body 计算属性
View 协议
在 SwiftUI 中,所有的视图都必须遵循 View 协议:
public protocol View {
associatedtype Body : View
@ViewBuilder var body: Self.Body { get }
}
核心概念:
- associatedtype Body:关联类型,表示视图的内容类型
- body:计算属性,返回视图的内容
- @ViewBuilder:属性包装器,允许使用声明式语法组合多个视图
body 计算属性
body 是 SwiftUI 视图的核心,它是一个计算属性,每次状态变化时都会重新计算。
重要特性:
- 计算属性:不是存储属性,每次访问都会重新计算
- 轻量级:应该保持简洁,避免复杂计算
- 返回类型:必须返回一个遵循
View协议的类型 - 自动合成:@ViewBuilder 允许使用简洁的语法组合多个视图
示例:
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, SwiftUI!")
Button("Tap Me") {}
}
}
}
理解 some View
some View 是一个不透明类型(Opaque Type),它表示:
- 这个函数返回一个遵循
View协议的类型 - 但具体是什么类型,不需要暴露给调用者
- 编译器可以进行更多的优化
2.3 结构体视图与值类型
视图是结构体
在 SwiftUI 中,视图是使用结构体(struct)实现的,这与 UIKit 中的类(class)不同。
结构体的优势:
- 值类型:传递时会复制,避免引用计数问题
- 轻量级:分配在栈内存上,创建和销毁成本低
- 不可变:默认不可变,状态通过 @State 等包装器管理
- 线程安全:值类型天生线程安全
结构体的生命周期
SwiftUI 视图的生命周期与结构体的实例化无关:
- 结构体可能被频繁创建和销毁
- 但底层的真实视图对象由 SwiftUI 管理
- 视图的身份(Identity)由其在视图树中的位置决定
示例:
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") {
count += 1
}
}
}
}
当 count 改变时:
- SwiftUI 检测到状态变化
- 重新创建
CounterView结构体实例 - 调用
body计算属性生成新的视图树 - 与旧视图树对比,只更新变化的部分
2.4 修饰符(Modifier)的使用
什么是修饰符?
修饰符是 SwiftUI 中用于修改视图属性和行为的方法。它们通常以点语法链式调用。
修饰符的工作原理
修饰符不是直接修改原视图,而是返回一个新的视图,这个新视图包含了原视图和应用的修改。
示例:
Text("Hello")
.font(.largeTitle) // 返回一个新的 Text 视图,字体为 largeTitle
.foregroundStyle(.blue) // 返回一个新的视图,文本颜色为蓝色
.padding() // 返回一个新的视图,带有内边距
常用修饰符
布局修饰符
- padding():添加内边距
- frame():设置视图大小和对齐方式
- background():设置背景
- foregroundStyle():设置前景样式(颜色、渐变等)
- clipShape():裁剪视图形状
- overlay():在视图上叠加内容
排版修饰符
- font():设置字体
- bold():加粗文本
- italic():斜体文本
- multilineTextAlignment():多行文本对齐
- lineLimit():限制文本行数
交互修饰符
- onTapGesture():添加点击手势
- disabled():禁用视图
- accessibility():添加无障碍支持
动画修饰符
- animation():添加动画
- transition():添加转场动画
修饰符的顺序
修饰符的顺序很重要,因为每个修饰符都会作用于前一个修饰符返回的视图。
示例:
// 先设置背景,再添加内边距
Text("Hello")
.background(Color.blue)
.padding()
// 先添加内边距,再设置背景
Text("Hello")
.padding()
.background(Color.blue)
这两种写法会产生不同的效果,第一种背景只覆盖文本区域,第二种背景会覆盖整个内边距区域。
实战:创建一个信息卡片
需求分析
创建一个包含以下元素的信息卡片:
- 标题
- 副标题
- 描述文本
- 图标
- 卡片样式(圆角、阴影)
代码实现
import SwiftUI
struct InfoCardView: View {
// 卡片数据
let title: String
let subtitle: String
let description: String
let iconName: String
let iconColor: Color
var body: some View {
VStack(alignment: .leading, spacing: 12) {
// 图标和标题区域
HStack(alignment: .center, spacing: 12) {
// 图标
Circle()
.fill(iconColor.opacity(0.1))
.frame(width: 48, height: 48)
.overlay {
Image(systemName: iconName)
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.foregroundStyle(iconColor)
}
// 标题和副标题
VStack(alignment: .leading, spacing: 4) {
Text(title)
.font(.headline)
.fontWeight(.semibold)
Text(subtitle)
.font(.subheadline)
.foregroundStyle(.secondary)
}
}
// 描述文本
Text(description)
.font(.body)
.foregroundStyle(.primary)
.lineLimit(nil) // 不限制行数
}
.padding(16) // 卡片内边距
.background(
RoundedRectangle(cornerRadius: 12)
.fill(Color(.systemBackground))
.shadow(color: .black.opacity(0.1), radius: 4, x: 0, y: 2)
)
.padding(.horizontal, 16) // 卡片外边距
}
}
// 使用示例
struct ContentView: View {
var body: some View {
VStack(spacing: 16) {
InfoCardView(
title: "SwiftUI 简介",
subtitle: "现代 UI 框架",
description: "SwiftUI 是一个声明式 UI 框架,允许开发者使用 Swift 语言创建跨 Apple 平台的用户界面。它提供了简洁、直观的语法,使 UI 开发变得更加高效。",
iconName: "star.fill",
iconColor: .yellow
)
InfoCardView(
title: "声明式编程",
subtitle: "现代编程范式",
description: "声明式编程让开发者只需要描述界面的样子,而不需要关心如何实现。系统会自动处理视图的创建、更新和销毁。",
iconName: "code",
iconColor: .blue
)
InfoCardView(
title: "跨平台",
subtitle: "一次编写,多处运行",
description: "SwiftUI 支持 iOS、iPadOS、macOS、watchOS 和 tvOS,让你的代码可以在所有 Apple 平台上运行。",
iconName: "globe",
iconColor: .green
)
}
.padding(.vertical, 16)
.background(Color(.systemGroupedBackground))
}
}
#Preview {
ContentView()
}
代码解析
- 结构体参数:通过参数传递卡片数据,使视图可复用
- VStack 和 HStack:使用栈布局组织视图
- Circle:创建圆形背景
- overlay:在圆形上叠加图标
- RoundedRectangle:创建圆角矩形背景
- shadow:添加阴影效果
- spacing:设置栈视图的间距
- alignment:设置栈视图的对齐方式
小结
本章介绍了声明式 UI 的基础概念,包括:
- 声明式 vs 命令式 UI 的对比
- View 协议与 body 计算属性
- 结构体视图与值类型的特性
- 修饰符的使用方法和顺序
- 一个信息卡片的实战实现
通过本章的学习,你已经了解了 SwiftUI 的基本工作原理和核心概念,为后续的学习打下了坚实的基础。