【问题标题】:SwiftUI State var array not updating child viewsSwiftUI State var 数组不更新子视图
【发布时间】:2021-02-18 03:27:40
【问题描述】:

由于某种原因,我不明白,当我在 MainView 中添加/删除 @State var 中的项目时,OutterViews 没有正确更新。

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

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

我尝试使用State vars 代替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))
    }
}

【问题讨论】:

    标签: swiftui swiftui-state


    【解决方案1】:

    这是一个简单的版本,可以满足您的需求(如下所述):

    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 中一种非常惯用的做事方式。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-09-23
      • 2021-08-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-19
      • 2020-04-10
      • 1970-01-01
      相关资源
      最近更新 更多