SwiftUI 选择器为所选项目和选择视图分隔文本

Posted

技术标签:

【中文标题】SwiftUI 选择器为所选项目和选择视图分隔文本【英文标题】:SwiftUI picker separate texts for selected item and selection view 【发布时间】:2020-06-18 07:35:36 【问题描述】:

我有一个Picker 嵌入在Form 中的NavigationView 中。我想在主View 中为所选项目提供单独文本,并在选择器View 中选择项目时提供更详细的描述。

这是我迄今为止尝试过的:

struct Item 
    let abbr: String
    let desc: String

struct ContentView: View 
    @State private var selectedIndex = 0
    let items: [Item] = [
        Item(abbr: "AA", desc: "aaaaa"),
        Item(abbr: "BB", desc: "bbbbb"),
        Item(abbr: "CC", desc: "ccccc"),
    ]

    var body: some View 
        NavigationView 
            Form 
                picker
            
        
    

    var picker: some View 
        Picker(selection: $selectedIndex, label: Text("Chosen item")) 
            ForEach(0..<items.count)  index in
                Group 
                    if self.selectedIndex == index 
                        Text(self.items[index].abbr)
                     else 
                        Text(self.items[index].desc)
                    
                
                .tag(index)
            
            .id(UUID())
        
    

当前解决方案

这是主视图中的选择器:

这是选择视图:

问题在于,在选择视图中使用此解决方案时,会有 "BB" 而不是 "bbbbb"

这是因为两个屏幕中的“BB”文本是由完全相同的Text 视图生成的。

预期结果

主视图中的选择器:

在选择视图中:

是否可以在 SwiftUI 中为两个屏幕设置单独的文本(视图)?

【问题讨论】:

它显示BB,因为它被选中,所以if self.selectedIndex == index Text(self.items[index].abbr 条件有效。我在这里没有看到问题。 @Asperi 我想在第一张图片中使用“BB”,在第二张图片中使用“aaaaa”、“bbbbb”、“ccccc”。我只是不知道这是否可能。 @pawello2222 感谢您的提问。我了解您尝试实现的目标,但是使用 Picker 的本机实现是不可能的。您必须自己使用 SwiftUI 元素(如 Forms 和 TextViews)来完成。如果你愿意,我可以告诉你如何实现这一目标? @JonasDeichelmann 当然 :) 它不一定是 Picker,它可以是 SwiftUI 元素的其他组合,看起来 像 Picker,并且在同样的方式。 【参考方案1】:

没有 Picker 的可能解决方案

正如我在comment 中提到的,目前还没有使用 SwiftUI Picker 进行本机实现的解决方案。相反,您可以使用 SwiftUI 元素,尤其是使用 NavigationLink。这是一个示例代码:

struct Item 
    let abbr: String
    let desc: String


struct ContentView: View 
    
    @State private var selectedIndex = 0
    let items: [Item] = [
        Item(abbr: "AA", desc: "aaaaa"),
        Item(abbr: "BB", desc: "bbbbb"),
        Item(abbr: "CC", desc: "ccccc"),
    ]
    
    var body: some View 
        NavigationView 
            Form 
                NavigationLink(destination: (
                    DetailSelectionView(items: items, selectedItem: $selectedIndex)
                    ), label: 
                        HStack 
                            Text("Chosen item")
                            Spacer()
                            Text(self.items[selectedIndex].abbr).foregroundColor(Color.gray)
                        
                )
            
        
    


struct DetailSelectionView: View 
    var items: [Item]
    @Binding var selectedItem: Int
    
    var body: some View 
        Form 
            ForEach(0..<items.count)  index in
                HStack 
                    Text(self.items[index].desc)
                    Spacer()
                    if self.selectedItem == index 
                        Image(systemName: "checkmark").foregroundColor(Color.blue)
                    
                
                .onTapGesture 
                    self.selectedItem = index
                
            
        
    

如果有任何改进,请随时编辑代码 sn-p。

【讨论】:

不知何故我没有意识到它可以用一个简单的NavigationLink 来完成。我发布了我的答案,并对您的代码进行了一些改进(使其行为更像选择器),但显然我接受了您的答案。非常感谢您的帮助:)【参考方案2】:

扩展 JonasDeichelmann's answer 我创建了自己的选择器:

struct CustomPicker<Item>: View where Item: Hashable 
    @State var isLinkActive = false
    @Binding var selection: Int
    let title: String
    let items: [Item]
    let shortText: KeyPath<Item, String>
    let longText: KeyPath<Item, String>

    var body: some View 
        NavigationLink(destination: selectionView, isActive: $isLinkActive, label: 
            HStack 
                Text(title)
                Spacer()
                Text(items[selection][keyPath: shortText])
                    .foregroundColor(Color.gray)
            
        )
    

    var selectionView: some View 
        Form 
            ForEach(0 ..< items.count)  index in
                Button(action: 
                    self.selection = index
                    self.isLinkActive = false
                ) 
                    HStack 
                        Text(self.items[index][keyPath: self.longText])
                        Spacer()
                        if self.selection == index 
                            Image(systemName: "checkmark")
                                .foregroundColor(Color.blue)
                        
                    
                    .contentShape(Rectangle())
                    .foregroundColor(.primary)
                
            
        
    

那么我们必须让Item符合Hashable

struct Item: Hashable  ... 

我们可以这样使用它:

struct ContentView: View 
    @State private var selectedIndex = 0
    let items: [Item] = [
        Item(abbr: "AA", desc: "aaaaa"),
        Item(abbr: "BB", desc: "bbbbb"),
        Item(abbr: "CC", desc: "ccccc"),
    ]

    var body: some View 
        NavigationView 
            Form 
                CustomPicker(selection: $selectedIndex, title: "Item", items: items,
                             shortText: \Item.abbr, longText: \Item.desc)
            
        
    

注意: 目前无法更改选择器的布局。如果需要,可以使用例如使其更通用。 @ViewBuilder.

【讨论】:

获得原始外观,如问题所示: var selectionView: some View List ....listStyle(GroupedListStyle()) 【参考方案3】:

我再次尝试了自定义 split 选择器。


实施

    首先,我们需要一个结构,因为我们将使用不同的项目进行选择、主屏幕和选择器屏幕。
public struct PickerItem<
    Selection: Hashable & LosslessStringConvertible,
    Short: Hashable & LosslessStringConvertible,
    Long: Hashable & LosslessStringConvertible
>: Hashable 
    public let selection: Selection
    public let short: Short
    public let long: Long

    public init(selection: Selection, short: Short, long: Long) 
        self.selection = selection
        self.short = short
        self.long = long
    

    然后,我们创建一个带有内部NavigationLink 的自定义视图来模拟Picker 的行为:
public struct SplitPicker<
    Label: View,
    Selection: Hashable & LosslessStringConvertible,
    ShortValue: Hashable & LosslessStringConvertible,
    LongValue: Hashable & LosslessStringConvertible
>: View 
    public typealias Item = PickerItem<Selection, ShortValue, LongValue>

    @State private var isLinkActive = false

    @Binding private var selection: Selection
    private let items: [Item]
    private var showMultiLabels: Bool
    private let label: () -> Label

    public init(
        selection: Binding<Selection>,
        items: [Item],
        showMultiLabels: Bool = false,
        label: @escaping () -> Label
    ) 
        self._selection = selection
        self.items = items
        self.showMultiLabels = showMultiLabels
        self.label = label
    

    public var body: some View 
        NavigationLink(destination: selectionView, isActive: $isLinkActive) 
            HStack 
                label()
                Spacer()
                if let selectedItem = selectedItem 
                    Text(String(selectedItem.short))
                        .foregroundColor(Color.secondary)
                
            
        
    

private extension SplitPicker 
    var selectedItem: Item? 
        items.first  selection == $0.selection 
    


private extension SplitPicker 
    var selectionView: some View 
        Form 
            ForEach(items, id: \.self)  item in
                itemView(item: item)
            
        
    


private extension SplitPicker 
    func itemView(item: Item) -> some View 
        Button(action: 
            selection = item.selection
            isLinkActive = false
        ) 
            HStack 
                if showMultiLabels 
                    itemMultiLabelView(item: item)
                 else 
                    itemLabelView(item: item)
                
                Spacer()
                if item == selectedItem 
                    Image(systemName: "checkmark")
                        .font(Font.body.weight(.semibold))
                        .foregroundColor(.accentColor)
                
            
            .contentShape(Rectangle())
        
    


private extension SplitPicker 
    func itemLabelView(item: Item) -> some View 
        HStack 
            Text(String(item.long))
                .foregroundColor(.primary)
            Spacer()
        
    


private extension SplitPicker 
    func itemMultiLabelView(item: Item) -> some View 
        HStack 
            HStack 
                Text(String(item.short))
                    .foregroundColor(.primary)
                Spacer()
            
            .frame(maxWidth: 50)
            Text(String(item.long))
                .font(.subheadline)
                .foregroundColor(.secondary)
        
    


演示

struct ContentView: View 
    @State private var selection = 2

    let items = (1...5)
        .map 
            PickerItem(
                selection: $0,
                short: String($0),
                long: "Long text of: \($0)"
            )
        
    
    var body: some View 
        NavigationView 
            Form 
                Text("Selected index: \(selection)")
                SplitPicker(selection: $selection, items: items) 
                    Text("Split picker")
                
            
        
    

【讨论】:

以上是关于SwiftUI 选择器为所选项目和选择视图分隔文本的主要内容,如果未能解决你的问题,请参考以下文章

UIDatePicker:无法选择默认值

如何在 SwiftUI 中删除列表选择指示器和分隔符?

选择其中一个时更改其他表格视图单元格的数据

如何确定在 SwiftUI 中的多词文本视图中点击的词

如何在 RecyclerView 中选择和取消选择项目?如何仅在回收站视图中突出显示所选项目?

SwiftUI Segmented Control 在视图刷新时选择段文本动画