SwiftUI中如何定义泛型KeyPath?用泛型简化同参异构类型代码
用泛型简化SwiftUI代码并实现泛型KeyPath
1. 抽象数据模型(消除重复类型代码)
因为MyCustomType1和MyCustomType2结构完全一致,先定义一个协议统一它们的公共属性,让两个类型遵守这个协议:
// 定义公共协议,包含两个类型共有的属性 protocol HasSize { var label: String { get } var size: CGSize { get } } // 让原有类型遵守协议(无需修改原有属性) struct MyCustomType1: HasSize { var label: String var size: CGSize = .zero } struct MyCustomType2: HasSize { var label: String var size: CGSize = .zero }
2. 实现泛型View(合并重复的View代码)
把MyView1和MyView2合并成一个泛型View,同时支持泛型KeyPath和不同形状的显示,提供两种方案:
方案一:基于Shape协议的泛型(更灵活)
利用SwiftUI的Shape协议,让View接受任意形状类型,同时约束数据类型遵守HasSize:
struct ShapeDisplayView<T: HasSize, S: Shape>: View { // 泛型数据模型,必须遵守HasSize协议 let value: T // 泛型KeyPath:从T类型映射到CGFloat let sizeKeyPath: KeyPath<T, CGFloat> // 要显示的形状,必须符合Shape协议 let shape: S var body: some View { // 通过泛型KeyPath获取对应的值 let targetSize = value[keyPath: sizeKeyPath] shape .fill(Color.black) .frame(width: targetSize, height: targetSize) .overlay(Text("\(targetSize)").foregroundColor(.white)) } }
方案二:用枚举指定形状(更简洁)
如果只需要固定几种形状,用枚举替代泛型Shape参数:
enum ShapeType { case circle, rectangle } struct ShapeDisplayView<T: HasSize>: View { let value: T let sizeKeyPath: KeyPath<T, CGFloat> let shapeType: ShapeType var body: some View { let targetSize = value[keyPath: sizeKeyPath] Group { switch shapeType { case .circle: Circle() case .rectangle: Rectangle() } } .fill(Color.black) .frame(width: targetSize, height: targetSize) .overlay(Text("\(targetSize)").foregroundColor(.white)) } }
3. 在ContentView中使用泛型View和KeyPath
现在可以用统一的泛型View替代原来的两个View,直接传入不同的数据、KeyPath和形状:
对应方案一的ContentView:
struct ContentView: View { let value1 = MyCustomType1(label: "Circle", size: CGSize(width: 100, height: 200)) let value2 = MyCustomType2(label: "Rectangle", size: CGSize(width: 100, height: 200)) var body: some View { VStack(spacing: 20) { // 传入Circle形状、MyCustomType1实例和对应的KeyPath ShapeDisplayView( value: value1, sizeKeyPath: \.size.height, shape: Circle() ) // 传入Rectangle形状、MyCustomType2实例和对应的KeyPath ShapeDisplayView( value: value2, sizeKeyPath: \.size.width, // 也可以换用width的KeyPath shape: Rectangle() ) } .padding() } }
对应方案二的ContentView:
struct ContentView: View { let value1 = MyCustomType1(label: "Circle", size: CGSize(width: 100, height: 200)) let value2 = MyCustomType2(label: "Rectangle", size: CGSize(width: 100, height: 200)) var body: some View { VStack(spacing: 20) { ShapeDisplayView( value: value1, sizeKeyPath: \.size.height, shapeType: .circle ) ShapeDisplayView( value: value2, sizeKeyPath: \.size.height, shapeType: .rectangle ) } .padding() } }
关于SwiftUI中泛型KeyPath的说明
在泛型View中定义泛型KeyPath的核心要点:
- 给KeyPath的源类型加上泛型约束(比如
T: HasSize),确保编译器知道该类型拥有KeyPath指向的属性 - 定义KeyPath的类型为
KeyPath<T, ValueType>,其中T是泛型参数,ValueType是你要获取的值的类型(这里是CGFloat) - 使用时直接传入形如
\.size.height的KeyPath即可,编译器会自动推断对应的泛型类型
这样既消除了重复的类型和View代码,又实现了灵活的泛型KeyPath使用。
内容的提问来源于stack exchange,提问作者swiftPunk
相关产品推荐
相关产品推荐

