Whi*_*ger 11 list selection swift ios13 swiftui
我正在创建单个选择列表以在我的应用程序的不同位置使用。
问题:
有没有我不知道的简单解决方案?
如果没有,我该如何完成当前的解决方案?
我的目标:
我当前的解决方案如下: 选择第二项的列表视图
我不能使用 Picker,因为外部操作(目标 3)很耗时。所以我认为它不会顺利进行。很可能在 SwiftUI 中有我的问题的解决方案,但是我错过了它,因为我是 swift 的新手,或者我理解并不是所有的东西都在 SwiftUI 中完美运行,例如:List 的透明背景(这就是为什么我需要清除init() 中的背景)。
所以我开始自己实现选择,并在此处停止:单击 item(Button) 时,我当前的解决方案不会更新视图。(只有出去和回到页面更新视图)。并且仍然可以选择多个项目。
import SwiftUI
struct ModuleList: View {
var modules: [Module] = []
@Binding var selectionKeeper: Int
var Action: () -> Void
init(list: [Module], selection: Binding<Int>, action: @escaping () -> Void) {
UITableView.appearance().backgroundColor = .clear
self.modules = list
self._selectionKeeper = selection
self.Action = action
}
var body: some View {
List(){
ForEach(0..<modules.count) { i in
ModuleCell(module: self.modules[i], action: { self.changeSelection(index: i) })
}
}.background(Constants.colorTransparent)
}
func changeSelection(index: Int){
modules[selectionKeeper].isSelected = false
modules[index].isSelected = true
selectionKeeper = index
self.Action()
}
}
struct ModuleCell: View {
var module: Module
var Action: () -> Void
init(module: Module, action: @escaping () -> Void) {
UITableViewCell.appearance().backgroundColor = .clear
self.module = module
self.Action = action
}
var body: some View {
Button(module.name, action: {
self.Action()
})
.frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
.modifier(Constants.CellSelection(isSelected: module.isSelected))
}
}
class Module: Identifiable {
var id = UUID()
var name: String = ""
var isSelected: Bool = false
var address: Int
init(name: String, address: Int){
self.name = name
self.address = address
}
}
let testLines = [
Module(name: "Line1", address: 1),
Module(name: "Line2", address: 3),
Module(name: "Line3", address: 5),
Module(name: "Line4", address: 6),
Module(name: "Line5", address: 7),
Module(name: "Line6", address: 8),
Module(name: "Line7", address: 12),
Module(name: "Line8", address: 14),
Module(name: "Line9", address: 11),
Module(name: "Line10", address: 9),
Module(name: "Line11", address: 22)
]
Run Code Online (Sandbox Code Playgroud)
尝试在 ModuleList 中添加 (isSelected: Bool) 的 @State 数组并将其绑定到可能会更新视图的 Module isSelected 参数...但在 init() 中填充此数组失败,因为 @State 数组参数在 .append() 之后将保持为空)... 也许添加函数 setList 可以解决这个问题,而我的目标是 Nr。4. 但我不确定这是否真的会首先更新我的观点。
struct ModuleList: View {
var modules: [Module] = []
@State var selections: [Bool] = []
init(list: [String]) {
UITableView.appearance().backgroundColor = .clear
selections = [Bool] (repeating: false, count: list.count) // stays empty
let test = [Bool] (repeating: false, count: list.count) // testing: works as it should
selections = test
for i in 0..<test.count { // for i in 0..<selections.count {
selections.append(false)
modules.append(Module(name: list[i], isSelected: $selections[i])) // Error selections is empty
}
}
var body: some View {
List{
ForEach(0..<modules.count) { i in
ModuleCell(module: self.modules[i], action: { self.changeSelection(index: i) })
}
}.background(Constants.colorTransparent)
}
func changeSelection(index: Int){
modules[index].isSelected = true
}
}
struct ModuleCell: View {
var module: Module
var Method: () -> Void
init(module: Module, action: @escaping () -> Void) {
UITableViewCell.appearance().backgroundColor = .clear
self.module = module
self.Method = action
}
var body: some View {
Button(module.name, action: {
self.Method()
})
.frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
.modifier(Constants.CellSelection(isSelected: module.isSelected))
}
}
struct Module: Identifiable {
var id = UUID()
var name: String = ""
@Binding var isSelected: Bool
init(name: String, isSelected: Binding<Bool>){
self.name = name
self._isSelected = isSelected
}
}
let testLines = ["Line1","Line2","Line3","Line4"
]
Run Code Online (Sandbox Code Playgroud)
LuL*_*aGa 35
实现这一点的最简单方法是在包含带有选择的列表的视图中使用@State,并将其作为@Binding 传递给单元格:
struct SelectionView: View {
let fruit = ["apples", "pears", "bananas", "pineapples"]
@State var selectedFruit: String? = nil
var body: some View {
List {
ForEach(fruit, id: \.self) { item in
SelectionCell(fruit: item, selectedFruit: self.$selectedFruit)
}
}
}
}
struct SelectionCell: View {
let fruit: String
@Binding var selectedFruit: String?
var body: some View {
HStack {
Text(fruit)
Spacer()
if fruit == selectedFruit {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
}
} .onTapGesture {
self.selectedFruit = self.fruit
}
}
}
Run Code Online (Sandbox Code Playgroud)
SwiftUI 目前没有内置的方式来选择列表的一行并相应地更改其外观。但你实际上非常接近你的答案。事实上,您的选择实际上已经有效,只是没有以任何方式使用。
为了说明,ModuleCell(...)在您的之后添加以下行ForEach:
.background(i == self.selectionKeeper ? Color.red : nil)
Run Code Online (Sandbox Code Playgroud)
换句话说,“如果我的当前行 ( i) 与存储在 中的值匹配selectionKeeper,则将单元格着色为红色,否则,使用默认颜色。” 您会看到,当您点击不同的行时,红色会跟随您的点击,表明底层选择实际上正在发生变化。
如果您想启用取消选择,您可以传入 aBinding<Int?>作为您的选择,并将其设置nil为点击当前选定的行时:
struct ModuleList: View {
var modules: [Module] = []
// this is new ------------------v
@Binding var selectionKeeper: Int?
var Action: () -> Void
// this is new ----------------------------v
init(list: [Module], selection: Binding<Int?>, action: @escaping () -> Void) {
...
func changeSelection(index: Int){
if selectionKeeper != index {
selectionKeeper = index
} else {
selectionKeeper = nil
}
self.Action()
}
}
Run Code Online (Sandbox Code Playgroud)
在更结构化的层面上,在使用像 SwiftUI 这样的声明式 UI 框架时,您确实需要一个单一的事实来源,并将您的视图与模型干净地分开。目前,你有重复的状态-selectionKeeper中ModuleList和isSelected在Module是否选择一个给定的模块的两个跟踪。
此外, isSelected 应该真正是视图 ( ModuleCell)的属性,而不是模型 ( Module)的属性,因为它与视图的显示方式有关,而不是每个模块的固有数据。
因此,您ModuleCell应该看起来像这样:
struct ModuleCell: View {
var module: Module
var isSelected: Bool // Added this
var Action: () -> Void
// Added this -------v
init(module: Module, isSelected: Bool, action: @escaping () -> Void) {
UITableViewCell.appearance().backgroundColor = .clear
self.module = module
self.isSelected = isSelected // Added this
self.Action = action
}
var body: some View {
Button(module.name, action: {
self.Action()
})
.frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
.modifier(Constants.CellSelection(isSelected: isSelected))
// Changed this ------------------------------^
}
}
Run Code Online (Sandbox Code Playgroud)
你ForEach会看起来像
ForEach(0..<modules.count) { i in
ModuleCell(module: self.modules[i],
isSelected: i == self.selectionKeeper,
action: { self.changeSelection(index: i) })
}
Run Code Online (Sandbox Code Playgroud)
这是一种更通用的方法,您仍然可以根据需要扩展答案;
TLDR
https://gist.github.com/EnesKaraosman/d778cdabc98ca269b3d162896bea8aac
细节
struct SingleSelectionList<Item: Identifiable, Content: View>: View {
var items: [Item]
@Binding var selectedItem: Item?
var rowContent: (Item) -> Content
var body: some View {
List(items) { item in
rowContent(item)
.modifier(CheckmarkModifier(checked: item.id == self.selectedItem?.id))
.contentShape(Rectangle())
.onTapGesture {
self.selectedItem = item
}
}
}
}
struct CheckmarkModifier: ViewModifier {
var checked: Bool = false
func body(content: Content) -> some View {
Group {
if checked {
ZStack(alignment: .trailing) {
content
Image(systemName: "checkmark")
.resizable()
.frame(width: 20, height: 20)
.foregroundColor(.green)
.shadow(radius: 1)
}
} else {
content
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
并进行演示;
struct PlaygroundView: View {
struct Koko: Identifiable {
let id = UUID().uuidString
var name: String
}
var mock = Array(0...10).map { Koko(name: "Item - \($0)") }
@State var selectedItem: Koko?
var body: some View {
VStack {
Text("Selected Item: \(selectedItem?.name ?? "Select one")")
Divider()
SingleSelectionList(items: mock, selectedItem: $selectedItem) { (item) in
HStack {
Text(item.name)
Spacer()
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
使用与Optional类型选择变量的绑定。
List将只允许选择一个元素。
struct ContentView: View {
// Use Optional for selection.
// Instead od Set or Array like this...
// @State var selection = Set<Int>()
@State var selection = Int?.none
var body: some View {
List(selection: $selection) {
ForEach(0..<128) { _ in
Text("Sample")
}
}
}
}
Run Code Online (Sandbox Code Playgroud)