在 SwiftUI 中使用到集合的绑定

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

我正在尝试构建一个 TextField,它从集合中获取其值,就像

Toggle<C>(
    _ titleKey: LocalizedStringKey,
    sources: C,
    isOn: KeyPath<C.Element, Binding<Bool>>
) where C : RandomAccessCollection

我已构建以下内容:

struct MultiTextField<C, V, F>: View where C: RandomAccessCollection & MutableCollection,
                                            F: ParseableFormatStyle,
                                            V == F.FormatInput,
                                            F.FormatOutput == String {
    let titleKey: LocalizedStringKey
    @Binding var sources: C
    let keyPath: KeyPath<Binding<C.Element>, Binding<V>>
    let format: F

    @State private var value: V?

    var body: some View {
        TextField(titleKey,
                  value: $value,
                  format: format)
        .onAppear(perform: {
            print($sources.projectedValue[keyPath: keyPath])
        })
    }
}

#Preview {
    struct Test {
        var value: Double
    }
 
    @State var sources = [Test(value: 0.1234),
                          Test(value: 2.3456)]

    return MultiTextField(titleKey: "Test",
                          sources: $sources,
                          keyPath: \.value,
                          format: .number)
}

文本字段本身可以工作,但我需要将集合/keyPath 的结果与文本字段使用的值耦合。当文本字段被提交或失去焦点时,我想将值写回集合(如果它有效(!= nil))。

我不知道如何对集合和关键路径做任何有用的事情,因此我不知道如何继续这里。此处所示的

print
语句会产生编译错误:
Key path of type 'KeyPath<Binding<C.Element>, Binding<V>>' cannot be applied to a base of type 'Binding<C>'

如果我将

keyPath
更改为
Binding<C>
的根类型,那么我会在预览中收到错误消息,指出
[Test]
没有动态成员
value

我能想到的最好的办法是

keyPath
类型:
WritableKeyPath<C.Element, V>
。这允许我们在
sources
上执行映射并提取有用的值,但它不允许我实际将数组中的所有值更新为新值。

如何利用集合和关键路径?

Toggle
如何访问其
sources

swift generics swiftui
1个回答
0
投票

你就快到了,只需做一些小改变。

import SwiftUI

struct MultiTextField<C, V, F>: View where C: RandomAccessCollection & MutableCollection,
                                           C.Element : Identifiable, //Add Identifiable for the ForEach
                                           F: ParseableFormatStyle,
                                           V == F.FormatInput,
                                           F.FormatOutput == String {
    let titleKey: LocalizedStringKey
    @Binding var sources: C
    let keyPath: WritableKeyPath<C.Element, V> //Binding == read/write so you need WritableKeyPath, KeyPath is read-only.
    let format: F
    
    
    var body: some View {
        ForEach($sources, id:\.id){ $source in //C is a collection so you need a ForEach
            TextField(titleKey,
                      value: Binding(get: { source[keyPath: keyPath] //CustomBinding
                                    }, set: { newValue in
                                        source[keyPath: keyPath] = newValue
                                    }),
                      format: format)
        }
        
    }
}

#Preview {
    struct Test: Identifiable{
        var id: UUID = .init()
        var value: Double
    }
    
    @State var sources = [Test(value: 0.1234),
                          Test(value: 2.3456)]
    
    return MultiTextField(titleKey: "Test",
                          sources: $sources,
                          keyPath: \.value,
                          format: .number)
}
© www.soinside.com 2019 - 2024. All rights reserved.