出于某种原因,我不明白,当我从 @State var
中的 MainView
添加/删除项目时,OutterView
未正确更新。
我想要实现的是,用户一次只能“标记”(选择)一项。例如,当我点击“item #1”时,它会被标记。如果我单击另一个项目,则不会再标记“项目 #1”,而只会标记我刚刚单击的新项目。
目前,我的代码显示所有项目,就好像它们被标记了一样,即使它们不再是。以下代码具有我为 MainView
、OutterView
和 InnerView
实现的最低结构和功能。
我曾尝试在 State var
中使用 OutterView
代替计算属性,但它不起作用。此外,我尝试在 var
中使用 OutterView
代替计算属性并在 init()
中对其进行初始化,但也不起作用。
希望你能帮我找出我做错了什么。 谢谢!
struct MainView: View {
@State var flagged: [String] = []
var data: [String] = ["item #1", "item #2", "item #3", "item #4", "item #5"]
var body: some View {
VStack(spacing: 50) {
VStack {
ForEach(data, id:\.self) { text in
OutterView(text: text, flag: flagged.contains(text)) { (flag: Bool) in
if flag {
flagged = [text]
} else {
if let index = flagged.firstIndex(of: text) {
flagged.remove(at: index)
}
}
}
}
}
Text("Flagged: \(flagged.description)")
Button(action: {
flagged = []
}, label: {
Text("Reset flagged")
})
}
}
}
struct OutterView: View {
@State private var flag: Bool
private let text: String
private var color: Color { flag ? Color.green : Color.gray }
private var update: (Bool)->Void
var body: some View {
InnerView(color: color, text: text)
.onTapGesture {
flag.toggle()
update(flag)
}
}
init(text: String, flag: Bool = false, update: @escaping (Bool)->Void) {
self.text = text
self.update = update
_flag = State(initialValue: flag)
}
}
struct InnerView: View {
let color: Color
let text: String
var body: some View {
Text(text)
.padding()
.background(
Capsule()
.fill(color))
}
}
答案 0 :(得分:0)
这是一个简单的版本,可以满足您的需求(解释如下):
struct Item : Identifiable {
var id = UUID()
var flagged = false
var title : String
}
class StateManager : ObservableObject {
@Published var items = [Item(title: "Item #1"),Item(title: "Item #2"),Item(title: "Item #3"),Item(title: "Item #4"),Item(title: "Item #5")]
func singularBinding(forIndex index: Int) -> Binding<Bool> {
Binding<Bool> { () -> Bool in
self.items[index].flagged
} set: { (newValue) in
self.items = self.items.enumerated().map { itemIndex, item in
var itemCopy = item
if index == itemIndex {
itemCopy.flagged = newValue
} else {
//not the same index
if newValue {
itemCopy.flagged = false
}
}
return itemCopy
}
}
}
func reset() {
items = items.map { item in
var itemCopy = item
itemCopy.flagged = false
return itemCopy
}
}
}
struct MainView: View {
@ObservedObject var stateManager = StateManager()
var body: some View {
VStack(spacing: 50) {
VStack {
ForEach(Array(stateManager.items.enumerated()), id:\.1.id) { (index,item) in
OutterView(text: item.title, flag: stateManager.singularBinding(forIndex: index))
}
}
Text("Flagged: \(stateManager.items.filter({ $0.flagged }).map({$0.title}).description)")
Button(action: {
stateManager.reset()
}, label: {
Text("Reset flagged")
})
}
}
}
struct OutterView: View {
var text: String
@Binding var flag: Bool
private var color: Color { flag ? Color.green : Color.gray }
var body: some View {
InnerView(color: color, text: text)
.onTapGesture {
flag.toggle()
}
}
}
struct InnerView: View {
let color: Color
let text: String
var body: some View {
Text(text)
.padding()
.background(
Capsule()
.fill(color))
}
}
发生了什么:
Item
,其中包含每个项目的 ID、项目的标记状态和标题StateManager
保存这些项目的数组。它还为数组的每个索引提供自定义绑定。对于 getter
,它只返回模型在该索引处的状态。对于 setter
,它创建了项目数组的新副本。只要设置了复选框,它就会取消选中所有其他复选框。ForEach
现在获得 items
的枚举。这可以在没有枚举的情况下完成,但是像这样通过索引编写自定义绑定很容易。您还可以按 ID 而不是索引进行过滤。请注意,由于枚举,它使用 .1.id
作为 id 参数 - .1
是项目,而 .0
是 index
。ForEach
中,之前的自定义绑定被创建并传递给子视图Binding
传递给的)使用包含您的所有状态并通过@Published 属性和@Bindings 传递它的ObservableObject
的这种策略可以更轻松地组织您的数据。它还避免了像最初使用 update
函数那样来回传递闭包。这最终成为 SwiftUI 中一种非常惯用的处理方式。