You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写可影响特定类型所有嵌套视图的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 01:30:01