swift - 实现自定义 ViewModifier,其中输出以具体 View 类型为条件 (SwiftUI)

标签 swift types swiftui viewmodifier

我想创建一个 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 中,尝试更好地了解发生这种情况的原因,并立即收到编译器警告,提示类型检查始终会失败(屏幕截图中的修饰符名称不同 - 请忽略):

Xcode 编译器错误: Errors

这解释了为什么代码没有按预期执行,但我无法确定我是否犯了错误,或者是否(实际上)无法检查发送到 ViewModifier 的 View 的具体类型。据我所知,发送到 ViewModifier 的内容参数似乎确实被类型删除了(基于 Xcode 中可访问的方法),但似乎确实有一种在这种情况下获取类型信息的方法,因为某些修饰符(例如 .focused() )仅对某些类型的 View (特别是交互式文本控件)进行操作并忽略其他类型。这当然可能是我们无法访问的私有(private) API(还...?​​)

有什么指导/解释吗?

最佳答案

你是对的,该实现中存在一些代码味道,首先是您需要编写类型检查来实现目标。每当您开始将 isas? 与具体类型一起编写时,您应该考虑抽象为协议(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>())
    }
}

上述实现具有编译时间优势,因为仅允许使用自定义修饰符修改 TextTextField。如果开发人员尝试在不接受的 View 类型上应用修饰符,他们将得到一个很好的实例方法“customModifier()”要求“MyView”符合“CustomModabilible”,IMO比欺骗修改器的行为(即对某些 View 不执行任何操作)要好。

如果您将来需要支持更多 View ,只需添加符合您的协议(protocol)的扩展即可。

关于swift - 实现自定义 ViewModifier,其中输出以具体 View 类型为条件 (SwiftUI),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71988019/

相关文章:

ios - 更改选项卡时 Web View 会使选项卡式应用程序崩溃?

swift - CollectionView 单元格 困惑

swift - 为 Siri 创建监听器

python - 简单的基本 Python 比较

ios - 如何更改 SwiftUI 中导航栏的背景颜色?

ios - 出现键盘时快速移动 View

json - 如何在 Flutter 中转换 json 空数组?

c++ - 错误 "xxxx"没有命名类型

swiftui - 获得阻力速度的最佳方法是什么?

ios - SwiftUI MapKit UIViewRepresentable 无法显示警报