swiftui-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwiftUI Patterns
SwiftUI 设计模式
Modern SwiftUI patterns for building declarative, performant user interfaces on Apple platforms. Covers the Observation framework, view composition, type-safe navigation, and performance optimization.
适用于Apple平台的现代SwiftUI设计模式,用于构建声明式、高性能的用户界面。涵盖Observation框架、视图组合、类型安全导航和性能优化。
When to Activate
适用场景
- Building SwiftUI views and managing state (,
@State,@Observable)@Binding - Designing navigation flows with
NavigationStack - Structuring view models and data flow
- Optimizing rendering performance for lists and complex layouts
- Working with environment values and dependency injection in SwiftUI
- 构建SwiftUI视图并管理状态(、
@State、@Observable)@Binding - 使用设计导航流程
NavigationStack - 构建视图模型和数据流
- 优化列表和复杂布局的渲染性能
- 在SwiftUI中使用环境值和依赖注入
State Management
状态管理
Property Wrapper Selection
属性包装器选择
Choose the simplest wrapper that fits:
| Wrapper | Use Case |
|---|---|
| View-local value types (toggles, form fields, sheet presentation) |
| Two-way reference to parent's |
| Owned model with multiple properties |
| Read-only reference passed from parent |
| Two-way binding to an |
| Shared dependencies injected via |
选择最适合场景的最简单包装器:
| 包装器 | 适用场景 |
|---|---|
| 视图本地的值类型(开关、表单字段、弹窗展示) |
| 对父视图 |
| 拥有多个属性的自有模型 |
| 从父视图传入的只读引用 |
| 与 |
| 通过 |
@Observable ViewModel
@Observable 视图模型
Use (not ) — it tracks property-level changes so SwiftUI only re-renders views that read the changed property:
@ObservableObservableObjectswift
@Observable
final class ItemListViewModel {
private(set) var items: [Item] = []
private(set) var isLoading = false
var searchText = ""
private let repository: any ItemRepository
init(repository: any ItemRepository = DefaultItemRepository()) {
self.repository = repository
}
func load() async {
isLoading = true
defer { isLoading = false }
items = (try? await repository.fetchAll()) ?? []
}
}使用(而非)——它会跟踪属性级别的变化,因此SwiftUI只会重新渲染读取了变化属性的视图:
@ObservableObservableObjectswift
@Observable
final class ItemListViewModel {
private(set) var items: [Item] = []
private(set) var isLoading = false
var searchText = ""
private let repository: any ItemRepository
init(repository: any ItemRepository = DefaultItemRepository()) {
self.repository = repository
}
func load() async {
isLoading = true
defer { isLoading = false }
items = (try? await repository.fetchAll()) ?? []
}
}View Consuming the ViewModel
消费视图模型的视图
swift
struct ItemListView: View {
@State private var viewModel: ItemListViewModel
init(viewModel: ItemListViewModel = ItemListViewModel()) {
_viewModel = State(initialValue: viewModel)
}
var body: some View {
List(viewModel.items) { item in
ItemRow(item: item)
}
.searchable(text: $viewModel.searchText)
.overlay { if viewModel.isLoading { ProgressView() } }
.task { await viewModel.load() }
}
}swift
struct ItemListView: View {
@State private var viewModel: ItemListViewModel
init(viewModel: ItemListViewModel = ItemListViewModel()) {
_viewModel = State(initialValue: viewModel)
}
var body: some View {
List(viewModel.items) { item in
ItemRow(item: item)
}
.searchable(text: $viewModel.searchText)
.overlay { if viewModel.isLoading { ProgressView() } }
.task { await viewModel.load() }
}
}Environment Injection
环境注入
Replace with :
@EnvironmentObject@Environmentswift
// Inject
ContentView()
.environment(authManager)
// Consume
struct ProfileView: View {
@Environment(AuthManager.self) private var auth
var body: some View {
Text(auth.currentUser?.name ?? "Guest")
}
}用替代:
@Environment@EnvironmentObjectswift
// 注入
ContentView()
.environment(authManager)
// 消费
struct ProfileView: View {
@Environment(AuthManager.self) private var auth
var body: some View {
Text(auth.currentUser?.name ?? "Guest")
}
}View Composition
视图组合
Extract Subviews to Limit Invalidation
提取子视图以限制无效重渲染
Break views into small, focused structs. When state changes, only the subview reading that state re-renders:
swift
struct OrderView: View {
@State private var viewModel = OrderViewModel()
var body: some View {
VStack {
OrderHeader(title: viewModel.title)
OrderItemList(items: viewModel.items)
OrderTotal(total: viewModel.total)
}
}
}将视图拆分为小型、专注的结构体。当状态变化时,只有读取该状态的子视图会重新渲染:
swift
struct OrderView: View {
@State private var viewModel = OrderViewModel()
var body: some View {
VStack {
OrderHeader(title: viewModel.title)
OrderItemList(items: viewModel.items)
OrderTotal(total: viewModel.total)
}
}
}ViewModifier for Reusable Styling
用于可复用样式的ViewModifier
swift
struct CardModifier: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(.regularMaterial)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
}
extension View {
func cardStyle() -> some View {
modifier(CardModifier())
}
}swift
struct CardModifier: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(.regularMaterial)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
}
extension View {
func cardStyle() -> some View {
modifier(CardModifier())
}
}Navigation
导航
Type-Safe NavigationStack
类型安全的NavigationStack
Use with for programmatic, type-safe routing:
NavigationStackNavigationPathswift
@Observable
final class Router {
var path = NavigationPath()
func navigate(to destination: Destination) {
path.append(destination)
}
func popToRoot() {
path = NavigationPath()
}
}
enum Destination: Hashable {
case detail(Item.ID)
case settings
case profile(User.ID)
}
struct RootView: View {
@State private var router = Router()
var body: some View {
NavigationStack(path: $router.path) {
HomeView()
.navigationDestination(for: Destination.self) { dest in
switch dest {
case .detail(let id): ItemDetailView(itemID: id)
case .settings: SettingsView()
case .profile(let id): ProfileView(userID: id)
}
}
}
.environment(router)
}
}结合和实现程序化、类型安全的路由:
NavigationStackNavigationPathswift
@Observable
final class Router {
var path = NavigationPath()
func navigate(to destination: Destination) {
path.append(destination)
}
func popToRoot() {
path = NavigationPath()
}
}
enum Destination: Hashable {
case detail(Item.ID)
case settings
case profile(User.ID)
}
struct RootView: View {
@State private var router = Router()
var body: some View {
NavigationStack(path: $router.path) {
HomeView()
.navigationDestination(for: Destination.self) { dest in
switch dest {
case .detail(let id): ItemDetailView(itemID: id)
case .settings: SettingsView()
case .profile(let id): ProfileView(userID: id)
}
}
}
.environment(router)
}
}Performance
性能优化
Use Lazy Containers for Large Collections
对大型集合使用惰性容器
LazyVStackLazyHStackswift
ScrollView {
LazyVStack(spacing: 8) {
ForEach(items) { item in
ItemRow(item: item)
}
}
}LazyVStackLazyHStackswift
ScrollView {
LazyVStack(spacing: 8) {
ForEach(items) { item in
ItemRow(item: item)
}
}
}Stable Identifiers
稳定标识符
Always use stable, unique IDs in — avoid using array indices:
ForEachswift
// Use Identifiable conformance or explicit id
ForEach(items, id: \.stableID) { item in
ItemRow(item: item)
}在中始终使用稳定、唯一的ID——避免使用数组索引:
ForEachswift
// 使用Identifiable协议一致性或显式id
ForEach(items, id: \.stableID) { item in
ItemRow(item: item)
}Avoid Expensive Work in body
避免在body中执行昂贵操作
- Never perform I/O, network calls, or heavy computation inside
body - Use for async work — it cancels automatically when the view disappears
.task {} - Use and
.sensoryFeedback()sparingly in scroll views.geometryGroup() - Minimize ,
.shadow(), and.blur()in lists — they trigger offscreen rendering.mask()
- 切勿在或
body中执行I/O、网络调用或繁重计算init - 使用处理异步工作——当视图消失时会自动取消
.task {} - 在滚动视图中谨慎使用和
.sensoryFeedback().geometryGroup() - 在列表中尽量减少、
.shadow()和.blur()的使用——它们会触发离屏渲染.mask()
Equatable Conformance
Equatable协议一致性
For views with expensive bodies, conform to to skip unnecessary re-renders:
Equatableswift
struct ExpensiveChartView: View, Equatable {
let dataPoints: [DataPoint] // DataPoint must conform to Equatable
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.dataPoints == rhs.dataPoints
}
var body: some View {
// Complex chart rendering
}
}对于body计算成本高的视图,遵循协议以跳过不必要的重渲染:
Equatableswift
struct ExpensiveChartView: View, Equatable {
let dataPoints: [DataPoint] // DataPoint必须遵循Equatable
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.dataPoints == rhs.dataPoints
}
var body: some View {
// 复杂图表渲染
}
}Previews
预览
Use macro with inline mock data for fast iteration:
#Previewswift
#Preview("Empty state") {
ItemListView(viewModel: ItemListViewModel(repository: EmptyMockRepository()))
}
#Preview("Loaded") {
ItemListView(viewModel: ItemListViewModel(repository: PopulatedMockRepository()))
}使用宏和内联模拟数据实现快速迭代:
#Previewswift
#Preview("Empty state") {
ItemListView(viewModel: ItemListViewModel(repository: EmptyMockRepository()))
}
#Preview("Loaded") {
ItemListView(viewModel: ItemListViewModel(repository: PopulatedMockRepository()))
}Anti-Patterns to Avoid
需避免的反模式
- Using /
ObservableObject/@Published/@StateObjectin new code — migrate to@EnvironmentObject@Observable - Putting async work directly in or
body— useinitor explicit load methods.task {} - Creating view models as inside child views that don't own the data — pass from parent instead
@State - Using type erasure — prefer
AnyViewor@ViewBuilderfor conditional viewsGroup - Ignoring requirements when passing data to/from actors
Sendable
- 在新代码中使用/
ObservableObject/@Published/@StateObject——迁移到@EnvironmentObject@Observable - 将异步工作直接放在或
body中——使用init或显式加载方法.task {} - 在不拥有数据的子视图中将视图模型创建为——改为从父视图传入
@State - 使用类型擦除——优先使用
AnyView或@ViewBuilder处理条件视图Group - 在与actor之间传递数据时忽略要求
Sendable
References
参考资料
See skill: for actor-based persistence patterns.
See skill: for protocol-based DI and testing with Swift Testing.
swift-actor-persistenceswift-protocol-di-testing请查看技能:以了解基于actor的持久化模式。
请查看技能:以了解基于协议的依赖注入和Swift Testing测试。
swift-actor-persistenceswift-protocol-di-testing