我想创建一个 ViewModifier,其中输出取决于它正在修改的内容类型。
我管理的概念的最佳测试(使用 Text 和 TextField 作为示例 View 类型)如下:
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
}
}
上述修饰符的问题是,当您使用修饰符时,您需要显式提供通用术语,因此似乎不正确(并且在我看来,有代码味道),因为您需要在父 View 上定义通用术语然后是其父级的泛型,依此类推。
例如
struct ContentView<T: View>: View {
var body: some View {
VStack {
Text("Hello world")
.modifier(CustomModifier<Text>())
TextField("Textfield", text: .constant(""))
.modifier(CustomModifier<TextField<T>>())
}
}
}
我在 View 上使用此扩展成功解决了这个问题(在 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 View 应具有不同的背景(分别为红色和蓝色),但它们显示不变。覆盖修饰符中的 View 类型检查(将类型检查硬编码为“true”)会导致背景颜色发生变化,从而应用修饰符;这是类型检查失败的原因。
我将代码拖到 Xcode 中,尝试更好地了解发生这种情况的原因,并立即收到编译器警告,提示类型检查始终会失败(屏幕截图中的修饰符名称不同 - 请忽略):
这解释了为什么代码没有按预期执行,但我无法确定我是否犯了错误,或者是否(实际上)无法检查发送到 ViewModifier 的 View 的具体类型。据我所知,发送到 ViewModifier 的内容参数似乎确实被类型删除了(基于 Xcode 中可访问的方法),但似乎确实有一种在这种情况下获取类型信息的方法,因为某些修饰符(例如 .focused() )仅对某些类型的 View (特别是交互式文本控件)进行操作并忽略其他类型。这当然可能是我们无法访问的私有(private) API(还...?)
有什么指导/解释吗?
最佳答案
你是对的,该实现中存在一些代码味道,首先是您需要编写类型检查来实现目标。每当您开始将 is
或 as?
与具体类型一起编写时,您应该考虑抽象为协议(protocol)。
就您而言,您需要一个抽象来为您提供背景颜色,因此一个简单的协议(protocol)如下:
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
是某种内部类型包装原始 View 的 SwiftUI。这意味着您无法(轻松)访问修改器所应用到的实际 View 。
这就是为什么 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
。如果开发人员尝试在不接受的 View 类型上应用修饰符,他们将得到一个很好的实例方法“customModifier()”要求“MyView”符合“CustomModabilible”
,IMO比欺骗修改器的行为(即对某些 View 不执行任何操作)要好。
如果您将来需要支持更多 View ,只需添加符合您的协议(protocol)的扩展即可。
关于swift - 实现自定义 ViewModifier,其中输出以具体 View 类型为条件 (SwiftUI),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71988019/