实现自定义 ViewModifier,其中输出以具体视图类型为条件 (SwiftUI)
Implementing a custom ViewModifier where output is conditional on concrete View type (SwiftUI)
我想创建一个 ViewModifier,其中的输出取决于它正在修改的内容类型。
我管理的概念的最佳测试(使用 Text 和 TextField 作为示例视图类型)如下:
struct CustomModifier<T: View>: ViewModifier {
@ViewBuilder func body(content: Content) -> some View {
if content is Text.Type {
content.background(Color.red)
} else {
if content is TextField<T>.Type {
content.background(Color.blue)
}
}
content
}
}
上述修饰符的问题在于,当您使用修饰符时,您需要明确提供通用术语,因此看起来不正确(而且,在我看来,代码有味道),因为您随后需要在父视图,然后是其父视图的泛型,等等。
例如
struct ContentView<T: View>: View {
var body: some View {
VStack {
Text("Hello world")
.modifier(CustomModifier<Text>())
TextField("Textfield", text: .constant(""))
.modifier(CustomModifier<TextField<T>>())
}
}
}
我设法在视图上使用此扩展解决了这个问题(在 Cristik 的一些指导下):
extension View {
func customModifier() -> some View where Self:View {
modifier(CustomModifier<Self>())
}
}
使用 iPadOS Playgrounds 使用以下方法测试修改器:
struct ContentView: View {
var body: some View {
Form {
Text("Hello")
.customModifier()
TextField("Text", text: .constant(""))
.customModifier()
}
}
}
这可以编译并运行,但输出不是我预期的。 Text 和 TextField 视图应该有不同的背景(分别为红色和蓝色),但它们显示不变。在修改器中覆盖视图类型检查(将类型检查硬编码为 'true')会导致背景颜色发生变化,因此正在应用修改器;是类型检查失败了。
我将代码拖入 Xcode 以尝试更好地了解为什么会发生这种情况,并立即收到编译器警告,提示类型检查总是失败(屏幕截图中的修饰符名称不同) - 请无视):
Xcode 编译器错误:
这解释了为什么代码没有按预期执行,但我无法确定我是否犯了错误,或者是否(实际上)没有办法检查发送到视图修改器。据我所知,发送到 ViewModifier 的内容参数似乎确实被类型擦除(基于 Xcode 中可访问的方法),但在这种情况下似乎确实有一种获取类型信息的方法,因为某些修饰符(例如.focused()) 仅对某些类型的视图(特别是交互式文本控件)进行操作,而忽略其他类型。这当然可以是我们无法访问的私有 API(还...?)
任何指导/解释?
你是对的,该实现中有一些代码味道,首先是你需要编写类型检查来实现目标。每当您开始编写 is
或 as?
以及具体类型时,您应该考虑抽象为协议。
在你的情况下,你需要一个抽象来给你背景颜色,所以一个简单的协议如:
protocol CustomModifiable: View {
var customProp: Color { get }
}
extension Text: CustomModifiable {
var customProp: Color { .red }
}
extension TextField: CustomModifiable {
var customProp: Color { .blue }
}
,应该是要走的路,修饰符应该可以简化为:
struct CustomModifier: ViewModifier {
@ViewBuilder func body(content: Content) -> some View {
if let customModifiable = content as? CustomModifiable {
content.background(customModifiable.customProp)
} else {
content
}
}
}
问题是这种惯用的方法不适用于 SwiftUI 修饰符,因为作为 body()
函数的参数接收的 content
是包装原始视图的某种内部类型的 SwiftUI .这意味着您不能(轻松地)访问应用修饰符的实际视图。
这就是 is
检查总是失败的原因,正如编译器所说的那样。
然而,并不是所有的都丢失了,因为我们可以通过静态属性和泛型来解决这个限制。
protocol CustomModifiable: View {
static var customProp: Color { get }
}
extension Text: CustomModifiable {
static var customProp: Color { .red }
}
extension TextField: CustomModifiable {
static var customProp: Color { .blue }
}
struct CustomModifier<T: CustomModifiable>: ViewModifier {
@ViewBuilder func body(content: Content) -> some View {
content.background(T.customProp)
}
}
extension View {
func customModifier() -> some View where Self: CustomModifiable {
modifier(CustomModifier<Self>())
}
}
上述实现具有编译时优势,因为只有 Text
和 TextField
允许使用自定义修饰符进行修改。如果开发人员试图在 non-accepted 类型的视图上应用修饰符,they-ll 会得到一个不错的 Instance method 'customModifier()' requires that 'MyView' conform to 'CustomModifiable'
,IMO 这比欺骗修饰符的行为要好(即什么都不做)一些观点)。
如果您将来需要支持更多视图,只需添加符合您的协议的扩展即可。
我想创建一个 ViewModifier,其中的输出取决于它正在修改的内容类型。
我管理的概念的最佳测试(使用 Text 和 TextField 作为示例视图类型)如下:
struct CustomModifier<T: View>: ViewModifier {
@ViewBuilder func body(content: Content) -> some View {
if content is Text.Type {
content.background(Color.red)
} else {
if content is TextField<T>.Type {
content.background(Color.blue)
}
}
content
}
}
上述修饰符的问题在于,当您使用修饰符时,您需要明确提供通用术语,因此看起来不正确(而且,在我看来,代码有味道),因为您随后需要在父视图,然后是其父视图的泛型,等等。
例如
struct ContentView<T: View>: View {
var body: some View {
VStack {
Text("Hello world")
.modifier(CustomModifier<Text>())
TextField("Textfield", text: .constant(""))
.modifier(CustomModifier<TextField<T>>())
}
}
}
我设法在视图上使用此扩展解决了这个问题(在 Cristik 的一些指导下):
extension View {
func customModifier() -> some View where Self:View {
modifier(CustomModifier<Self>())
}
}
使用 iPadOS Playgrounds 使用以下方法测试修改器:
struct ContentView: View {
var body: some View {
Form {
Text("Hello")
.customModifier()
TextField("Text", text: .constant(""))
.customModifier()
}
}
}
这可以编译并运行,但输出不是我预期的。 Text 和 TextField 视图应该有不同的背景(分别为红色和蓝色),但它们显示不变。在修改器中覆盖视图类型检查(将类型检查硬编码为 'true')会导致背景颜色发生变化,因此正在应用修改器;是类型检查失败了。
我将代码拖入 Xcode 以尝试更好地了解为什么会发生这种情况,并立即收到编译器警告,提示类型检查总是失败(屏幕截图中的修饰符名称不同) - 请无视):
Xcode 编译器错误:
这解释了为什么代码没有按预期执行,但我无法确定我是否犯了错误,或者是否(实际上)没有办法检查发送到视图修改器。据我所知,发送到 ViewModifier 的内容参数似乎确实被类型擦除(基于 Xcode 中可访问的方法),但在这种情况下似乎确实有一种获取类型信息的方法,因为某些修饰符(例如.focused()) 仅对某些类型的视图(特别是交互式文本控件)进行操作,而忽略其他类型。这当然可以是我们无法访问的私有 API(还...?)
任何指导/解释?
你是对的,该实现中有一些代码味道,首先是你需要编写类型检查来实现目标。每当您开始编写 is
或 as?
以及具体类型时,您应该考虑抽象为协议。
在你的情况下,你需要一个抽象来给你背景颜色,所以一个简单的协议如:
protocol CustomModifiable: View {
var customProp: Color { get }
}
extension Text: CustomModifiable {
var customProp: Color { .red }
}
extension TextField: CustomModifiable {
var customProp: Color { .blue }
}
,应该是要走的路,修饰符应该可以简化为:
struct CustomModifier: ViewModifier {
@ViewBuilder func body(content: Content) -> some View {
if let customModifiable = content as? CustomModifiable {
content.background(customModifiable.customProp)
} else {
content
}
}
}
问题是这种惯用的方法不适用于 SwiftUI 修饰符,因为作为 body()
函数的参数接收的 content
是包装原始视图的某种内部类型的 SwiftUI .这意味着您不能(轻松地)访问应用修饰符的实际视图。
这就是 is
检查总是失败的原因,正如编译器所说的那样。
然而,并不是所有的都丢失了,因为我们可以通过静态属性和泛型来解决这个限制。
protocol CustomModifiable: View {
static var customProp: Color { get }
}
extension Text: CustomModifiable {
static var customProp: Color { .red }
}
extension TextField: CustomModifiable {
static var customProp: Color { .blue }
}
struct CustomModifier<T: CustomModifiable>: ViewModifier {
@ViewBuilder func body(content: Content) -> some View {
content.background(T.customProp)
}
}
extension View {
func customModifier() -> some View where Self: CustomModifiable {
modifier(CustomModifier<Self>())
}
}
上述实现具有编译时优势,因为只有 Text
和 TextField
允许使用自定义修饰符进行修改。如果开发人员试图在 non-accepted 类型的视图上应用修饰符,they-ll 会得到一个不错的 Instance method 'customModifier()' requires that 'MyView' conform to 'CustomModifiable'
,IMO 这比欺骗修饰符的行为要好(即什么都不做)一些观点)。
如果您将来需要支持更多视图,只需添加符合您的协议的扩展即可。