SwiftUI — Apple 用于在生态系统所有平台上构建用户界面的声明式框架,于 WWDC 2019 上推出。与命令式的 UIKit 及其 viewDidLoad 和手动更新屏幕不同,SwiftUI 将 UI 描述为符合 View 协议的简单结构的集合。根据 Swift.org(2025)的数据,SwiftUI 用于 App Store 中发布的 65% 的新项目。该框架通过 State 和 Data Flow 机制自动管理界面更新——当数据发生变化时,View 会自动重新绘制,无需手动调用 reloadData。
要点
SwiftUI — Apple 的声明式 UI 框架,与 UIKit 有着根本性的不同。开发者无需创建控制器、视图并手动管理它们的生命周期,而是以声明的方式描述界面:屏幕上应该有什么,而不是如何构建它。SwiftUI 基于响应式原则:界面是状态的函数。当状态(State)发生变化时,SwiftUI 会自动重新计算所有依赖 View 的 body,并且只更新屏幕中已更改的部分。SwiftUI 适用于 iOS 13+、iPadOS 13+、macOS 10.15+、watchOS 6+、tvOS 13+ 和 visionOS 1+。SwiftUI 代码是跨平台的:一个文件在 iPhone、iPad、Mac 和 Apple Watch 上运行,只需最少的平台适配。根据 Apple WWDC Session 101(2024),SwiftUI 覆盖了 App Store 中超过 90% 的标准 UI 模式。
UIKit — 命令式框架(2008):开发者创建 UIViewController,在 viewDidLoad 中配置子视图,为 UITableView 实现 delegate/datasource,并通过 reloadData 或 setNeedsLayout 更新屏幕。SwiftUI 用简单的 View 结构替换控制器,用 binding 和 onChange 替换委托,用 HStack/VStack/ZStack 及修饰符(padding、frame、offset)替换 Auto Layout。UIKit 需要通过 ARC 手动管理内存;SwiftUI —— 不需要引用计数的结构。SwiftUI 的性能与 UIKit 相当:该框架使用 diffing 算法来实现最小的更改集。在 IT Sectr,SwiftUI 用于目标为 iOS 17+ 的新项目;支持 iOS 14–15 的项目需要 UIKit,因为 SwiftUI 的兼容性有限。
在 SwiftUI 中,界面通过 ViewBuilder 描述——一种 result builder,将一组 View 转换为元组或 Group。修饰符(.padding()、.font()、.foregroundColor())会创建具有修改设置的新 View,而不是改变原始对象。每个修饰符返回一个新的 View,从而允许链式调用。ViewBuilder 支持 if/else、switch、ForEach——无需单独控制器的条件性和循环性渲染。SwiftUI 中的 View 是值类型(struct),这保证了可预测的行为并消除了竞态条件。
View — 只有一个要求的协议:类型为 some View 的计算属性 body。每个符合 View 的结构都在 body 中描述其屏幕部分。some View 类型——不透明返回类型,隐藏返回的 View 的具体类型(VStack、HStack、ZStack、Text、Image 等的组合)。Swift 编译器在编译阶段推断具体类型,保持直接调用的性能,无需类型擦除。
import SwiftUI
struct GreetingView: View {
var name: String
var body: some View {
VStack(spacing: 12) {
Text("你好,\(name)!")
.font(.largeTitle)
.foregroundColor(.primary)
Text("欢迎使用 SwiftUI")
.font(.body)
.foregroundColor(.secondary)
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.fill(.ultraThinMaterial)
)
}
}GreetingView 结构接受 name 参数并在垂直堆栈中显示两个文本块。.font、.foregroundColor、.padding 和 .background 修饰符配置外观。SwiftUI 在输入参数(name)每次更改时都会调用 body——重新绘制仅针对更改的部分进行。示例中使用了带有 .ultraThinMaterial 的 RoundedRectangle——SwiftUI 内置的原生模糊背景。
@State — 声明属于单个 View 的本地状态的属性包装器。SwiftUI 自动管理 State 的内存:当值发生变化时,body 会重新绘制,但仅限于使用此 State 的 View。State 是简单类型(String、Int、Bool、enum)的事实来源(source of truth)。不要对复杂数据模型使用 @State——@StateObject 和 @ObservedObject 用于此类目的。State 应该是私有的,并存储在 View 本身中,而不是在组件之间传递。
import SwiftUI
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
Text("计数:\(count)")
.font(.system(size: 48, weight: .bold))
Button(action: { count += 1 }) {
Label("增加", systemImage: "plus.circle")
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}初始值 count = 0。每次按钮点击都会增加 count;SwiftUI 自动完全重新绘制 CounterView(所有 View)。在 UIKit 中,类似的场景需要 IBOutlet、IBAction 和手动更新 label.text。@State 保证 View 仅在特定 State 更改时才重新绘制——SwiftUI 的 diffing 算法会在树中找到最小的更改。
@Binding — 在 View 和它不拥有的数据之间创建双向连接的属性包装器。Binding 是对 State(或其他事实来源)的引用,允许子 View 读取和更改存储在父级中的值。Binding 用 $ 前缀表示:$count 将 Binding<Int> 传递给子 View。没有 Binding,子 View 无法更改父级的数据——只能读取它们。
import SwiftUI
struct StepperControl: View {
@Binding var value: Int
let range: ClosedRange<Int>
var body: some View {
HStack {
Button(action: { if value > range.lowerBound { value -= 1 } }) {
Image(systemName: "minus.circle")
}
Text("\(value)")
.frame(minWidth: 40)
Button(action: { if value < range.upperBound { value += 1 } }) {
Image(systemName: "plus.circle")
}
}
}
}
struct ParentView: View {
@State private var quantity = 5
var body: some View {
StepperControl(value: $quantity, range: 1...10)
}
}ParentView 拥有 State quantity 并通过 $quantity 传递 Binding。StepperControl 可以更改 value,并且父级中的 quantity 会自动同步。Binding 不是数据的副本,而是通往事实来源的桥梁。对于必须更改父级数据的自定义控件、编辑器和可重用组件,请使用 @Binding。
@StateObject — 用于创建和拥有符合 ObservableObject 的类实例的属性包装器。View 在每个生命周期中创建一次该对象,并在其 @Published 属性更改时重新绘制。@ObservedObject — 类似的包装器,但 View 不拥有该对象——该对象是在 View 外部创建和存储的(通过初始化器传递)。Apple 建议在 View 层次结构中使用 @StateObject 作为事实来源,使用 @ObservedObject 进行依赖注入。
import SwiftUI
import Combine
class UserSettings: ObservableObject {
@Published var username: String = "Guest"
@Published var isLoggedIn = false
}
struct ProfileView: View {
@StateObject private var settings = UserSettings()
var body: some View {
VStack {
TextField("Username", text: $settings.username)
.textFieldStyle(.roundedBorder)
Toggle("Logged In", isOn: $settings.isLoggedIn)
if settings.isLoggedIn {
Text("欢迎,\(settings.username)!")
.font(.headline)
}
}
.padding()
}
}UserSettings — 具有两个 @Published 属性的 ObservableObject。ProfileView 通过 @StateObject 拥有该对象。username 或 isLoggedIn 的更改会自动重新绘制 ProfileView。@Published 使用 Combine Publisher 将更改通知 SwiftUI。要将 settings 传递给子 View,请使用 @ObservedObject:
Apple 定义了 SwiftUI 中的四个 Data Flow 级别:@State(本地,值类型)、@Binding(双向)、@StateObject/@ObservedObject(带有 ObservableObject 的引用类型)、@EnvironmentObject(全局,通过环境注入)。EnvironmentObject 允许数据通过整个 View 层次结构传递,而无需在初始化器中显式传递。此外,@AppStorage 与 UserDefaults 一起工作,@SceneStorage —— 与场景状态一起工作,@FetchRequest —— 与 Core Data 一起工作。选择 Data Flow 级别决定了应用程序的架构:简单屏幕使用 State/Binding,模块化——ObservedObject,大规模——EnvironmentObject + 类似 Redux 的解决方案(TCA、Composable Architecture)。
| 属性包装器 | 所有权 | 类型 | 使用时机 |
|---|---|---|---|
| @State | 本地 | 值(struct、enum) | 单个 View 的简单状态(计数器、切换开关、文本字段) |
| @Binding | 外部 | 对 State 的引用 | 更改父级数据的子 View |
| @StateObject | View 拥有 | 引用(类) | 复杂数据模型的事实来源 |
| @ObservedObject | 注入 | 引用(类) | 在 View 外部创建的模型(通过 init 传递) |
| @EnvironmentObject | 全局 | 引用(类) | 整个层次结构可访问的数据(认证、主题) |
常见问题
@State —— 用于值类型(struct、enum、String、Int)和单个 View 的本地状态。SwiftUI 自动管理 State 的内存。@StateObject —— 用于符合 ObservableObject 的引用类型(类)。@StateObject 拥有该对象,并在 @Published 属性更改时重新绘制 View。对于简单的计数器,使用 @State;对于带有逻辑的模型,使用 @StateObject。
是的,SwiftUI 通过 UIHostingController(UIKit 内部的 SwiftUI)和 UIViewRepresentable(SwiftUI 内部的 UIKit)与 UIKit 集成。UIHostingController 将 SwiftUI View 包装到 UIViewController 中。UIViewRepresentable 允许在 SwiftUI 中使用 UIKit 组件(MKMapView、WKWebView)。这是从 UIKit 迁移到 SwiftUI 的标准方法。
ViewBuilder —— result builder(Swift 5.1),将一组 View 转换为类型为 TupleView、Group 或 ConditionalContent 的单个值。ViewBuilder 允许在声明式 body 中编写命令式的 if/else 和 switch。如果没有 ViewBuilder,每个条件块都需要返回 AnyView 或 Group。ViewBuilder 是 body 中 View 之间不需要逗号的原因。
是的,SwiftUI 支持 iOS 13+、iPadOS 13+、macOS 10.15+、watchOS 6+、tvOS 13+ 和 visionOS 1+。但是某些 API 仅适用于较新版本:例如 navigationStack(iOS 16+)、Observable macro(iOS 17+)。为了实现向后兼容性,请使用 #available 和 UIKit 适配。
Xcode Debug View Hierarchy 显示带有修饰符和框架的 SwiftUI View 树。SwiftUI Inspector 工具(Xcode 的右侧面板)允许实时更改修饰符。self._printChanges() 在 body 中记录重新绘制的原因。Instruments 使用 SwiftUI 模板跟踪 View 性能并检测过度重新绘制。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。