SwiftUI 组件封装进阶之路:组合、泛型、状态驱动与主题化

张开发
2026/4/10 9:53:37 15 分钟阅读

分享文章

SwiftUI 组件封装进阶之路:组合、泛型、状态驱动与主题化
SwiftUI 自定义组件可以从单文件组合一路进阶到完整设计系统。下面用 5 个层次展示从简单到复杂的演进每个层次都配完整代码。层次 1简单组合组件单一职责最基本的封装复用样式和基础逻辑。// 简单带图标的标签structIconTag:View{lettext:Stringleticon:Stringvarcolor:Color.bluevarbody:someView{Label(text,systemImage:icon).font(.caption).padding(.horizontal,12).padding(.vertical,6).background(color.opacity(0.2)).foregroundColor(color).cornerRadius(20)}}// 使用IconTag(text:SwiftUI,icon:swift,color:.orange)层次 2可配置组件暴露控制点通过参数控制外观和行为提供默认值。// 可配置评分星星structRatingStars:View{letrating:Int// 1-5letmaxRating:Int5varstarSize:CGFloat20varactiveColor:Color.yellowvarinactiveColor:Color.grayvaronRatingChanged:((Int)-Void)?varbody:someView{HStack(spacing:4){ForEach(1...maxRating,id:\.self){indexinImage(systemName:indexrating?star.fill:star).font(.system(size:starSize)).foregroundColor(indexrating?activeColor:inactiveColor).onTapGesture{onRatingChanged?(index)}}}}}// 使用RatingStars(rating:3,starSize:24){newRatinginprint(新评分:\(newRating))}层次 3泛型 ViewBuilder内容注入让调用方决定内部布局实现高度复用如弹窗、卡片。// 高级通用弹窗组件structDialogContent:View:View{lettitle:Stringletcontent:ContentletconfirmText:StringletcancelText:String?letonConfirm:()-VoidletonCancel:(()-Void)?BindingvarisPresented:Boolinit(title:String,isPresented:BindingBool,confirmText:String确定,cancelText:String?取消,onConfirm:escaping()-Void,onCancel:(()-Void)?nil,ViewBuildercontent:()-Content){self.titletitleself._isPresentedisPresentedself.confirmTextconfirmTextself.cancelTextcancelTextself.onConfirmonConfirmself.onCancelonCancelself.contentcontent()}varbody:someView{ifisPresented{ZStack{// 背景遮罩Color.black.opacity(0.4).ignoresSafeArea().onTapGesture{isPresentedfalseonCancel?()}// 弹窗内容VStack(spacing:20){Text(title).font(.headline)content.padding(.horizontal)HStack(spacing:12){ifletcancelText{Button(cancelText){isPresentedfalseonCancel?()}.buttonStyle(.bordered)}Button(confirmText){onConfirm()isPresentedfalse}.buttonStyle(.borderedProminent)}}.padding().background(Color(.systemBackground)).cornerRadius(16).padding(.horizontal,40)}}}}// 使用structContentView:View{StateprivatevarshowDialogfalsevarbody:someView{Button(显示弹窗){showDialogtrue}.dialog(title:确认删除,isPresented:$showDialog){Text(此操作不可撤销确定要删除吗).foregroundColor(.red)}onConfirm:{print(执行删除)}}}// 扩展提供便捷方法extensionView{funcdialogContent:View(title:String,isPresented:BindingBool,confirmText:String确定,cancelText:String?取消,onConfirm:escaping()-Void,onCancel:(()-Void)?nil,ViewBuildercontent:escaping()-Content)-someView{self.overlay(Dialog(title:title,isPresented:isPresented,confirmText:confirmText,cancelText:cancelText,onConfirm:onConfirm,onCancel:onCancel,content:content))}}层次 4状态驱动 动画响应式交互内部维护状态提供流畅动画。// 高级可展开/折叠的详情卡片structExpandableCardHeader:View,Detail:View:View{letheader:Headerletdetail:DetailStateprivatevarisExpandedfalsevaranimation:Animation.spring(response:0.4,dampingFraction:0.8)init(ViewBuilderheader:()-Header,ViewBuilderdetail:()-Detail){self.headerheader()self.detaildetail()}varbody:someView{VStack(alignment:.leading,spacing:0){// 头部始终显示header.contentShape(Rectangle()).onTapGesture{withAnimation(animation){isExpanded.toggle()}}.overlay(alignment:.trailing){Image(systemName:chevron.right).rotationEffect(.degrees(isExpanded?90:0)).padding(.trailing)}// 详情条件显示ifisExpanded{detail.padding(.top,12).transition(.move(edge:.top).combined(with:.opacity))}}.padding().background(Color(.secondarySystemBackground)).cornerRadius(12)}}// 使用ExpandableCard{HStack{Image(systemName:info.circle)Text(高级设置).font(.headline)}}detail:{VStack(alignment:.leading,spacing:8){Toggle(启用通知,isOn:.constant(true))Toggle(夜间模式,isOn:.constant(false))Text(更多选项...).font(.caption).foregroundColor(.gray)}}层次 5完整设计系统可复用组件库企业级封装包含主题、预设样式、组合能力。// 1. 定义设计令牌Design TokensstructDesignTokens{// 间距structSpacing{staticletxs:CGFloat4staticletsm:CGFloat8staticletmd:CGFloat16staticletlg:CGFloat24staticletxl:CGFloat32}// 圆角structRadius{staticletsm:CGFloat4staticletmd:CGFloat8staticletlg:CGFloat12staticletxl:CGFloat16staticletround:CGFloat999}// 字体structTypography{staticletcaptionFont.system(size:12,weight:.regular)staticletbodyFont.system(size:16,weight:.regular)staticletheadlineFont.system(size:18,weight:.semibold)staticlettitleFont.system(size:24,weight:.bold)}}// 2. 主题协议protocolTheme{varprimary:Color{get}varsecondary:Color{get}varbackground:Color{get}vartext:Color{get}}structLightTheme:Theme{letprimaryColor.blueletsecondaryColor.grayletbackgroundColor.whitelettextColor.black}structDarkTheme:Theme{letprimaryColor.orangeletsecondaryColor.gray.opacity(0.8)letbackgroundColor.blacklettextColor.white}// 3. 环境值传递主题structThemeKey:EnvironmentKey{staticletdefaultValue:ThemeLightTheme()}extensionEnvironmentValues{vartheme:Theme{get{self[ThemeKey.self]}set{self[ThemeKey.self]newValue}}}// 4. 构建按钮组件族structPrimaryButton:View{lettitle:Stringletaction:()-VoidvarisFullWidth:BoolfalseEnvironment(\.theme)varthemeEnvironment(\.isEnabled)varisEnabledvarbody:someView{Button(action:action){Text(title).font(DesignTokens.Typography.body).frame(maxWidth:isFullWidth?.infinity:nil).padding(.horizontal,DesignTokens.Spacing.lg).padding(.vertical,DesignTokens.Spacing.md).background(isEnabled?theme.primary:theme.primary.opacity(0.5)).foregroundColor(.white).cornerRadius(DesignTokens.Radius.md)}.disabled(!isEnabled)}}structSecondaryButton:View{lettitle:Stringletaction:()-VoidEnvironment(\.theme)varthemevarbody:someView{Button(action:action){Text(title).font(DesignTokens.Typography.body).padding(.horizontal,DesignTokens.Spacing.lg).padding(.vertical,DesignTokens.Spacing.md).background(Color.clear).foregroundColor(theme.primary).overlay(RoundedRectangle(cornerRadius:DesignTokens.Radius.md).stroke(theme.primary,lineWidth:1))}}}// 5. 组件变体使用 ViewModifierenumButtonStyle{caseprimary,secondary,destructive}structStyledButton:ViewModifier{letstyle:ButtonStyleletisFullWidth:BoolEnvironment(\.theme)varthemefuncbody(content:Content)-someView{switchstyle{case.primary:content.font(DesignTokens.Typography.body).frame(maxWidth:isFullWidth?.infinity:nil).padding(.horizontal,DesignTokens.Spacing.lg).padding(.vertical,DesignTokens.Spacing.md).background(theme.primary).foregroundColor(.white).cornerRadius(DesignTokens.Radius.md)case.secondary:content.padding(.horizontal,DesignTokens.Spacing.lg).padding(.vertical,DesignTokens.Spacing.md).background(Color.clear).foregroundColor(theme.primary).overlay(RoundedRectangle(cornerRadius:DesignTokens.Radius.md).stroke(theme.primary,lineWidth:1))case.destructive:content.padding(.horizontal,DesignTokens.Spacing.lg).padding(.vertical,DesignTokens.Spacing.md).background(Color.red).foregroundColor(.white).cornerRadius(DesignTokens.Radius.md)}}}extensionView{funcbuttonStyle(_style:ButtonStyle,isFullWidth:Boolfalse)-someView{self.modifier(StyledButton(style:style,isFullWidth:isFullWidth))}}// 6. 使用示例structDesignSystemDemo:View{StateprivatevarisDarkModefalseStateprivatevarisButtonEnabledtruevarbody:someView{VStack(spacing:DesignTokens.Spacing.lg){Toggle(暗色模式,isOn:$isDarkMode)Toggle(启用按钮,isOn:$isButtonEnabled)Group{PrimaryButton(title:主要按钮,action:{}){$0.isFullWidthtrue}SecondaryButton(title:次要按钮,action:{})Text(变体方式).buttonStyle(.primary,isFullWidth:true).onTapGesture{print(点击)}Text(删除).buttonStyle(.destructive).onTapGesture{print(删除)}}}.padding().environment(\.theme,isDarkMode?DarkTheme():LightTheme())}}选型建议复杂度 适用场景 示例层次1-2 小型项目、原型、重复出现的UI片段 标签、评分、开关按钮层次3 中型项目、需要灵活内容的组件 弹窗、卡片、菜单层次4 需要丰富交互的组件 折叠面板、轮播图、下拉刷新层次5 大型项目、团队协作、设计系统 统一按钮库、主题切换进阶方向当你需要完全自定义布局时如瀑布流、弧形菜单可以学习 Layout 协议需要自定义转场效果时学习 Transition 协议。

更多文章