我基本上试图创建类似于内置的buttonStyle
,labelStyle
等修饰符的东西。它们会影响所有嵌套的按钮/标签,无论它们的嵌套有多深。例如
VStack {
VStack {
VStack {
Button("OK") {}
...
}
...
}
...
}
.buttonStyle(.bordered) // this changes the style of the button, even if it is deeply nested
我的目标是对我的自定义视图执行相同的操作。我们称之为TwinTextsView
:
VStack {
VStack {
VStack {
TwinTexts(text1: "Foo", text2: "Bar")
...
}
...
}
...
}
.twinTextsStyle(CustomStyle()) // this changes the style of TwinTexts, even if it is deeply nested
其中CustomStyle
可以像ButtonStyle
一样实现,makeBody
方法采用具有两个视图的配置,表示文本
struct TwinTextsStyleConfiguration {
// not sure what types these should be. 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 {
// let's suppose I want to put one text on top of the other.
VStack {
configuration.text1
configuration.text2
}
}
}
然后TwinTexts
将使用此样式,如下所示:
struct TwinTexts: View {
let text1: String
let text2: String
var body: some View {
// note that these are not of type "Text". I don't know what type they are as I will be adding view modifiers to them
// here I am using lineLimit as an example
let text1 = Text(text1).lineLimit(1)
let text2 = Text(text2).lineLimit(1)
// not sure how I would get the "style" here
style.makeBody(.init(text1: text1, text2: text2))
}
}
我想到的一个想法是使用自定义的Environment
,因为.environment
适用于所有嵌套视图。存在两个问题:
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)) // this returns "any View"
}
}
- 我不知道
TwinTextsStyleConfiguration
中的视图应该使用什么类型。
我看了ButtonStyleConfiguration.Label
,根据文档,这是一个“类型擦除”视图。这是AnyView
的实际用例吗?
2条答案
按热度按时间6ioyuze21#
我已经实现了几种样式和视图,可以使用与内置视图类似的API进行样式化,它们都遵循类似的模式。正如Benzy Neez发现的那样,您可以很快地陷入泛型的混乱之中。
您完全正确,因为这是
AnyView
的完美用例。在编译时不可能知道:据我所知,您在内置配置类型中看到的类型擦除视图在功能上与
AnyView
相同。创建样式和可设置样式的视图的步骤如下:
1.定义表示样式的协议:
这在任何地方都是一样的,只是把XX改成了你的真名。
2.定义配置类型
此类型应包含样式实现需要呈现的所有内容。如果你用视图构建器传入东西,你应该将它们存储为
AnyView
。如果你有字符串之类的东西,把它们存储为字符串。3.环境和视图修改器杂务
样式存在于环境中,因为这就是如何将修改器应用于容器一次,以便该容器中的所有视图都可以检测到。为你的样式创建一个键,默认值为你的样式协议的自动或内置实现:
添加视图修改器以自动应用此修改,这样您就可以像
.buttonStyle()
一样使用它。4.视图实现
按如下方式定义视图:
传入构成实现的数据和/或视图,并将它们存储在init时创建的配置中。然后从环境中获取样式,在编译时是
any
样式,从中获取主体,然后将其 Package 在AnyView
中,这样类型系统就不会爆炸。这是一个相当复杂的过程,但它确实有效,我对在这里使用
AnyView
并不感到不好,因为这正是像Button
这样的内置视图所做的,即使您没有应用样式。njthzxwz2#
在你的问题中,你给出了
.buttonStyle
和.labelStyle
作为修饰符的例子,这些修饰符可以应用在父级,并影响较低级别的视图。在这两种情况下,样式都适用于非常特定的视图类型,即Button
和Label
。在设计模式的世界里,我将这些风格描述为装饰器。值得注意的是,目标视图的类型在被修饰后不会改变,它们仍然是Button
和Label
。另一方面,我会说你在大纲中给出的代码更像是一个Builder的例子,因为它试图处理通用内容。我在想也许可以通过使用泛型来让它工作,但是当我尝试它时,我被
some View
的处理所束缚,甚至无法让它编译。在与协议和泛型的斗争中筋疲力尽,我后退了一步,意识到如果你完全控制代码库,那么就有一个更简单的方法来解决这个问题。这是将不同风格的所有知识放入
TwinTexts
中,并在它们之间切换。这使得TwinTexts
成为构建器。如果不同的样式很复杂,那么TwinTexts
可以委托给特定的实现,但切换逻辑将保留在TwinTexts
中。因此,识别不同样式所需的全部内容是enum
:正如您所看到的,您最初的要求,即能够在较高级别应用修改器以控制较低级别的样式设置,已经得到满足。但是,这种方法不能给予您无法控制生成器时添加新的自定义样式。