0

出于某种原因,我不明白,当我从 a 添加/删除项目@State varMainViewOutterViews 没有正确更新。

我想要实现的是用户一次只能“标记”(选择)一个项目。例如,当我点击“item #1”时,它会被标记。如果我单击另一个项目,则“项目 #1”将不再被标记,而只会标记我刚刚单击的新项目。

在此处输入图像描述

目前,我的代码显示所有项目,就好像它们已被标记一样,即使它们不再存在。以下代码具有我为MainViewOutterView和实现的最小结构和功能InnerView

我尝试使用State vars 而不是 in 中的计算属性OutterView,但它不起作用。另外,我尝试使用 avar而不是计算属性 inOutterView并将其初始化,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))
    }
}
4

1 回答 1

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))
    }
}

发生了什么:

  1. 每个项目都有Item一个 ID、该项目的标记状态和标题
  2. StateManager保留这些项目的数组。它还为数组的每个索引提供了自定义绑定。对于getter,它只返回该索引处的模型状态。对于setter,它会创建项目数组的新副本。任何时候设置一个复选框,它都会取消选中所有其他框。
  3. 现在ForEach获取items. 这可以在没有枚举的情况下完成,但是像这样通过索引编写自定义绑定很容易。您还可以按 ID 而不是索引进行过滤。请注意,由于枚举,它.1.id用于 id 参数 -.1是项目,.0index.
  4. 在内部ForEach,之前的自定义绑定被创建并传递给子视图
  5. 在子视图中,没有使用@State,而是使用了@Binding(这是自定义Binding传递给的)

使用这种ObservableObject包含所有状态并通过@Published 属性和@Bindings 传递的策略,可以更轻松地组织数据。它还避免了像您最初使用update函数时那样来回传递闭包。这最终成为 SwiftUI 中一种非常惯用的做事方式。

于 2021-02-18T03:57:06.723 回答