swiftui-expert-skill - layout-best-practices

📅 发布时间:2026/10/13 9:59:39
swiftui-expert-skill - layout-best-practices
SwiftUI 布局最佳实践参考目录相对布局优于常量上下文无关的视图拥有你的容器布局性能视图逻辑与可测试性全宽视图动作处理器汇总清单相对布局优于常量使用动态布局计算而不是硬编码值。// 好 - 相对于实际布局GeometryReader{geometryinVStack{HeaderView().frame(height:geometry.size.height*0.2)ContentView()}}// 避免 - 不具适应性的魔法数字VStack{HeaderView().frame(height:150)// 不适用于不同屏幕ContentView()}原因硬编码值没有考虑不同的屏幕尺寸、方向或动态内容如通话期间的状态栏。上下文无关的视图视图应该在任何上下文中都能工作。绝不要假设呈现样式或屏幕尺寸。// 好 - 适应给定的空间structProfileCard:View{letuser:Uservarbody:someView{VStack{Image(user.avatar).resizable().aspectRatio(contentMode:.fit)Text(user.name)Spacer()}.padding()}}// 避免 - 假设全屏structProfileCard:View{letuser:Uservarbody:someView{VStack{Image(user.avatar).frame(width:UIScreen.main.bounds.width)// 错误Text(user.name)}}}原因视图应该可以作为全屏、模态、sheet、popover 或嵌入内容工作。拥有你的容器自定义视图应该拥有静态容器但不拥有惰性/可重复的容器。// 好 - 拥有静态容器structHeaderView:View{varbody:someView{HStack{Image(systemName:star)Text(Title)Spacer()}}}// 避免 - 缺少容器structHeaderView:View{varbody:someView{Image(systemName:star)Text(Title)// 调用方必须包裹在 HStack 中}}// 好 - 调用方拥有惰性容器structFeedView:View{letitems:[Item]varbody:someView{LazyVStack{ForEach(items){iteminItemRow(item:item)}}}}布局性能避免布局抖动Layout Thrash最小化深层视图层级和过度的布局依赖。// 坏 - 深层嵌套、过多的布局传递VStack{HStack{VStack{HStack{VStack{Text(Deep)}}}}}// 好 - 更扁平的结构VStack{Text(Shallow)Text(Structure)}避免过度的GeometryReader和偏好链// 坏 - 多个 geometry reader 导致布局抖动GeometryReader{outerGeometryinVStack{GeometryReader{innerGeometryin// 布局会重新计算多次}}}// 好 - 单个 geometry reader 或使用替代方案iOS 17containerRelativeFrame(.horizontal){width,_inwidth*0.8}对频繁的几何更新进行门控// 坏 - 每个像素变化都更新.onPreferenceChange(ViewSizeKey.self){sizeincurrentSizesize}// 好 - 用阈值门控.onPreferenceChange(ViewSizeKey.self){sizeinletdifferenceabs(size.width-currentSize.width)ifdifference10{// 仅在显著变化时更新currentSizesize}}视图逻辑与可测试性将业务逻辑保留在服务和模型中业务逻辑属于服务和模型而不是视图。视图应该保持简单和声明式——编排 UI 状态而不是实现业务规则。这使得逻辑无需实例化视图即可独立测试。iOS 17使用带State的Observable。ObservablefinalclassAuthService{varemailvarpasswordvarisValid:Bool{!email.isEmptypassword.count8}funclogin()asyncthrows{// 业务逻辑在这里 —— 无需视图即可测试}}structLoginView:View{StateprivatevarauthServiceAuthService()varbody:someView{Form{TextField(Email,text:$authService.email)SecureField(Password,text:$authService.password)Button(Login){Task{try?awaitauthService.login()}}.disabled(!authService.isValid)}}}对于 iOS 16 及更早版本使用带StateObject的ObservableObject——传统模式请参阅state-management.md。避免将业务逻辑直接嵌入视图闭包中例如在Button动作内进行验证检查。这会使逻辑在未实例化视图的情况下无法测试。注意这里关注的是让业务逻辑可测试而不是强制某种特定架构。关键是逻辑应存在于视图之外以便能够独立测试。全宽视图当单个视图需要填满可用宽度时使用.frame(maxWidth: .infinity, alignment:)而不是将其包裹在带Spacer的堆栈中。// 好 - frame 修饰符Text(Hello).frame(maxWidth:.infinity,alignment:.leading)// 避免 - 不必要的堆栈和 spacerHStack{Text(Hello)Spacer()}原因.frame(maxWidth:alignment:)是单个修饰符能清晰地传达意图。用带Spacer的HStack包裹会为视图层级添加一个多余的容器没有任何好处。动作处理器将布局与逻辑分离。视图 body 应该引用动作方法而不是包含内联逻辑。// 好 - 动作引用方法Button(Publish Project,action:publishService.handlePublish)// 避免 - 闭包中的多行逻辑Button(Publish Project){isLoadingtrueapiService.publish(project){resultin/* ... */}}汇总清单使用相对布局而不是硬编码常量视图在任何上下文中都能工作不假设屏幕尺寸自定义视图拥有静态容器避免深层视图层级布局抖动用阈值门控频繁的几何更新业务逻辑保留在服务和模型中不在视图中动作处理器引用方法而不是内联逻辑全宽视图使用.frame(maxWidth: .infinity, alignment:)而不是HStackSpacer避免过度使用GeometryReader在合适时使用containerRelativeFrame()