如何编写可影响特定类型所有嵌套视图的Style视图修饰符?
实现类似SwiftUI内置样式修饰符的自定义视图样式系统
需求说明
我想实现类似SwiftUI内置buttonStyle、labelStyle的视图修饰符,让它能作用于所有嵌套层级的特定自定义视图。比如SwiftUI原生用法:
VStack { VStack { VStack { Button("OK") {} ... } ... } ... } .buttonStyle(.bordered) // 该修饰符会改变所有深层嵌套按钮的样式
我的目标是给自定义视图TwinTexts实现同样的效果:
VStack { VStack { VStack { TwinTexts(text1: "Foo", text2: "Bar") ... } ... } ... } .twinTextsStyle(CustomStyle()) // 修改所有嵌套的TwinTexts样式
样式协议设计思路
我希望CustomStyle能像ButtonStyle那样,通过makeBody方法接收包含两个视图的配置来实现:
struct TwinTextsStyleConfiguration { // 不确定此处应使用什么类型,AnyView? let text1: Text1 let text2: Text2 } protocol TwinTextsStyle { associatedtype Body: View @ViewBuilder func makeBody(configuration: TwinTextsStyleConfiguration) -> Body } struct CustomStyle: TwinTextsStyle { func makeBody(configuration: TwinTextsStyleConfiguration) -> some View { // 示例:将两个文本上下排列 VStack { configuration.text1 configuration.text2 } } }
TwinTexts视图的预期用法:
struct TwinTexts: View { let text1: String let text2: String var body: some View { // 注意:这些不是原生Text类型,会添加lineLimit等修饰符 let text1 = Text(text1).lineLimit(1) let text2 = Text(text2).lineLimit(1) // 不确定如何获取当前样式 style.makeBody(.init(text1: text1, text2: text2)) } }
尝试方案与问题
我想到用自定义Environment来实现,因为.environment会作用于所有嵌套视图,但遇到两个问题:
问题1:style.makeBody返回any View无法在body中使用
我的实现代码:
struct TwinTextsStyleKey: EnvironmentKey { static let defaultValue: any TwinTextsStyle = DefaultStyle() } extension EnvironmentValues { var twinTextsStyle: any TwinTextsStyle { get { self[TwinTextsStyleKey.self] } set { self[TwinTextsStyleKey.self] = newValue } } } extension View { func twinTextsStyle(_ style: some TwinTextsStyle) -> some View { self.environment(\.twinTextsStyle, style) } } struct TwinTexts: View { let text1: String let text2: String @Environment(\.twinTextsStyle) var style var body: some View { let text1 = Text(text1).lineLimit(1) let text2 = Text(text2).lineLimit(1) style.makeBody(.init(text1: text1, text2: text2)) // 返回any View,无法直接用在body中 } }
问题2:TwinTextsStyleConfiguration中的视图类型选择
参考SwiftUI的ButtonStyleConfiguration.Label,它是“类型擦除”的视图,这是不是AnyView的适用场景?不确定配置里的text1和text2应该用什么类型。
内容的提问来源于stack exchange,提问作者Sweeper
相关产品推荐
相关产品推荐

