SwiftUI封装集合Toggle时如何正确构建KeyPath?
我尝试构建一个封装了Toggle的SwiftUI View,该Toggle访问集合以呈现混合状态,但在构建正确的KeyPath时遇到困难。
编写的视图代码如下:
struct CornersView<C>: View where C: RandomAccessCollection { @Binding var sources: C let keyPath: KeyPath<C.Element, Binding<ApertureCorners>> var body: some View { Toggle(sources: sources, isOn: keyPath.appending(path: \.upperLeftActive), label: {}) } } #Preview { @State var sources = [ApertureCornerTreatment(radius: 1, corners: [], chamfered: false)] return CornersView(sources: $sources, keyPath: \.corners) }
预览的最后一行出现如下错误:
Key path value type 'ApertureCorners' cannot be converted to contextual type 'Binding<ApertureCorners>'
奇怪的是,如果我想在预览中为chamfered创建Toggle,只需编写:
Toggle(sources: $sources, isOn: \.chamfered, label: {})
这与当前预览的写法非常相似,请问我哪里出错了?
问题原因与修复方案
核心问题
你错误地将keyPath的类型定义为KeyPath<C.Element, Binding<ApertureCorners>>,但实际传入的\.corners是KeyPath<ApertureCornerTreatment, ApertureCorners>——它指向的是普通属性而非Binding类型,因此类型不匹配导致报错。同时,你传给Toggle的sources参数是原始集合(非Binding类型),而系统的集合型Toggle需要接收Binding<Collection>类型的参数才能自动处理元素绑定逻辑。
修复后的代码
调整CornersView的keyPath类型为普通属性的KeyPath,并在Toggle中传入$sources(Binding投影):
struct CornersView<C>: View where C: RandomAccessCollection { @Binding var sources: C // 改为普通属性的KeyPath,而非返回Binding的类型 let keyPath: KeyPath<C.Element, ApertureCorners> var body: some View { // 传入$sources作为Binding<Collection>,并组合KeyPath到目标Bool属性 Toggle(sources: $sources, isOn: keyPath.appending(path: \.upperLeftActive), label: {}) } } #Preview { @State var sources = [ApertureCornerTreatment(radius: 1, corners: [], chamfered: false)] return CornersView(sources: $sources, keyPath: \.corners) }
为什么直接用\.chamfered可以?
因为\.chamfered是KeyPath<ApertureCornerTreatment, Bool>,直接匹配集合型Toggle的isOn参数要求;同时你传入了$sources(Binding<[ApertureCornerTreatment]>),SwiftUI会自动为集合中的每个元素生成对应的Binding<Bool>,因此可以正常工作。
内容的提问来源于stack exchange,提问作者Remco Poelstra

