英文:
SwiftUI Generics
问题
Here is the translated content:
如何将这两个视图合并为一个 PickerFromOptions
视图,该视图可以接受 enum
选项或 [String]
选项参数?
我意识到我可以将它们保留为两个单独的视图,并根据需要调用正确的视图,但是我正在尝试更好地理解泛型,所以我想看看如何制作一个可以处理这两种情况的视图。到目前为止,我一直在尝试将它们合并到一个视图中。
我还意识到我可以将枚举映射到字符串数组以传递,但然后将选择存储回数据模型将需要经过更多的“步骤”。
我想要能够像这样使用枚举来调用它:
enum enumOptions: String, CaseIterable, Identifiable {
case one, two, three
var id: String { self.rawValue }
}
@State var enumSelection: enumOptions
PickerFromOptions(label: "enumOptions", options: enumOptions.allCases, selection: $enumOptionSelection)
或者从字符串选项数组中调用它:
let stringOptions = ["one", "two", "three"]
@State var stringSelection: String
PickerFromOptions(label: "stringOptions", options: stringOptions, selection: $stringSelection)
我的第一个想法是让 OptionType
只符合 Hashable
,但后来似乎我必须进行运行时类型检查来确定如何获取 id 和字符串值。
英文:
How can I combine these two views into one PickerFromOptions
view that can take either an enum
options or a `[String] option parameter?
I realize I can just leave these as two separate views and call the correct one depending on what I want to pass it, however, I'm trying to understand Generics better and so I'd like to see how I can make just one view that handles both scenarios. So far I've had troubles combining these into one view.
I also realize I could map the enum to a string array to pass, but then storing the selection back into the data model would have to go through more "hoops".
struct PickerFromEnumOptions<SelectionType, OptionType>: View where SelectionType: Hashable, OptionType: Identifiable & RawRepresentable & Hashable {
var label: String
var options: [OptionType]
@Binding var selection: SelectionType
var body: some View {
Picker(label, selection:$selection) {
ForEach(options) { option in
Text("\(option.rawValue as! String)")
.tag(option)
}
}
}
}
struct PickerFromStringArrayOptions<SelectionType, OptionType>: View where SelectionType: Hashable, OptionType: StringProtocol {
var label: String
var options: [OptionType]
@Binding var selection: SelectionType
var body: some View {
Picker(label, selection:$selection) {
ForEach(options, id:\.self) { option in
Text("\(option as! String)")
.tag(option)
}
}
}
}
I want to be able to call it using an Enum for the options like this:
enum enumOptions: String, CaseIterable, Identifiable {
case one, two, three
var id: String { self.rawValue }
}
@State var enumSelection: enumOptions
PickerFromOptions(label: "enumOptions", options: enumOptions.allCases, selection: $enumOptionSelection)
or from an array of string options like this:
let stringOptions = ["one", "two", "three"]
@State var stringSelection: String
PickerFromOptions(label: "stringOptions", options: stringOptions, selection: $stringSelection)
My first thought was to make OptionType just conform to Hashable, but then it seemed like I have to do runtime type checking to determine how to get the id and string value.
答案1
得分: 1
以下是您要翻译的内容:
"要做到这一点,我更喜欢首先对要使用泛型的内容进行一些更改,首先我认为最好为实际集合使用泛型类型,而不是集合的元素。
使用 ForEach
的要求是集合符合 RandomAccessCollection
,因此泛型类型将为
<OptionsType: RandomAccessCollection>
而且由于我们现在使用了一个集合,我们不需要第二个泛型类型,因为这将是 RandomAccessCollection.Element
。
struct PickerFromOptions<OptionsType: RandomAccessCollection>: View
我们希望 Element
类型符合 Hashable
,在这里我还使其符合 CustomStringConvertible
,这样我们在 Picker
中有东西可以显示。
where OptionsType.Element: Hashable, OptionsType.Element: CustomStringConvertible
我也更喜欢让选择属性是可选的,这样我们可以从未选择的状态开始。
因此,泛型视图变成了
struct PickerFromOptions<OptionsType: RandomAccessCollection>: View where OptionsType.Element: Hashable,
OptionsType.Element: CustomStringConvertible {
var label: String
var options: OptionsType
@Binding var selection: OptionsType.Element?
var body: some View {
Picker(label, selection:$selection) {
ForEach(options, id: \.self) { option in
Text(option.description)
.tag(Optional(option))
}
}
}
}
一个使用示例
@State var enumSelection: enumOptions?
@State var stringSelection: String?
var body: some View {
VStack {
PickerFromOptions(label: "enumOptions", options: enumOptions.allCases, selection: $enumSelection)
PickerFromOptions(label: "stringOptions", options: stringOptions, selection: $stringSelection)
}
.padding()
}
如果您不想使用 CustomStringConvertible
,一种备选且可能更好的解决方案是引入一个元素需要符合的协议
protocol OptionsElement: Hashable {
var name: String { get }
}
然后我们稍微更改实现
struct PickerFromOptionsCustom<OptionsType: RandomAccessCollection>: View where OptionsType.Element: OptionsElement {
var label: String
var options: OptionsType
@Binding var selection: OptionsType.Element?
var body: some View {
Picker(label, selection:$selection) {
ForEach(options, id: \.self) { option in
Text(option.name)
.tag(option)
}
}
}
}
```"
<details>
<summary>英文:</summary>
To do this I prefer to do some changes first to what to use generics for, first of all I think it's better to have a generic type for the actual collection rather than the element of the collection.
The requirement for using `ForEach` is that the collection conforms to `RandomAccessCollection` so the generic type will then be
<OptionsType: RandomAccessCollection>
And since we now use a collection we don't need the second generic type since this will be `RandomAccessCollection.Element`.
struct PickerFromOptions<OptionsType: RandomAccessCollection>: View
We want the `Element` type to conform to `Hashable` and here I also make it conform to `CustomStringConvertible` so we have something to display in the `Picker`
where OptionsType.Element: Hashable, OptionsType.Element: CustomStringConvertible
I also prefer to let the selection property to be optional so we can start with nothing selected.
So the generic view then becomes
struct PickerFromOptions<OptionsType: RandomAccessCollection>: View where OptionsType.Element: Hashable,
OptionsType.Element: CustomStringConvertible {
var label: String
var options: OptionsType
@Binding var selection: OptionsType.Element?
var body: some View {
Picker(label, selection:$selection) {
ForEach(options, id: \.self) { option in
Text(option.description)
.tag(Optional(option))
}
}
}
}
A usage example
@State var enumSelection: enumOptions?
@State var stringSelection: String?
var body: some View {
VStack {
PickerFromOptions(label: "enumOptions", options: enumOptions.allCases, selection: $enumSelection)
PickerFromOptions(label: "stringOptions", options: stringOptions, selection: $stringSelection)
}
.padding()
}
If you don't want to use `CustomStringConvertible` an alternative and perhaps better solution is to introduce a protocol that the elements need to conform to
protocol OptionsElement: Hashable {
var name: String { get }
}
And then we slightly change the implementation
struct PickerFromOptionsCustom<OptionsType: RandomAccessCollection>: View where OptionsType.Element: OptionsElement {
var label: String
var options: OptionsType
@Binding var selection: OptionsType.Element?
var body: some View {
Picker(label, selection:$selection) {
ForEach(options, id: \.self) { option in
Text(option.name)
.tag(option)
}
}
}
}
</details>
# 答案2
**得分**: 0
当`Enum`和`String`相同时,我会选择使用`Enum`,如下所示:
```swift
enum Numbers: String, CaseIterable {
case one, two, three
}
然后像这样使用它:
@State var selection: Numbers
Picker("Picker", selection: $selection) {
ForEach(Numbers.allCases, id: \.self) { number in
Text(number.rawValue)
}
}
这样做可以确保不会漏掉字符,并且可以查看所有可能的选项。
英文:
When the Enum
and String
s are the same, I would go with an Enum
like so:
enum Numbers: String, CaseIterable {
case one, two, three
}
And then use it like so:
@State var selection: Numbers
Picker("Picker", selection: $selection) {
ForEach(Numbers.allCases, id: \.self) { number in
Text(number.rawValue)
}
}
This gives you the safety of not missing a character and an overview of all possible options.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论