Ios “我怎样才能解决问题?”;“索引超出范围”;在SwiftUI中查看多维视图

Ios “我怎样才能解决问题?”;“索引超出范围”;在SwiftUI中查看多维视图,ios,swift,xcode,swiftui,Ios,Swift,Xcode,Swiftui,在问下一个“索引超出范围”的问题之前,我尽了最大努力,因为一般来说,我理解为什么会出现索引超出范围的问题,但这个具体问题让我发疯: struct Parent: Identifiable { let id = UUID() let name: String var children: [Child]? } struct Child: Identifiable { let id = UUID() let name: String var puppe

在问下一个“索引超出范围”的问题之前,我尽了最大努力,因为一般来说,我理解为什么会出现索引超出范围的问题,但这个具体问题让我发疯:

struct Parent: Identifiable {
    let id = UUID()
    let name: String
    var children: [Child]?
}

struct Child: Identifiable {
    let id = UUID()
    let name: String
    var puppets: [Puppet]?
}

struct Puppet: Identifiable {
    let id = UUID()
    let name: String
}

class AppState: ObservableObject {
    @Published var parents: [Parent]
    init() {
        self.parents = [
            Parent(name: "Foo", children: [Child(name: "bar", puppets: [Puppet(name: "Tom")])]),
            Parent(name: "FooBar", children: [Child(name: "foo", puppets: nil)]),
            Parent(name: "FooBar", children: nil)
        ]
    }
}


struct ContentView: View {
    @EnvironmentObject var appState: AppState
    var body: some View {
        NavigationView {
            VStack {
                List {
                    ForEach (appState.parents.indices, id: \.self) { parentIndex in
                        NavigationLink (destination: ChildrenView(parentIndex: parentIndex).environmentObject(self.appState)) {
                            Text(self.appState.parents[parentIndex].name)
                        }
                    }
                    .onDelete(perform: deleteItem)
                }
                Button(action: {
                    self.appState.parents.append(Parent(name: "Test", children: nil))
                }) {
                    Text("Add")
                }
                .padding(.bottom, 15)
            }
        .navigationBarTitle(Text("Parents"))
        }
    }
    private func deleteItem(at indexSet: IndexSet) {
        self.appState.parents.remove(atOffsets: indexSet)
    }
}


struct ChildrenView: View {
    @EnvironmentObject var appState: AppState
    var parentIndex: Int
    var body: some View {
        let children = appState.parents[parentIndex].children
        return VStack {
            List {
                if (children?.indices != nil) {
                    ForEach (children!.indices, id: \.self) { childIndex in
                        NavigationLink (destination: PuppetsView(parentIndex: self.parentIndex, childIndex: childIndex).environmentObject(self.appState)) {
                            Text(children![childIndex].name)
                        }
                    }
                    .onDelete(perform: deleteItem)
                }
            }
            Button(action: {
                var children = self.appState.parents[self.parentIndex].children
                if (children != nil) {
                    children?.append(Child(name: "Teest"))
                } else {
                    children = [Child(name: "Teest")]
                }
            }) {
                Text("Add")
            }
            .padding(.bottom, 15)
        }
        .navigationBarTitle(Text("Children"))
    }
    private func deleteItem(at indexSet: IndexSet) {
        if (self.appState.parents[self.parentIndex].children != nil) {
            self.appState.parents[self.parentIndex].children!.remove(atOffsets: indexSet)
        }
    }
}


struct PuppetsView: View {
    @EnvironmentObject var appState: AppState
    var parentIndex: Int
    var childIndex: Int
    var body: some View {
        let child = appState.parents[parentIndex].children?[childIndex]
        return VStack {
            List {
                if (child != nil && child!.puppets?.indices != nil) {
                    ForEach (child!.puppets!.indices, id: \.self) { puppetIndex in
                        Text(self.appState.parents[self.parentIndex].children![self.childIndex].puppets![puppetIndex].name)
                    }
                    .onDelete(perform: deleteItem)
                }
            }
            Button(action: {
                var puppets = self.appState.parents[self.parentIndex].children![self.childIndex].puppets
                if (puppets != nil) {
                   puppets!.append(Puppet(name: "Teest"))
                } else {
                   puppets = [Puppet(name: "Teest")]
                }
            }) {
                Text("Add")
            }
            .padding(.bottom, 15)
        }
        .navigationBarTitle(Text("Puppets"))
    }
    private func deleteItem(at indexSet: IndexSet) {
        if (self.appState.parents[self.parentIndex].children != nil) {
            self.appState.parents[self.parentIndex].children![self.childIndex].puppets!.remove(atOffsets: indexSet)
        }
    }
}
我可以删除Foo和FooBar的两个子项,但当我首先删除child bar的木偶时,应用程序就会崩溃,如评论中所示


我不理解childIndex已经不存在了,但是我不明白为什么当没有带木偶的孩子时会再次构建视图。

可选链接的问题是,此行生成的结果类型是
child
,而不是
child?

appState.parents[parentIndex].children?[childIndex]
如果它不是可选的,则在未检查
childIndex
是否有效的情况下,您不能在
childIndex]
上调用
puppets

// this will crash when childIndex is out of range
appState.parents[parentIndex].children?[childIndex].puppets?.indices
我建议使用
safeIndex
下标来访问可能的空元素:

var body: some View {

    let child = appState.parents[safeIndex: parentIndex]?.children?[safeIndex: childIndex]
    return VStack {
        List {
            if (child != nil && child!.puppets?.indices != nil) {
                ForEach ((appState.parents[parentIndex].children?[childIndex].puppets!.indices)!, id: \.self) { puppetIndex in
                    Text(self.appState.parents[self.parentIndex].children![self.childIndex].puppets![puppetIndex].name)
                }
                .onDelete(perform: deleteItem)
            }
        }
    ...
}
为此,您需要一个
Array
扩展,该扩展允许以安全的方式访问数组元素(即返回
nil
,而不是抛出错误):

扩展数组{
公共下标(safeIndex:Int)->元素{
保护索引>=0,索引

注意:您需要对父视图执行相同的操作,因此总体而言,Paulw11的答案更清晰。

所有数组索引的引用对我来说都非常糟糕。使用数组索引还需要将各种对象向下传递到子视图

为了解决这个问题,我从更改模型开始—使它们成为类而不是结构,这样您就可以使它们成为
@observeObject
。它们还需要是可散列的
和可均衡的

我还向模型对象添加了
add
remove
函数,这样在添加/删除子对象/木偶时就不必担心索引问题。
remove
方法使用数组扩展来删除可识别的
对象,而无需知道索引

最后,我将
子数组
木偶数组
更改为非可选数组。
nil
可选数组和空的非可选数组之间的语义差别不大,但后者更容易处理

class Parent: ObservableObject, Hashable {

    static func == (lhs: Parent, rhs: Parent) -> Bool {
        lhs.id == rhs.id
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }

    let id = UUID()
    let name: String
    @Published var children: [Child]

    init(name: String, children: [Child]? = nil) {
        self.name = name
        self.children = children ?? []
    }

    func remove(child: Child) {
        self.children.remove(child)
    }

    func add(child: Child) {
        self.children.append(child)
    }
}

class Child: ObservableObject, Identifiable, Hashable {
    static func == (lhs: Child, rhs: Child) -> Bool {
        return lhs.id == rhs.id
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }

    let id = UUID()
    let name: String
    @Published var puppets: [Puppet]

    init(name: String, puppets:[Puppet]? = nil) {
        self.name = name
        self.puppets = puppets ?? []
    }

    func remove(puppet: Puppet) {
        self.puppets.remove(puppet)
    }

    func add(puppet: Puppet) {
        self.puppets.append(puppet)
    }
}

struct Puppet: Identifiable, Hashable {
    let id = UUID()
    let name: String
}

class AppState: ObservableObject {
    @Published var parents: [Parent]
    init() {
        self.parents = [
            Parent(name: "Foo", children: [Child(name: "bar", puppets: [Puppet(name: "Tom")])]),
            Parent(name: "FooBar", children: [Child(name: "foo", puppets: nil)])
        ]
    }
}

extension Array where Element: Identifiable {
    mutating func remove(_ object: Element) {
        if let index = self.firstIndex(where: { $0.id == object.id}) {
            self.remove(at: index)
        }
    }
}
整理好模型后,视图只需知道其特定项:

struct ContentView: View {
    @EnvironmentObject var appState: AppState
    var body: some View {
        NavigationView {
            VStack {
                List {
                    ForEach (appState.parents, id: \.self) {  parent in
                        NavigationLink (destination: ChildrenView(parent: parent)) {
                            Text(parent.name)
                        }
                    }
                    .onDelete(perform: deleteItem)
                }
                Button(action: {
                    self.appState.parents.append(Parent(name: "Test", children: nil))
                }) {
                    Text("Add")
                }
                .padding(.bottom, 15)
            }
            .navigationBarTitle(Text("Parents"))
        }
    }
    private func deleteItem(at indexSet: IndexSet) {
        self.appState.parents.remove(atOffsets: indexSet)
    }
}

struct ChildrenView: View {
    @ObservedObject var parent: Parent
    var body: some View {
        VStack {
            List {
                ForEach (self.parent.children, id: \.self) { child in
                    NavigationLink (destination: PuppetsView(child:child)) {
                        Text(child.name)
                    }
                }
                .onDelete(perform: deleteItem)
            }
            Button(action: {
                self.parent.add(child: Child(name: "Test"))
            }) {
                Text("Add")
            }
            .padding(.bottom, 15)
        }
        .navigationBarTitle(Text("Children"))
    }

    private func deleteItem(at indexSet: IndexSet) {
        let children = Array(indexSet).map { self.parent.children[$0]}
        for child in children {
            self.parent.remove(child: child)
        }
    }
}

struct PuppetsView: View {
    @ObservedObject var child: Child
    var body: some View {
        VStack {
            List {
                ForEach (child.puppets, id: \.self) { puppet in
                    Text(puppet.name)
                }
                .onDelete(perform: deleteItem)
            }
            Button(action: {
                self.child.add(puppet:Puppet(name: "Test"))
            })
            {
                Text("Add")
            }
            .padding(.bottom, 15)
        }
        .navigationBarTitle(Text("Puppets"))
    }

    func deleteItem(at indexSet: IndexSet) {
        let puppets = Array(indexSet).map { self.child.puppets[$0] }
        for puppet in puppets {
            self.child.remove(puppet:puppet)
        }
    }
}

只有在确定选项(
子项!
)存在时,才能开始展开它们。例如,在
deleteItem
中的
PuppetView
中,您不需要对照
nil
进行检查。您还可以提取
self.appState.parents[self.parentIndex].children![self.childIndex]。还可以将
设置为变量,这样您的代码将更易于阅读(和调试)。感谢您的提示。我更新了我的帖子。问题仍然存在,更改没有效果。使用
let child=appState.parents[parentIndex].children?[childIndex]
时,函数声明了一个不透明的返回类型,但在其正文中没有返回语句,从中可以推断基础类型,而不提取
appState.parents[parentIndex].children?[childIndex]
但添加了
appState.parents[parentIndex]。children?[childIndex]!=nil
我仍然得到超出范围的索引。感谢您使我的代码更具可读性,以便很快获得解决方案。我更新了我的原始帖子。@T.Karter我更新了我的答案,以便您可以安全地使用数组。但另一种解决方案更干净。除非您不想拥有
@ObservedObjects
,否则我建议您使用它。谢谢不管怎样。所有改进我的代码的东西都很有帮助!这是我第一次收到可以直接工作的复制粘贴解决方案。谢谢你。你的回答解决了我的问题,所以我会将它标记为已解决。但是代码与我的代码大不相同,而且是我从未见过的样式,因此我需要一些时间来完全理解它。但我会尽我最大的努力。Regard我想补充一点,这不仅仅是一个答案,这激励着我,让我在iOS中越来越好,并向我展示,我落后了多远lol谢谢你againI还有一个问题:我如何使
Parent
符合
Codable
,以便我可以将appState保存到
UserDefaults
?您好。您的问题已经得到了回答。应该通过打开一个新问题来询问评论中的这个附加问题。这样其他人就可以找到它。评论中的文本不会以与问题标题相同的突出程度出现在搜索中。这也为回答者提供了获得进一步声誉的机会(你也是。)说得好。给你。
struct ContentView: View {
    @EnvironmentObject var appState: AppState
    var body: some View {
        NavigationView {
            VStack {
                List {
                    ForEach (appState.parents, id: \.self) {  parent in
                        NavigationLink (destination: ChildrenView(parent: parent)) {
                            Text(parent.name)
                        }
                    }
                    .onDelete(perform: deleteItem)
                }
                Button(action: {
                    self.appState.parents.append(Parent(name: "Test", children: nil))
                }) {
                    Text("Add")
                }
                .padding(.bottom, 15)
            }
            .navigationBarTitle(Text("Parents"))
        }
    }
    private func deleteItem(at indexSet: IndexSet) {
        self.appState.parents.remove(atOffsets: indexSet)
    }
}

struct ChildrenView: View {
    @ObservedObject var parent: Parent
    var body: some View {
        VStack {
            List {
                ForEach (self.parent.children, id: \.self) { child in
                    NavigationLink (destination: PuppetsView(child:child)) {
                        Text(child.name)
                    }
                }
                .onDelete(perform: deleteItem)
            }
            Button(action: {
                self.parent.add(child: Child(name: "Test"))
            }) {
                Text("Add")
            }
            .padding(.bottom, 15)
        }
        .navigationBarTitle(Text("Children"))
    }

    private func deleteItem(at indexSet: IndexSet) {
        let children = Array(indexSet).map { self.parent.children[$0]}
        for child in children {
            self.parent.remove(child: child)
        }
    }
}

struct PuppetsView: View {
    @ObservedObject var child: Child
    var body: some View {
        VStack {
            List {
                ForEach (child.puppets, id: \.self) { puppet in
                    Text(puppet.name)
                }
                .onDelete(perform: deleteItem)
            }
            Button(action: {
                self.child.add(puppet:Puppet(name: "Test"))
            })
            {
                Text("Add")
            }
            .padding(.bottom, 15)
        }
        .navigationBarTitle(Text("Puppets"))
    }

    func deleteItem(at indexSet: IndexSet) {
        let puppets = Array(indexSet).map { self.child.puppets[$0] }
        for puppet in puppets {
            self.child.remove(puppet:puppet)
        }
    }
}