SwiftUI 列出单个可选项
Posted
技术标签:
【中文标题】SwiftUI 列出单个可选项【英文标题】:SwiftUI List single selectable item 【发布时间】:2021-02-16 05:49:50 【问题描述】:我正在尝试创建一个List
并一次只允许选择一项。我将如何在ForEach
循环中这样做?我可以选择多个项目就好了,但最终目标是在List
的选定项目中只有一个复选标记。它甚至可能不是处理我正在尝试的正确方法。
struct ContentView: View
var body: some View
NavigationView
List((1 ..< 4).indices, id: \.self) index in
CheckmarkView(index: index)
.padding(.all, 3)
.listStyle(PlainListStyle())
.navigationBarTitleDisplayMode(.inline)
//.environment(\.editMode, .constant(.active))
struct CheckmarkView: View
let index: Int
@State var check: Bool = false
var body: some View
Button(action:
check.toggle()
)
HStack
Image("Image-\(index)")
.resizable()
.frame(width: 70, height: 70)
.cornerRadius(13.5)
Text("Example-\(index)")
Spacer()
if check
Image(systemName: "checkmark")
.resizable()
.frame(width: 12, height: 12)
【问题讨论】:
这能回答你的问题吗? How to make List with single selection with SwiftUI 5 【参考方案1】:您需要一些东西来存储所有状态,而不是在每个复选标记视图中存储它,因为需要一次只检查一件事。我做了一个小例子,其中逻辑在ObservableObject
中处理,并通过处理检查/取消检查状态的自定义Binding
传递给复选标记视图:
struct CheckmarkModel
var id = UUID()
var state = false
class StateManager : ObservableObject
@Published var checkmarks = [CheckmarkModel(), CheckmarkModel(), CheckmarkModel(), CheckmarkModel()]
func singularBinding(forIndex index: Int) -> Binding<Bool>
Binding<Bool> () -> Bool in
self.checkmarks[index].state
set: (newValue) in
self.checkmarks = self.checkmarks.enumerated().map itemIndex, item in
var itemCopy = item
if index == itemIndex
itemCopy.state = newValue
else
//not the same index
if newValue
itemCopy.state = false
return itemCopy
struct ContentView: View
@ObservedObject var state = StateManager()
var body: some View
NavigationView
List(Array(state.checkmarks.enumerated()), id: \.1.id) (index, item) in //<-- here
CheckmarkView(index: index + 1, check: state.singularBinding(forIndex: index))
.padding(.all, 3)
.listStyle(PlainListStyle())
.navigationBarTitleDisplayMode(.inline)
struct CheckmarkView: View
let index: Int
@Binding var check: Bool //<-- Here
var body: some View
Button(action:
check.toggle()
)
HStack
Image("Image-\(index)")
.resizable()
.frame(width: 70, height: 70)
.cornerRadius(13.5)
Text("Example-\(index)")
Spacer()
if check
Image(systemName: "checkmark")
.resizable()
.frame(width: 12, height: 12)
发生了什么:
-
有一个
CheckmarkModel
,每个复选框都有一个 ID,以及该框的状态
StateManager
保留了这些模型的数组。它还为数组的每个索引提供了自定义绑定。对于getter
,它只返回该索引处模型的状态。对于setter
,它会创建一个复选框数组的新副本。任何时候设置一个复选框,它都会取消选中所有其他框。我还保留了您最初不允许检查的行为
List
现在获得了state.checkmarks
的枚举——使用enumerated
可以让我保持您之前能够将索引号传递给复选框视图的行为
在 ForEach 内部,创建之前的自定义绑定并将其传递给子视图
在子视图中,不使用@State,而是使用@Binding(这是自定义Binding
传递给的)
【讨论】:
很好的解释和很好的例子,特别是对于刚刚学习的人。【参考方案2】:我们可以从 Swift 5.5 的绑定集合中受益。
import SwiftUI
struct CheckmarkModel: Identifiable, Hashable
var id = UUID()
var state = false
class StateManager : ObservableObject
@Published var checkmarks = [CheckmarkModel(), CheckmarkModel(), CheckmarkModel(), CheckmarkModel()]
struct SingleSelectionList<Content: View>: View
@Binding var items: [CheckmarkModel]
@Binding var selectedItem: CheckmarkModel?
var rowContent: (CheckmarkModel) -> Content
@State var previouslySelectedItemNdx: Int?
var body: some View
List(Array($items.enumerated()), id: \.1.id) (ndx, $item) in
rowContent(item)
.modifier(CheckmarkModifier(checked: item.id == self.selectedItem?.id))
.contentShape(Rectangle())
.onTapGesture
if let prevIndex = previouslySelectedItemNdx
items[prevIndex].state = false
self.selectedItem = item
item.state = true
previouslySelectedItemNdx = ndx
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
struct ContentView: View
@ObservedObject var state = StateManager()
@State private var selectedItem: CheckmarkModel?
var body: some View
VStack
Text("Selected Item: \(selectedItem?.id.description ?? "Select one")")
Divider()
SingleSelectionList(items: $state.checkmarks, selectedItem: $selectedItem) item in
HStack
Text(item.id.description + " " + item.state.description)
Spacer()
稍微简化的版本
struct ContentView: View
@ObservedObject var state = StateManager()
@State private var selection: CheckmarkModel.ID?
var body: some View
List
ForEach($state.checkmarks) $item in
SelectionCell(item: $item, selectedItem: $selection)
.onTapGesture
if let ndx = state.checkmarks.firstIndex(where: $0.id == selection)
state.checkmarks[ndx].state = false
selection = item.id
item.state = true
.listStyle(.plain)
struct SelectionCell: View
@Binding var item: CheckmarkModel
@Binding var selectedItem: CheckmarkModel.ID?
var body: some View
HStack
Text(item.id.description + " " + item.state.description)
Spacer()
if item.id == selectedItem
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
使用内部List
的选定标记和选择的版本:
import SwiftUI
struct CheckmarkModel: Identifiable, Hashable
var name: String
var state: Bool = false
var id = UUID()
class StateManager : ObservableObject
@Published var checkmarks = [CheckmarkModel(name: "Name1"), CheckmarkModel(name: "Name2"), CheckmarkModel(name: "Name3"), CheckmarkModel(name: "Name4")]
struct ContentView: View
@ObservedObject var state = StateManager()
@State private var selection: CheckmarkModel.ID?
@State private var selectedItems = [CheckmarkModel]()
var body: some View
VStack
Text("Items")
List($state.checkmarks, selection: $selection) $item in
Text(item.name + " " + item.state.description)
.onChange(of: selection) s in
for index in state.checkmarks.indices
if state.checkmarks[index].state == true
state.checkmarks[index].state = false
selectedItems = []
if let ndx = state.checkmarks.firstIndex(where: $0.id == selection)
state.checkmarks[ndx].state = true
selectedItems = [state.checkmarks[ndx]]
print(selectedItems)
.environment(\.editMode, .constant(.active))
Divider()
List(selectedItems)
Text($0.name + " " + $0.state.description)
Text("\(selectedItems.count) selections")
【讨论】:
【参考方案3】:List
ForEach(0 ..< RemindTimeType.allCases.count)
index in CheckmarkView(title:getListTitle(index), index: index, markIndex: $markIndex)
.padding(.all, 3)
.listRowBackground(Color.clear)
struct CheckmarkView: View
let title: String
let index: Int
@Binding var markIndex: Int
var body: some View
Button(action:
markIndex = index
)
HStack
Text(title)
.foregroundColor(Color.white)
.font(.custom(FontEnum.Regular.fontName, size: 14))
Spacer()
if index == markIndex
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color(hex: 0xe6c27c))
【讨论】:
请不要只发布代码作为答案,还要解释您的代码的作用以及它如何解决问题的问题。带有解释的答案通常更有帮助、质量更好,并且更有可能吸引投票。以上是关于SwiftUI 列出单个可选项的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI:为 .toolbar ToolbarItem 设置单独的颜色?
尝试使用 SwiftUI 制作可滑动的图像视图时,索引意外更改