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 选择器为所选项目和选择视图分隔文本的主要内容,如果未能解决你的问题,请参考以下文章