【问题标题】:Error: Initializer 'init(_:)' requires that 'Binding<String>' conform to 'StringProtocol'错误:初始化程序 'init(_:)' 要求 'Binding<String>' 符合 'StringProtocol'
【发布时间】:2020-01-23 19:08:10
【问题描述】:

我收到上述错误,不知道如何解决。我有一个包含布尔值的对象数组,并且需要为每个布尔值显示一个切换。

下面是代码。

class Item: Identifiable {
    var id: String
    var label: String
    var isOn: Bool
}

class Service: ObservableObject {
    var didChange = PassthroughSubject<Void, Never>()

    var items: [Item] {
        didSet {
            didChange.send(())
        }
    }
}

struct MyView: View {
    @ObservedObject var service: Service

    var body: some View {
        List {
            ForEach(service.items, id: \.self) { (item: Binding<Item>) in
                Section(header: Text(item.label)) {  // Error: Initializer 'init(_:)' requires that 'Binding<String>' conform to 'StringProtocol'
                    Toggle(isOn: item.isOn) {
                        Text("isOn")
                    }
                }
            }
        }
        .listStyle(GroupedListStyle())
    }
}

【问题讨论】:

    标签: swift swiftui combine


    【解决方案1】:

    Service 类中使用@Published 属性包装器,而不是didChange,并像这样遍历service.items 的索引:

    struct Item: Identifiable {
        var id: String
        var label: String
        var isOn: Bool {
            didSet {
                // Added to show that state is being modified
                print("\(label) just toggled")
            }
        }
    }
    
    class Service: ObservableObject {
        @Published var items: [Item]
    
        init() {
            self.items = [
                Item(id: "0", label: "Zero", isOn: false),
                Item(id: "1", label: "One", isOn: true),
                Item(id: "2", label: "Two", isOn: false)
            ]
        }
    }
    
    struct MyView: View {
        @ObservedObject var service: Service
    
        var body: some View {
            List {
                ForEach(service.items.indices, id: \.self) { index in
                    Section(header: Text(self.service.items[index].label)) {
                        Toggle(isOn: self.$service.items[index].isOn) {
                            Text("isOn")
                        }
                    }
                }
            }
            .listStyle(GroupedListStyle())
        }
    }
    

    更新:为什么要使用索引?

    在这个例子中,我们需要从模型中的每个 Item 中获取两个东西:

    1. label 属性的String 值,用于文本视图。
    2. Binding&lt;Bool&gt; 来自 isOn 属性,用于切换视图。

    (请参阅 this answer 我解释绑定的地方。)

    我们可以通过直接遍历项目来获取标签值:

    ForEach(service.items) { (item: Item) in
        Section(header: Text(item.label)) {
        ...
    }
    

    但 Item 结构不包含绑定。如果您尝试引用 Toggle(isOn: item.$isOn),则会收到错误消息:“'Item' 类型的值没有成员 '$isOn'。”

    相反,绑定由@ObservedObject 属性包装器在顶层提供,这意味着$ 必须位于service 之前。但是如果我们从service 开始,我们需要一个索引(我们不能在 ForEach 结构中声明中间变量,所以我们必须内联计算它):

    ForEach(service.items) { (item: Item) in
        Section(header: Text(item.label)) {
            Toggle(isOn: self.$service.items[self.service.items.firstIndex(of: item)!].isOn) {
            // This computes the index       ^--------------------------------------^
                Text("isOn")
            }
        }
    }
    

    哦,找到索引的比较意味着 Item 必须符合 Equatable。而且,最重要的是,因为我们循环遍历 ForEach 中的所有项目,然后再循环 .firstIndex(of:),我们将代码从 O(n) 复杂度转换为 O(n^2),这意味着它将当数组中有大量 Item 时,运行速度会慢得多。

    所以我们只使用索引。只是为了衡量,

    ForEach(service.items.indices, id: \.self) { index in
    

    等价于

    ForEach(0..<service.items.count, id: \.self) { index in
    

    【讨论】:

    • 这很有趣!为什么它适用于索引而不适用于实际对象?
    • @user1366265 我添加到我的答案中,解释了为什么最容易迭代索引。您可以迭代项目,这并不好玩。 ?
    猜你喜欢
    • 2020-08-10
    • 2021-11-24
    • 2020-07-20
    • 2021-02-10
    • 1970-01-01
    • 2021-09-26
    • 2019-11-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多