SwiftUI 泛型

huangapple go评论65阅读模式
英文:

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&#39;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 

    &lt;OptionsType: RandomAccessCollection&gt;

And since we now use a collection we don&#39;t need the second generic type since this will be `RandomAccessCollection.Element`. 

    struct PickerFromOptions&lt;OptionsType: RandomAccessCollection&gt;: 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: &quot;enumOptions&quot;, options: enumOptions.allCases, selection: $enumSelection)
            PickerFromOptions(label: &quot;stringOptions&quot;, options: stringOptions, selection: $stringSelection)
        }
        .padding()
    }


If you don&#39;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 Strings 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(&quot;Picker&quot;, 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.

huangapple
  • 本文由 发表于 2023年5月18日 03:28:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/76275599.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定