我正在尝试构建一个新的 SwiftUI
View
,它封装了 Toggle
。
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(sources: $sources, isOn: \.chamfered, label: {})
,看起来与当前预览非常相似。
我做错了什么?
以后请发布完整的示例。我已经插入了一些代码以便能够重现您的错误,但您应该将所有这些都包含在您的帖子中:
struct ApertureCorners: OptionSet {
var rawValue: UInt8
static let upperLeft = Self(rawValue: 1 << 0)
var upperLeftActive: Bool {
get { contains(.upperLeft) }
set {
if newValue { insert(.upperLeft) }
else { remove(.upperLeft) } }
}
}
struct ApertureCornerTreatment {
var radius: CGFloat
var corners: ApertureCorners
var chamfered: Bool
}
有了这些定义,我可以重现您的错误:
struct CornersView<C>: View where C: RandomAccessCollection & MutableCollection {
@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>'
)
}
不过,错误的位置并不是真正出现问题的位置。
主要问题在于您对
Toggle
的调用(为了可读性换行):
Toggle(
sources: sources,
isOn: keyPath.appending(path: \.upperLeftActive),
label: {}
)
您正在通过
sources
,而不是 $sources
。因此,您将一个普通数组(类型 [ApertureCornerTreatment]
)传递给 Toggle
。因此 isOn
参数需要是 KeyPath<ApertureCornerTreatment, Binding<Bool>>
。
Binding
或ApertureCornerTreatment
中没有ApertureCorners
,也没有任何明显的方法来创建它,因此您将无法创建该类型的关键路径。
在
Toggle
的文档中没有解释的技巧是:无论您作为 sources
参数传递给 Toggle
,都需要不仅仅是一个 RandomAccessCollection
,而且还需要 可以创建 Binding
的东西
s。
我所知道的唯一符合
RandomAccessCollection
并且可以创建 Binding
的类型是…… Binding
本身,当它的 Value
类型本身就是 RandomAccessCollection
时。
因此,让我们更改代码以将
$sources
传递给 Toggle
,并得到两个新错误:
Toggle(
// ^ 🛑 Initializer 'init(sources:isOn:label:)' requires that 'C' conform to 'MutableCollection'
sources: $sources,
isOn: keyPath.appending(path: \.upperLeftActive),
// ^ 🛑 Cannot convert value of type 'KeyPath<C.Element, Binding<Bool>>' to expected argument type 'KeyPath<Binding<C>.Element, Binding<Bool>>'
label: {}
)
我们通过添加
C
符合 MutableCollection
的要求并更改 keyPath
属性的类型来修复这些错误:
struct CornersView<C>: View where C: RandomAccessCollection & MutableCollection {
// ADD THIS ^^^^^^^^^^^^^^^^^^^
@Binding var sources: C
let keyPath: KeyPath<Binding<C.Element>, Binding<ApertureCorners>>
// CHANGE THIS ^^^^^^^^^^^^^^^^^^
var body: some View {
Toggle(
sources: $sources,
isOn: keyPath.appending(path: \.upperLeftActive),
label: {}
)
}
}
通过这些更改,Swift 接受
#Preview
。