简体   繁体   English

SwiftUI 中的 @Binding 和 ForEach

[英]@Binding and ForEach in SwiftUI

I can't undertand how to use @Binding<\/code> in combination with ForEach<\/code> in SwiftUI.我不能undertand如何使用@Binding<\/code>结合ForEach<\/code>在SwiftUI。 Let's say I want to create a list of Toggle<\/code> s from an array of booleans.假设我想从一个布尔数组创建一个Toggle<\/code>列表。

struct ContentView: View {
    @State private var boolArr = [false, false, true, true, false]

    var body: some View {
        List {
            ForEach(boolArr, id: \.self) { boolVal in
                Toggle(isOn: $boolVal) {
                    Text("Is \(boolVal ? "On":"Off")")
                }                
            }
        }
    }
}

You can use something like the code below.您可以使用类似下面的代码。 Note that you will get a deprecated warning, but to address that, check this other answer: https://stackoverflow.com/a/57333200/7786555请注意,您将收到已弃用的警告,但要解决此问题,请查看其他答案: https : //stackoverflow.com/a/57333200/7786555

import SwiftUI

struct ContentView: View {
    @State private var boolArr = [false, false, true, true, false]

    var body: some View {
        List {
            ForEach(boolArr.indices) { idx in
                Toggle(isOn: self.$boolArr[idx]) {
                    Text("boolVar = \(self.boolArr[idx] ? "ON":"OFF")")
                }
            }
        }
    }
}

In SwiftUI, just use Identifiable structs instead of Bools在 SwiftUI 中,只需使用Identifiable structs 而不是 Bools

struct ContentView: View {
    @State private var boolArr = [BoolSelect(isSelected: true), BoolSelect(isSelected: false), BoolSelect(isSelected: true)]

    var body: some View {
        List {
            ForEach(boolArr.indices) { index in
                Toggle(isOn: self.$boolArr[index].isSelected) {
                    Text(self.boolArr[index].isSelected ? "ON":"OFF")
                }
            }
        }
    }
}

struct BoolSelect: Identifiable {
    var id = UUID()
    var isSelected: Bool
}

If also you need to change the number of Toggle s (not only their values), consider this.如果您还需要更改Toggle的数量(不仅是它们的值),请考虑这一点。 BTW, we can omit ForEach {} block.顺便说一句,我们可以省略ForEach {} 块。

struct ContentView: View {
   @State var boolArr = [false, false, true, true, false]

    var body: some View {
        NavigationView {
            // id: \.self is obligatory if you need to insert
            List(boolArr.indices, id: \.self) { idx in
                    Toggle(isOn: self.$boolArr[idx]) {
                        Text(self.boolArr[idx] ? "ON":"OFF")
                }
            }
            .navigationBarItems(leading:
                Button(action: { self.boolArr.append(true) })
                { Text("Add") }
                , trailing:
                Button(action: { self.boolArr.removeAll() })
                { Text("Remove") })
        }
    }
}

⛔️ Don't use a Bad practice! ⛔️ 不要使用不好的做法!

Most of the answers (including the @kontiki accepted answer) method cause the engine to rerender the entire UI on each change and Apple mentioned this as a bad practice at wwdc2021 (around time 7:40)大多数答案(包括@kontiki 接受的答案)方法会导致引擎在每次更改时重新渲染整个 UI,Apple 在wwdc2021 (大约时间 7:40)提到这是一种不好的做法


✅ Swift 5.5 ✅ 斯威夫特 5.5

From this version of swift, you can use binding array elements directly by passing in the bindable item like:在这个版本的 swift 中,您可以通过传入可绑定项直接使用绑定数组元素,例如:

在此处输入图片说明

⚠️ Note that Swift 5.5 is not supported on iOS 14 and below but at least check for the os version and don't continue the bad practice! ⚠️请注意,iOS 14 及更低版本不支持 Swift 5.5,但至少检查操作系统版本,不要继续这种不良做法!

In WWDC21 videos Apple clearly stated that using .indices in the ForEach loop is a bad practice.在 WWDC21 视频中,Apple 明确表示在ForEach循环中使用.indices是一种不好的做法。 Besides that, we need a way to uniquely identify every item in the array, so you can't use ForEach(boolArr, id:\\.self) because there are repeated values in the array.除此之外,我们需要一种方法来唯一标识数组中的每个项目,因此您不能使用ForEach(boolArr, id:\\.self)因为数组中有重复的值。

As @Mojtaba Hosseini stated, new to Swift 5.5 you can now use binding array elements directly passing the bindable item.正如@Mojtaba Hosseini 所说,Swift 5.5 的新手现在可以使用绑定数组元素直接传递可绑定项。 But if you still need to use a previous version of Swift, this is how I accomplished it:但是,如果您仍然需要使用以前版本的 Swift,我就是这样做的:

struct ContentView: View {
  @State private var boolArr: [BoolItem] = [.init(false), .init(false), .init(true), .init(true), .init(false)]
  
  var body: some View {
    List {
      ForEach(boolArr) { boolItem in
        makeBoolItemBinding(boolItem).map {
          Toggle(isOn: $0.value) {
            Text("Is \(boolItem.value ? "On":"Off")")
          }
        }
      }
    }
  }
  
  struct BoolItem: Identifiable {
    let id = UUID()
    var value: Bool
    
    init(_ value: Bool) {
      self.value = value
    }
  }
  
  func makeBoolItemBinding(_ item: BoolItem) -> Binding<BoolItem>? {
    guard let index = boolArr.firstIndex(where: { $0.id == item.id }) else { return nil }
    return .init(get: { self.boolArr[index] },
                 set: { self.boolArr[index] = $0 })
  }
}

First we make every item in the array identifiable by creating a simple struct conforming to Identifiable .首先,我们通过创建一个符合Identifiable的简单结构来使数组中的每一项都可Identifiable Then we make a function to create a custom binding.然后我们创建一个函数来创建自定义绑定。 I could have used force unwrapping to avoid returning an optional from the makeBoolItemBinding function but I always try to avoid it.我本可以使用强制解包来避免从makeBoolItemBinding函数返回一个可选项,但我总是尽量避免它。 Returning an optional binding from the function requires the map method to unwrap it.从函数返回一个可选绑定需要 map 方法来解包它。

I have tested this method in my projects and it works faultlessly so far.我已经在我的项目中测试了这种方法,到目前为止它工作得很好。

如何在可绑定数组中进行 .filtering?

"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM