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)
}
}
}