前言
在 Apple 推出 SwiftUI 之后,iOS 开发迈入了声明式 UI 的新时代。本章节将系统性地回顾 SwiftUI 的基础语法与核心概念,并标注出每个特性的 Swift 版本与最低 iOS 支持版本,帮助开发者在项目中合理规划兼容性。
SwiftUI是什么?
SwiftUI 是 Apple 在 WWDC 2019(Swift 5.1)中引入的一套声明式 UI 框架,目标是用更简洁、更可组合、更具响应式的数据驱动方式构建 UI。SwiftUI 从 iOS 13 起可用,并在后续版本中不断增强。
基本结构:View 协议与组合视图
声明一个视图
struct GreetingView: View {
var body: some View {
Text("Hello, SwiftUI!")
}
}
- View是 SwiftUI 中所有视图的根协议。
- body属性是只读的,用于描述当前视图的UI.
- SwiftUI 会根据数据变化自动刷新视图
组合视图
SwiftUI 提倡组合(Composition)而非继承:
struct WelcomeView: View {
var body: some View {
VStack {
Text("Welcome")
.font(.title)
Text("SwiftUI makes UI easy")
.foregroundColor(.gray)
}
}
}
- VStack, HStack, ZStack 是基本布局容器。
- 所有的修饰符(.font(), .foregroundColor() 等)都是返回新的视图结构。
状态驱动:@State、@Binding、@ObservedObject 等
SwiftUI 的核心理念之一是:状态驱动 UI。各种属性包装器(Property Wrappers)用于绑定数据源与视图。
@State
struct CounterView: View {
@State private var count = 0
var body: some View {
Button("Clicked \(count) times") {
count += 1
}
}
}
- State 是本地状态,仅适用于当前View。
- 每次 count变化时,body会重新计算。
✅ iOS 13+
✅ Swift 5.1+
@Binding
struct ToggleView: View {
@Binding var isOn: Bool
var body: some View {
Toggle("Enable", isOn: $isOn)
}
}
@binding 用于在父子视图之间传递可变状态。
✅ iOS 13+
✅ Swift 5.1+
@ObservedObject 和 @StateObject
class TimerModel: ObservableObject {
@Published var time = Date()
}
struct TimerView: View {
@ObservedObject var model: TimerModel
var body: some View {
Text("\(model.time)")
}
}
- @ObservedObject 用于观察外部传入的可观察对象。
- @StateObject 是 iOS 14+ 提供的新属性,用于首次创建并持有生命周期的对象。
✅ @ObservedObject:iOS 13+ / Swift 5.1+
✅ @StateObject:iOS 14+ / Swift 5.3+
视图修饰符(Modifiers)
SwiftUI 中的视图修饰符是一种链式调用模式,提升了代码的可读性与组合性:
Text("Hello, World!")
.font(.headline)
.padding()
.background(Color.yellow)
.cornerRadius(8)
✅ 修饰符通用于 iOS 13+
修饰符返回新视图,不会修改原始视图本身。
条件与列表
if isLoggedIn {
Text("Welcome Back")
} else {
Text("Please Login")
}
SwiftUI 允许在 body 中写条件语句。
列表展示
struct ItemListView: View {
let items = ["Apple", "Banana", "Orange"]
var body: some View {
List(items, id: \.self) { item in
Text(item)
}
}
}
- List 会自动提供滚动与复用机制。
- id: 参数指定数据的唯一标识。
✅ List:iOS 13+
✅ 条件控制语法:SwiftUI 支持原生 Swift 条件表达式
生命周期与 App 协议(iOS 14+)
在 iOS 14 中,SwiftUI 引入了 @main 与 App 协议,简化 App 入口定义:
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
- 替代传统 UIApplicationDelegate.
- 可以组合多个Scene,支持多窗口(iPadOS).
✅ iOS 14+
✅ Swift 5.3+
特性 | 属性包装器/组件 | 最低 iOS 版本 | Swift 版本 |
---|---|---|---|
@State |
本地状态 | iOS 13+ | Swift 5.1+ |
@Binding |
绑定父状态 | iOS 13+ | Swift 5.1+ |
@ObservedObject |
外部状态观察 | iOS 13+ | Swift 5.1+ |
@StateObject |
自有状态持有(推荐) | iOS 14+ | Swift 5.3+ |
App 协议 |
新版 App 入口 | iOS 14+ | Swift 5.3+ |
List |
列表组件 | iOS 13+ | Swift 5.1+ |
在实际开发中,SwiftUI 与 UIKit 在架构思想、视图构建、状态管理、动画处理等方面有着根本的不同。本章将从以下几个关键维度出发,详细对比 SwiftUI 和 UIKit 的异同,并注释各项技术的可用最低版本和平台支持情况。
二、视图构建方式对比 SwiftUI vs UIKit
对比项 | SwiftUI | UIKit |
---|---|---|
声明方式 | 声明式(Declarative) | 命令式(Imperative) |
构建结构 | 使用 struct 和组合 View |
使用 UIViewController 、UIView
|
最低支持版本 | iOS 13+、macOS 10.15+ | iOS 2.0+ |
示例
var body: some View {
VStack {
Text("Hello")
Button("Tap Me") { /* 逻辑 */ }
}
}
let label = UILabel()
label.text = "Hello"
let button = UIButton()
button.setTitle("Tap Me", for: .normal)
view.addSubview(label)
view.addSubview(button)
|
生命周期管理
对比项 | SwiftUI | UIKit |
---|---|---|
生命周期入口 |
@main + App 协议 (SwiftUIApp ) |
UIApplicationDelegate , SceneDelegate
|
页面生命周期 |
onAppear , onDisappear
|
viewDidLoad , viewWillAppear , viewDidAppear 等 |
最低支持版本 | iOS 14+ (@main ),iOS 13+ (SceneDelegate ) |
iOS 2.0+ |
状态管理机制
对比项 | SwiftUI | UIKit |
---|---|---|
数据绑定 |
@State , @Binding , @ObservedObject , @EnvironmentObject
|
手动状态同步 |
UI 更新 | 自动响应数据变更 | 手动调用 reloadData 、setNeedsLayout 等 |
多页面数据共享 |
ObservableObject + @EnvironmentObject
|
通常通过代理、单例或闭包传递 |
最低支持版本 | iOS 13+ | iOS 2.0+ |
动画机制对比
对比项 | SwiftUI | UIKit |
---|---|---|
动画语法 |
.animation , .transition , .withAnimation
|
UIView.animate(...) |
控制细节 | 限于系统提供的组合 | 可通过 Core Animation 自定义 |
最低支持版本 | iOS 13+ | iOS 2.0+ |
SwiftUI 示例:
@State private var scale = 1.0
Button("Animate") {
withAnimation {
scale += 0.1
}
}
.scaleEffect(scale)
布局系统对比
对比项 | SwiftUI | UIKit |
---|---|---|
布局方式 | 使用 VStack , HStack , ZStack , GeometryReader
|
使用 Auto Layout , frame , stackView 等 |
自适应布局 | 更加自然响应尺寸变化 | 通常依赖 NSLayoutConstraint 或 SnapKit 等三方库 |
最低支持版本 | iOS 13+ | iOS 6+(Auto Layout) |
数据源绑定(列表)
对比项 | SwiftUI | UIKit |
---|---|---|
列表视图 |
List 组件 |
UITableView , UICollectionView
|
刷新机制 |
@State 或 ObservableObject 自动刷新 |
手动调用 reloadData()
|
最低支持版本 | iOS 13+ | iOS 2.0+ |
响应式编程支持
对比项 | SwiftUI | UIKit |
---|---|---|
响应式内建 | 原生支持 Combine 框架 | 通常需配合 RxSwift、Combine 手动集成 |
最低支持版本 | iOS 13+(Combine) | iOS 13+(Combine),iOS 8+(RxSwift) |
响应式声明 vs 命令式编程
比较项 | SwiftUI(声明式) | UIKit(命令式) |
---|---|---|
编程方式 | 声明式编程,描述 UI 的状态和依赖关系 | 命令式编程,逐步设置 UI 的属性与事件响应 |
UI 更新方式 | 自动追踪状态变化并刷新 UI | 手动触发 reloadData 或调用 setNeedsLayout 等方法 |
数据驱动 | 使用 @State 、@Binding 、@ObservedObject 等数据绑定工具 |
使用 MVC/MVVM 等手动绑定数据到视图 |
生命周期 | 组件级生命周期,使用 .onAppear 、.onDisappear 等处理 |
ViewController 生命周期,如 viewDidLoad 、viewWillAppear 等 |
动画处理 | 使用 withAnimation 轻松声明动画 |
使用 UIView.animate(...) 手动指定动画 |
示例
// SwiftUI 实现按钮点击计数器
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("点击次数: \(count)")
Button("点击我") {
count += 1
}
}
}
}
// UIKit 实现按钮点击计数器
class CounterViewController: UIViewController {
var count = 0
let label = UILabel()
let button = UIButton(type: .system)
override func viewDidLoad() {
super.viewDidLoad()
label.text = "点击次数: 0"
button.setTitle("点击我", for: .normal)
button.addTarget(self, action: #selector(increaseCount), for: .touchUpInside)
// 手动布局略
}
@objc func increaseCount() {
count += 1
label.text = "点击次数: \(count)"
}
}
三、 SwiftUI与UIKit 混合开发
尽管 SwiftUI 发展迅速,许多现有项目依然是基于 UIKit 构建的。在这种情况下,掌握如何将 SwiftUI 与 UIKit 混合使用成为过渡期的重要技能。Apple 也为这种混合提供了良好的支持。
在 UIKit 中嵌入 SwiftUI
在 UIKit 项目中,可以通过 UIHostingController 将 SwiftUI 视图嵌入到任意 UIKit 层级中:
import SwiftUI
struct MySwiftUIView: View {
var body: some View {
Text("这是 SwiftUI 视图")
.padding()
}
}
// 在 UIViewController 中使用
let swiftUIView = MySwiftUIView()
let hostingController = UIHostingController(rootView: swiftUIView)
present(hostingController, animated: true)
✅ 最低支持:iOS 13
✅ 使用场景:从 UIKit 过渡到 SwiftUI 的局部页面替换、展示临时页面、弹窗等
也可以直接将其嵌入某个 UIView:
let hosting = UIHostingController(rootView: MySwiftUIView())
addChild(hosting)
view.addSubview(hosting.view)
hosting.view.frame = view.bounds
hosting.didMove(toParent: self)
在 SwiftUI 中嵌入 UIKit
在 SwiftUI 中也可以嵌入 UIKit 控件或控制器,使用的是 UIViewRepresentable 和 UIViewControllerRepresentable 协议。
嵌入 UIView 示例:
import UIKit
import SwiftUI
struct UIKitButton: UIViewRepresentable {
func makeUIView(context: Context) -> UIButton {
let button = UIButton(type: .system)
button.setTitle("UIKit Button", for: .normal)
return button
}
func updateUIView(_ uiView: UIButton, context: Context) {
// 更新逻辑
}
}
嵌入 UIViewController 示例:
struct UIKitViewControllerWrapper: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> MyViewController {
return MyViewController()
}
func updateUIViewController(_ uiViewController: MyViewController, context: Context) {}
}
✅ 最低支持:iOS 13
✅ 使用场景:已有组件复用、自定义控件封装、三方库兼容、复杂动画控制等
状态共享与事件桥接
在混合开发中,SwiftUI 和 UIKit 如何共享状态或进行事件传递,是一个关键问题。
方法一:通过 Combine 桥接
使用 ObservableObject 与 @Published 可以让 UIKit 监听 SwiftUI 的状态变更,反之亦然。
class SharedModel: ObservableObject {
@Published var isOn = false
}
- 在 SwiftUI 中使用:@ObservedObject var model: SharedModel
- 在 UIKit 中订阅:model.$isOn.sink { ... }
方法二:使用代理(Delegate)或闭包(Closure)
当 SwiftUI 嵌入 UIKit 或 UIKit 嵌入 SwiftUI 时,可以使用闭包作为事件回调的桥梁。