swiftui-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwiftUI Patterns
SwiftUI 模式
Modern SwiftUI patterns targeting iOS 26+ with Swift 6.2. Covers architecture, state management, navigation, view composition, and component usage. Patterns are backward-compatible to iOS 17 unless noted.
面向iOS 26+、基于Swift 6.2的现代SwiftUI模式,涵盖架构、状态管理、导航、视图组合和组件使用。除非特别说明,所有模式均向下兼容至iOS 17。
Architecture: Model-View (MV) Pattern
架构:模型-视图(MV)模式
Default to MV -- views are lightweight state expressions; models and services own business logic. Do not introduce view models unless the existing code already uses them.
Core principles:
- Favor ,
@State,@Environment,@Query, and.taskfor orchestration.onChange - Inject services and shared models via ; keep views small and composable
@Environment - Split large views into smaller subviews rather than introducing a view model
- Test models, services, and business logic; keep views simple and declarative
swift
struct FeedView: View {
@Environment(FeedClient.self) private var client
enum ViewState {
case loading, error(String), loaded([Post])
}
@State private var viewState: ViewState = .loading
var body: some View {
List {
switch viewState {
case .loading:
ProgressView()
case .error(let message):
ContentUnavailableView("Error", systemImage: "exclamationmark.triangle",
description: Text(message))
case .loaded(let posts):
ForEach(posts) { post in
PostRow(post: post)
}
}
}
.task { await loadFeed() }
.refreshable { await loadFeed() }
}
private func loadFeed() async {
do {
let posts = try await client.getFeed()
viewState = .loaded(posts)
} catch {
viewState = .error(error.localizedDescription)
}
}
}For MV pattern rationale and extended examples, see .
references/mv-patterns.md默认优先使用MV模式:视图是轻量级的状态表达,业务逻辑由模型和服务负责。除非现有代码已经使用了视图模型,否则不要额外引入。
核心原则:
- 优先使用、
@State、@Environment、@Query和.task进行编排.onChange - 通过注入服务和共享模型,保持视图小巧可组合
@Environment - 将大型视图拆分为更小的子视图,而不是引入视图模型
- 针对模型、服务和业务逻辑编写测试,保持视图简洁声明式
swift
struct FeedView: View {
@Environment(FeedClient.self) private var client
enum ViewState {
case loading, error(String), loaded([Post])
}
@State private var viewState: ViewState = .loading
var body: some View {
List {
switch viewState {
case .loading:
ProgressView()
case .error(let message):
ContentUnavailableView("Error", systemImage: "exclamationmark.triangle",
description: Text(message))
case .loaded(let posts):
ForEach(posts) { post in
PostRow(post: post)
}
}
}
.task { await loadFeed() }
.refreshable { await loadFeed() }
}
private func loadFeed() async {
do {
let posts = try await client.getFeed()
viewState = .loaded(posts)
} catch {
viewState = .error(error.localizedDescription)
}
}
}如需了解MV模式的设计原理和更多示例,请查看。
references/mv-patterns.mdState Management
状态管理
@Observable Ownership Rules
@Observable 所有权规则
Important: Always annotate view model classes with to ensure UI-bound state is updated on the main thread. Required for Swift 6 concurrency safety.
@Observable@MainActor| Wrapper | When to Use |
|---|---|
| View owns the object or value. Creates and manages lifecycle. |
| View receives an |
| View receives an |
| Access shared |
| View-local simple state: toggles, counters, text field values. Always |
| Two-way connection to parent's |
重要提示: 所有视图模型类都需要添加注解,确保UI相关状态在主线程更新,这是Swift 6并发安全的强制要求。
@Observable@MainActor| 包装器 | 适用场景 |
|---|---|
| 视图持有该对象或值,负责创建和管理其生命周期。 |
| 视图接收一个 |
| 视图接收一个 |
| 从环境中访问共享的 |
| 视图本地的简单状态:开关、计数器、文本框输入值,始终声明为 |
| 与父级的 |
Ownership Pattern
所有权模式
swift
// @Observable view model -- always @MainActor
@MainActor
@Observable final class ItemStore {
var title = ""
var items: [Item] = []
}
// View that OWNS the model
struct ParentView: View {
@State var viewModel = ItemStore()
var body: some View {
ChildView(store: viewModel)
.environment(viewModel)
}
}
// View that READS (no wrapper needed for @Observable)
struct ChildView: View {
let store: ItemStore
var body: some View { Text(store.title) }
}
// View that BINDS (needs two-way access)
struct EditView: View {
@Bindable var store: ItemStore
var body: some View {
TextField("Title", text: $store.title)
}
}
// View that reads from ENVIRONMENT
struct DeepView: View {
@Environment(ItemStore.self) var store
var body: some View {
@Bindable var s = store
TextField("Title", text: $s.title)
}
}Granular tracking: SwiftUI only re-renders views that read properties that changed. If a view reads but not , changing does not trigger a re-render. This is a major performance advantage over .
itemsisLoadingisLoadingObservableObjectswift
// @Observable view model -- always @MainActor
@MainActor
@Observable final class ItemStore {
var title = ""
var items: [Item] = []
}
// View that OWNS the model
struct ParentView: View {
@State var viewModel = ItemStore()
var body: some View {
ChildView(store: viewModel)
.environment(viewModel)
}
}
// View that READS (no wrapper needed for @Observable)
struct ChildView: View {
let store: ItemStore
var body: some View { Text(store.title) }
}
// View that BINDS (needs two-way access)
struct EditView: View {
@Bindable var store: ItemStore
var body: some View {
TextField("Title", text: $store.title)
}
}
// View that reads from ENVIRONMENT
struct DeepView: View {
@Environment(ItemStore.self) var store
var body: some View {
@Bindable var s = store
TextField("Title", text: $s.title)
}
}细粒度更新: SwiftUI仅会重新渲染读取了发生变更属性的视图。如果一个视图仅读取了而没有读取,那么修改不会触发该视图重绘,这是相比的核心性能优势。
itemsisLoadingisLoadingObservableObjectLegacy ObservableObject
遗留ObservableObject
Only use if supporting iOS 16 or earlier. → , → , → .
@StateObject@State@ObservedObjectlet@EnvironmentObject@Environment(Type.self)仅当需要支持iOS 16或更低版本时使用,对应替换规则: → 、 → 、 → 。
@StateObject@State@ObservedObjectlet@EnvironmentObject@Environment(Type.self)View Ordering Convention
视图成员排序约定
Order members top to bottom within a view struct:
- properties
@Environment - /
privatepublicpropertieslet - and other stored properties
@State - Computed (non-view)
var initbody- Computed view builders and view helpers
- Helper and async functions
视图结构体内部的成员按从上到下的顺序排列:
- 属性
@Environment - /
privatepublic属性let - 和其他存储属性
@State - 非视图类型的计算
var - 构造函数
init - 属性
body - 计算视图构造器和视图辅助方法
- 工具方法和异步函数
View Composition
视图组合
Extract Subviews
提取子视图
Break views into focused subviews. Each should have a single responsibility.
swift
var body: some View {
VStack {
HeaderSection(title: title, isPinned: isPinned)
DetailsSection(details: details)
ActionsSection(onSave: onSave, onCancel: onCancel)
}
}将视图拆分为职责单一的聚焦子视图,每个子视图仅负责一项功能。
swift
var body: some View {
VStack {
HeaderSection(title: title, isPinned: isPinned)
DetailsSection(details: details)
ActionsSection(onSave: onSave, onCancel: onCancel)
}
}Computed View Properties
计算视图属性
Keep related subviews as computed properties in the same file; extract to a standalone struct when reuse is intended or the subview carries its own state.
Viewswift
var body: some View {
List {
header
filters
results
}
}
private var header: some View {
VStack(alignment: .leading, spacing: 6) {
Text(title).font(.title2)
Text(subtitle).font(.subheadline)
}
}将相关的子视图作为计算属性放在同一个文件中;如果需要复用或者子视图自带独立状态,则提取为独立的结构体。
Viewswift
var body: some View {
List {
header
filters
results
}
}
private var header: some View {
VStack(alignment: .leading, spacing: 6) {
Text(title).font(.title2)
Text(subtitle).font(.subheadline)
}
}ViewBuilder Functions
ViewBuilder 函数
For conditional logic that does not warrant a separate struct:
swift
@ViewBuilder
private func statusBadge(for status: Status) -> some View {
switch status {
case .active: Text("Active").foregroundStyle(.green)
case .inactive: Text("Inactive").foregroundStyle(.secondary)
}
}适用于不需要单独封装结构体的条件逻辑场景:
swift
@ViewBuilder
private func statusBadge(for status: Status) -> some View {
switch status {
case .active: Text("Active").foregroundStyle(.green)
case .inactive: Text("Inactive").foregroundStyle(.secondary)
}
}Custom View Modifiers
自定义View Modifier
Extract repeated styling into :
ViewModifierswift
struct CardStyle: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(.background)
.clipShape(RoundedRectangle(cornerRadius: 12))
.shadow(radius: 2)
}
}
extension View { func cardStyle() -> some View { modifier(CardStyle()) } }将重复的样式逻辑提取为:
ViewModifierswift
struct CardStyle: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(.background)
.clipShape(RoundedRectangle(cornerRadius: 12))
.shadow(radius: 2)
}
}
extension View { func cardStyle() -> some View { modifier(CardStyle()) } }Stable View Tree
稳定的视图树
Avoid top-level conditional view swapping. Prefer a single stable base view with conditions inside sections or modifiers (, , , ).
overlayopacitydisabledtoolbar避免在顶层使用条件判断切换视图,优先使用单个稳定的基础视图,在内部的区块或修饰符(、、、)中使用条件逻辑。
overlayopacitydisabledtoolbarLarge File Handling
大文件处理
When a view file exceeds ~300 lines, split with extensions and comments.
// MARK: -当视图文件超过约300行时,使用扩展和注释进行拆分。
// MARK: -Navigation
导航
NavigationStack (Push Navigation)
NavigationStack(压栈导航)
swift
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List(items) { item in
NavigationLink(value: item) {
ItemRow(item: item)
}
}
.navigationDestination(for: Item.self) { item in
DetailView(item: item)
}
.navigationTitle("Items")
}
}
}Programmatic navigation:
swift
path.append(item) // Push
path.removeLast() // Pop one
path = NavigationPath() // Pop to rootswift
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List(items) { item in
NavigationLink(value: item) {
ItemRow(item: item)
}
}
.navigationDestination(for: Item.self) { item in
DetailView(item: item)
}
.navigationTitle("Items")
}
}
}编程式导航:
swift
path.append(item) // 压入新页面
path.removeLast() // 弹出上一个页面
path = NavigationPath() // 弹出到根页面NavigationSplitView (Multi-Column)
NavigationSplitView(多列导航)
swift
struct MasterDetailView: View {
@State private var selectedItem: Item?
var body: some View {
NavigationSplitView {
List(items, selection: $selectedItem) { item in
NavigationLink(value: item) { ItemRow(item: item) }
}
.navigationTitle("Items")
} detail: {
if let item = selectedItem {
ItemDetailView(item: item)
} else {
ContentUnavailableView("Select an Item", systemImage: "sidebar.leading")
}
}
}
}swift
struct MasterDetailView: View {
@State private var selectedItem: Item?
var body: some View {
NavigationSplitView {
List(items, selection: $selectedItem) { item in
NavigationLink(value: item) { ItemRow(item: item) }
}
.navigationTitle("Items")
} detail: {
if let item = selectedItem {
ItemDetailView(item: item)
} else {
ContentUnavailableView("Select an Item", systemImage: "sidebar.leading")
}
}
}
}Sheet Presentation
Sheet 弹窗
Prefer over when state represents a selected model. Sheets should own their actions and call internally.
.sheet(item:).sheet(isPresented:)dismiss()swift
@State private var selectedItem: Item?
.sheet(item: $selectedItem) { item in
EditItemSheet(item: item)
}Presentation sizing (iOS 18+): Control sheet dimensions with :
.presentationSizingswift
.sheet(item: $selectedItem) { item in
EditItemSheet(item: item)
.presentationSizing(.form) // .form, .page, .fitted, .automatic
}Dismissal confirmation (iOS 26+):
swift
.sheet(item: $selectedItem) { item in
EditItemSheet(item: item)
.dismissalConfirmationDialog("Discard changes?", shouldPresent: hasUnsavedChanges) {
Button("Discard", role: .destructive) { discardChanges() }
}
}Enum-driven sheet routing: Centralize sheets with an enum and a helper modifier. See and for full patterns.
references/sheets.mdreferences/app-wiring.md当状态对应选中的模型时,优先使用而非。Sheet内部应该自行处理操作逻辑并调用关闭。
.sheet(item:).sheet(isPresented:)dismiss()swift
@State private var selectedItem: Item?
.sheet(item: $selectedItem) { item in
EditItemSheet(item: item)
}弹窗尺寸控制(iOS 18+): 使用控制sheet的尺寸:
.presentationSizingswift
.sheet(item: $selectedItem) { item in
EditItemSheet(item: item)
.presentationSizing(.form) // 可选值:.form, .page, .fitted, .automatic
}关闭确认(iOS 26+):
swift
.sheet(item: $selectedItem) { item in
EditItemSheet(item: item)
.dismissalConfirmationDialog("Discard changes?", shouldPresent: hasUnsavedChanges) {
Button("Discard", role: .destructive) { discardChanges() }
}
}枚举驱动的sheet路由: 使用枚举和辅助修饰符统一管理sheet,完整模式请查看和。
references/sheets.mdreferences/app-wiring.mdTab-Based Navigation
标签页导航
iOS 26 introduces an expanded Tab API with , , roles, and customization:
TabTabSectionswift
struct MainTabView: View {
@State private var selectedTab: AppTab = .home
var body: some View {
TabView(selection: $selectedTab) {
Tab("Home", systemImage: "house", value: .home) {
NavigationStack { HomeView() }
}
Tab("Search", systemImage: "magnifyingglass", value: .search) {
NavigationStack { SearchView() }
}
Tab("Profile", systemImage: "person", value: .profile) {
NavigationStack { ProfileView() }
}
Tab(role: .search) {
SearchView()
}
}
.tabBarMinimizeBehavior(.onScrollDown) // iOS 26: auto-hide tab bar on scroll
}
}iOS 26 additions:
- replaces the tab bar with a search field
Tab(role: .search) - --
.tabBarMinimizeBehavior(_:),.onScrollDown,.onScrollUp.never - for sidebar grouping,
TabSection,.tabViewSidebarHeader/Footer/BottomBarTabViewBottomAccessoryPlacement
See for full TabView patterns and for root shell wiring.
references/tabview.mdreferences/app-wiring.mdiOS 26引入了扩展的Tab API,支持、、角色配置和自定义功能:
TabTabSectionswift
struct MainTabView: View {
@State private var selectedTab: AppTab = .home
var body: some View {
TabView(selection: $selectedTab) {
Tab("Home", systemImage: "house", value: .home) {
NavigationStack { HomeView() }
}
Tab("Search", systemImage: "magnifyingglass", value: .search) {
NavigationStack { SearchView() }
}
Tab("Profile", systemImage: "person", value: .profile) {
NavigationStack { ProfileView() }
}
Tab(role: .search) {
SearchView()
}
}
.tabBarMinimizeBehavior(.onScrollDown) // iOS 26特性:滚动时自动隐藏标签栏
}
}iOS 26新增特性:
- 将标签栏替换为搜索框
Tab(role: .search) - 支持
.tabBarMinimizeBehavior(_:)、.onScrollDown、.onScrollUp三种行为.never - 支持侧边栏分组、
TabSection、.tabViewSidebarHeader/Footer/BottomBar等配置TabViewBottomAccessoryPlacement
完整的TabView模式请查看,根容器配置请查看。
references/tabview.mdreferences/app-wiring.mdEnvironment
环境变量
Custom Environment Values
自定义环境变量
swift
private struct ThemeKey: EnvironmentKey {
static let defaultValue: Theme = .default
}
extension EnvironmentValues {
var theme: Theme {
get { self[ThemeKey.self] }
set { self[ThemeKey.self] = newValue }
}
}
// Usage
.environment(\.theme, customTheme)
@Environment(\.theme) var themeswift
private struct ThemeKey: EnvironmentKey {
static let defaultValue: Theme = .default
}
extension EnvironmentValues {
var theme: Theme {
get { self[ThemeKey.self] }
set { self[ThemeKey.self] = newValue }
}
}
// 使用方式
.environment(\.theme, customTheme)
@Environment(\.theme) var themeCommon Built-in Environment Values
常用内置环境变量
swift
@Environment(\.dismiss) var dismiss
@Environment(\.colorScheme) var colorScheme
@Environment(\.dynamicTypeSize) var dynamicTypeSize
@Environment(\.horizontalSizeClass) var sizeClass
@Environment(\.isSearching) var isSearching
@Environment(\.openURL) var openURL
@Environment(\.modelContext) var modelContextswift
@Environment(\.dismiss) var dismiss
@Environment(\.colorScheme) var colorScheme
@Environment(\.dynamicTypeSize) var dynamicTypeSize
@Environment(\.horizontalSizeClass) var sizeClass
@Environment(\.isSearching) var isSearching
@Environment(\.openURL) var openURL
@Environment(\.modelContext) var modelContextAsync Data Loading
异步数据加载
Always use -- it cancels automatically on view disappear:
.taskswift
struct ItemListView: View {
@State var store = ItemStore()
var body: some View {
List(store.items) { item in
ItemRow(item: item)
}
.task { await store.load() }
.refreshable { await store.refresh() }
}
}Use to re-run when a dependency changes:
.task(id:)swift
.task(id: searchText) {
guard !searchText.isEmpty else { return }
await search(query: searchText)
}Never create manual in unless you need to store a reference for cancellation. Exception: is acceptable in synchronous action closures (e.g., Button actions) for immediate state updates before async work.
TaskonAppearTask {}始终使用,它会在视图消失时自动取消任务:
.taskswift
struct ItemListView: View {
@State var store = ItemStore()
var body: some View {
List(store.items) { item in
ItemRow(item: item)
}
.task { await store.load() }
.refreshable { await store.refresh() }
}
}使用在依赖项变更时重新执行任务:
.task(id:)swift
.task(id: searchText) {
guard !searchText.isEmpty else { return }
await search(query: searchText)
}不要在中手动创建,除非你需要存储引用用于手动取消。例外情况:同步的动作闭包(比如Button点击事件)中可以使用,用于在异步工作执行前更新即时状态。
onAppearTaskTask {}iOS 26+ New APIs
iOS 26+ 新API
Scroll Edge Effects
滚动边缘效果
swift
ScrollView {
content
}
.scrollEdgeEffectStyle(.soft, for: .top) // iOS 26: fading edge effect
.backgroundExtensionEffect() // iOS 26: mirror/blur at safe area edgesswift
ScrollView {
content
}
.scrollEdgeEffectStyle(.soft, for: .top) // iOS 26特性:顶部渐变边缘效果
.backgroundExtensionEffect() // iOS 26特性:安全区域边缘的镜像/模糊效果@Animatable Macro (iOS 26+)
@Animatable 宏(iOS 26+)
Synthesizes conformance automatically:
AnimatableDataswift
@Animatable
struct PulseEffect: ViewModifier {
var scale: Double
// scale is automatically animatable -- no manual AnimatableData needed
}自动生成协议实现:
AnimatableDataswift
@Animatable
struct PulseEffect: ViewModifier {
var scale: Double
// scale自动支持动画,无需手动编写AnimatableData代码
}TextEditor Enhancements (iOS 26+)
TextEditor 增强(iOS 26+)
TextEditorAttributedStringFindContextTextEditorAttributedStringFindContextPerformance Guidelines
性能指南
- Lazy stacks/grids: Use ,
LazyVStack,LazyHStack,LazyVGridfor large collections. Regular stacks render all children immediately.LazyHGrid - Stable IDs: All items in /
Listmust conform toForEachwith stable IDs. Never use array indices.Identifiable - Avoid body recomputation: Move filtering and sorting to computed properties or the model, not inline in .
body - Equatable views: For complex views that re-render unnecessarily, conform to .
Equatable
- 懒加载栈/网格: 大型列表使用、
LazyVStack、LazyHStack、LazyVGrid,普通栈会立即渲染所有子元素。LazyHGrid - 稳定ID: /
List中的所有项必须实现ForEach协议并提供稳定ID,禁止使用数组索引。Identifiable - 避免body重复计算: 将过滤、排序逻辑移到计算属性或模型中,不要直接写在内联代码中。
body - Equatable视图: 对于不必要频繁重绘的复杂视图,让其遵循协议。
Equatable
Component Reference
组件参考
See for the full index of component-specific guides:
references/components-index.md- Layout: List, ScrollView, Grids, Split Views
- Navigation: NavigationStack, TabView, Sheets, Deep Links
- Input: Form, Controls, Focus, Searchable, Input Toolbar
- Presentation: Overlay/Toasts, Loading/Placeholders, Matched Transitions
- Platform: Top Bar, Title Menus, Menu Bar, macOS Settings
- Media & Theming: Media, Theming, Haptics
- Architecture: App Wiring, Lightweight Clients
Each component file includes intent, minimal usage pattern, pitfalls, and performance notes.
完整的组件指南索引请查看:
references/components-index.md- 布局: List、ScrollView、Grids、Split Views
- 导航: NavigationStack、TabView、Sheets、Deep Links
- 输入: Form、Controls、Focus、Searchable、Input Toolbar
- 展示: Overlay/Toasts、Loading/Placeholders、Matched Transitions
- 平台适配: Top Bar、Title Menus、Menu Bar、macOS Settings
- 媒体与主题: Media、Theming、Haptics
- 架构: App Wiring、Lightweight Clients
每个组件文件都包含设计意图、最简使用示例、常见陷阱和性能注意事项。
HIG Alignment
HIG 对齐
Follow Apple Human Interface Guidelines for layout, typography, color, and accessibility. Key rules:
- Use semantic colors (,
Color.primary,.secondary) for automatic light/dark modeColor(uiColor: .systemBackground) - Use system font styles (,
.title,.headline,.body) for Dynamic Type support.caption - Use for empty and error states
ContentUnavailableView - Support adaptive layouts via
horizontalSizeClass - Provide VoiceOver labels () and support Dynamic Type accessibility sizes by switching layout orientation
.accessibilityLabel
See for full HIG pattern examples.
references/hig-patterns.md布局、排版、颜色和无障碍设计遵循苹果人类界面指南,核心规则:
- 使用语义颜色(、
Color.primary、.secondary)实现自动明暗模式适配Color(uiColor: .systemBackground) - 使用系统字体样式(、
.title、.headline、.body)支持动态类型.caption - 空状态和错误状态使用
ContentUnavailableView - 通过支持自适应布局
horizontalSizeClass - 提供VoiceOver标签(),通过切换布局方向支持动态类型无障碍尺寸
.accessibilityLabel
完整的HIG模式示例请查看。
references/hig-patterns.mdCommon Mistakes
常见错误
- Using to create objects -- use
@ObservedObject(legacy) or@StateObject(modern)@State - Heavy computation in view -- move to model or computed property
body - Not using for async work -- manual
.taskinTaskleaks if not cancelledonAppear - Array indices as IDs -- causes incorrect diffing and UI bugs
ForEach - Forgetting --
@Bindablesyntax on$propertyrequires@Observable@Bindable - Over-using -- only for view-local state; shared state belongs in
@State@Observable - Not extracting subviews -- long body blocks are hard to read and optimize
- Using -- deprecated; use
NavigationViewNavigationStack - Inline closures in body -- extract complex closures to methods
- when state represents a model -- use
.sheet(isPresented:)instead.sheet(item:)
- 使用创建对象:应该使用
@ObservedObject(遗留方案)或@StateObject(现代方案)@State - 在视图中执行 heavy 计算:应该移到模型或计算属性中
body - 异步工作不使用:
.task中手动创建的onAppear如果没有取消会造成泄露Task - 使用数组索引作为的ID:会导致diff计算错误和UI异常
ForEach - 忘记添加:
@Bindable对象使用@Observable语法需要搭配$property@Bindable - 过度使用:仅用于视图本地状态,共享状态应该放在
@State对象中@Observable - 不提取子视图:过长的body代码可读性差,也难以优化
- 使用:已废弃,应该使用
NavigationViewNavigationStack - body中使用内联闭包:将复杂闭包提取为方法
- 状态对应模型时使用:应该优先使用
.sheet(isPresented:).sheet(item:)
Review Checklist
代码评审检查清单
- used for shared state models (not
@Observableon iOS 17+)ObservableObject - owns objects;
@State/letreceives them@Bindable - used (not
NavigationStack)NavigationView - modifier for async data loading
.task - /
LazyVStackfor large collectionsLazyHStack - Stable IDs (not array indices)
Identifiable - Views decomposed into focused subviews
- No heavy computation in view
body - Environment used for deeply shared state
- Custom for repeated styling
ViewModifier - preferred over
.sheet(item:).sheet(isPresented:) - Sheets own their actions and call internally
dismiss() - MV pattern followed -- no unnecessary view models
- view model classes are
@Observable-isolated@MainActor - Model types passed across concurrency boundaries are
Sendable
- iOS 17+版本使用作为共享状态模型(而非
@Observable)ObservableObject - 持有对象,
@State/let接收对象@Bindable - 使用(而非已废弃的
NavigationStack)NavigationView - 异步数据加载使用修饰符
.task - 大型集合使用/
LazyVStackLazyHStack - 使用稳定的ID(而非数组索引)
Identifiable - 视图已拆分为职责单一的子视图
- 视图中无heavy计算逻辑
body - 深层共享状态使用环境变量传递
- 重复样式封装为自定义
ViewModifier - 优先使用而非
.sheet(item:).sheet(isPresented:) - Sheet内部自行处理操作并调用关闭
dismiss() - 遵循MV模式,无不必要的视图模型
- 视图模型类添加了
@Observable隔离@MainActor - 跨并发边界传递的模型类型遵循协议
Sendable