如何构建封装的Toggle访问集合的关键路径

问题描述 投票:0回答:1

我正在尝试构建一个新的 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: {})
,看起来与当前预览非常相似。

我做错了什么?

generics swiftui swift-keypath
1个回答
0
投票

以后请发布完整的示例。我已经插入了一些代码以便能够重现您的错误,但您应该将所有这些都包含在您的帖子中:

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

© www.soinside.com 2019 - 2024. All rights reserved.