【问题标题】:SwiftUI Newbie: Binding, Toggles, and Fatal Error: Index out of rangeSwiftUI 新手:绑定、切换和致命错误:索引超出范围
【发布时间】:2021-09-04 13:36:46
【问题描述】:

对 SwiftUI 和 Swift 非常陌生,并试图将我的头脑围绕在绑定上。

具体来说,我有一个 Tasks 的 ForEach 循环,它显示了一个新的 TaskRow Tasks。该任务行有两个操作:

  • 用于删除相关行中显示的任务的按钮
  • 用于将任务状态从“已完成”更改为“未完成”的开关

如果没有切换,每当我删除任务时,视图都会正常更新。但是在切换存在的情况下,删除任务会引发:

Thread 1: Fatal error: Index out of range

错误。

我认为这与 $isSet 绑定到 self.$data 有关,但我不知道如何以切换将更新的方式传递该数据。代码如下,深表感谢:

TaskList.swift

import SwiftUI

struct TaskList: View {
    @ObservedObject private var data = TaskData()
    @State private var showFinished: Bool = false

    
    var visibleTasks: [Task] {
        data.tasks.filter { task in
            showFinished || !task.finished
        }
    }
    
    var body: some View {
     
        VStack {
            Toggle(isOn: $showFinished) {
                Text("Show Finished")
            }
            
            ForEach(data.tasks.indices, id: \.self) { index in
                    TaskRow(
                            isSet: self.$data.tasks[index].finished,
                            task: self.data.tasks[index],
                            onChange: { self.data.save() },
                            onDelete: { self.deleteTask(task_index: index)})
                
                    Divider()
                }
            AddTask(data: data)
        
        }
        .padding()
        .onAppear {
            data.load()
        }
        
    }
    
    func deleteTask(task_index: Int) {
        self.data.tasks.remove(at: self.data.tasks[task_index].id)
    }
}

TaskRow.swift

导入 SwiftUI

struct TaskRow: View {
    @Binding var isSet: Bool
    @State var task: Task
    var onChange: () -> ()
    var onDelete: () -> ()
  
    var body: some View {
        HStack() {
            Toggle("", isOn: $isSet)
                .onChange(of: isSet) { _isOn in
                   onChange()
            }
            Text(task.name)
            Spacer()
            Button(action: onDelete) {
                Image(systemName: "minus.circle")
            }
        }
        .scaledToFit()
        
    }
}

【问题讨论】:

  • 你应该看看 WWDC 2021 的 Demystify SwiftUI 视频。你有一些小错误。
  • 嗨,PlankTon,cn,请不要将数据设为私有 var,而是设为 var?然后检查 data.tasks.indices 是否仍然没有更新。
  • @MacUserT 为什么你认为它可以改变任何东西?
  • 算了,它没有。尽管如此,ObservedObject 是一个 var,而不是私有 var。
  • @MacUserT 如果您不需要为此变量生成可选的初始值设定项参数,它可以很容易地私有化。

标签: swift swiftui


【解决方案1】:

在 iOS 15 中,您可以执行以下操作:

ForEach(Array($data.tasks.enumerated()), id: \.element.wrappedValue) { (index, $task) in
    TaskRow(
        isSet: $task.finished,
        task: task,
        onChange: { self.data.save() },
        onDelete: { self.deleteTask(task_index: index) }
    )

    Divider()
}

对于 iOS 14,您会收到以下警告(以及其他警告):

“绑定”与“序列”的一致性仅适用于 iOS 15.0 或更高版本

【讨论】:

    【解决方案2】:

    当您通过indexes 枚举然后通过ForEach 内的索引获取绑定时会发生这种情况。这是不安全的,因为在状态更新期间 ID 不正确。我为这种情况创建了ForEachIndexed

    struct ForEachIndexed<Data: RandomAccessCollection, RowContent: View, ID: Hashable>: View, DynamicViewContent where Data.Index: Hashable {
        var data: [(Data.Index, Data.Element)] {
            forEach.data
        }
    
        let forEach: ForEach<[(Data.Index, Data.Element)], ID, RowContent>
    
        init(_ data: Binding<Data>,
             @ViewBuilder rowContent: @escaping (Data.Index, Binding<Data.Element>) -> RowContent
        ) where
        Data.Element: Identifiable,
        Data.Element.ID == ID,
        Data: MutableCollection {
            forEach = ForEach(
                Array(zip(data.wrappedValue.indices, data.wrappedValue)),
                id: \.1.id
            ) { i, _ in
                rowContent(i, Binding(get: {
                    data.wrappedValue[i]
                }, set: {
                    data.wrappedValue[i] = $0
                }))
            }
        }
    
        var body: some View {
            forEach
        }
    }
    

    用法:

    ForEachIndexed($data.tasks) { index, taskBinding in
        TaskRow(
            isSet: taskBinding.finished,
            task: taskBinding.wrappedValue,
            onChange: { self.data.save() },
            onDelete: { self.deleteTask(task_index: index)})
    
        Divider()
    }
    

    【讨论】:

      猜你喜欢
      • 2020-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-20
      • 2016-08-15
      • 1970-01-01
      相关资源
      最近更新 更多