【问题标题】:SwiftUI crashes when using a binding for deep nested values [duplicate]使用深度嵌套值的绑定时,SwiftUI 崩溃 [重复]
【发布时间】:2020-04-12 11:52:02
【问题描述】:

我正在努力使我的应用内容保持反应性并与远程数据保持同步。我的价值观是使用NavigationLink 探索的,但是当我深入到 2 页并且原始项目消失时,SwiftUI 会崩溃。

开始:

class App: ObservableObject {
    @Published var items: [Item] = [
        Item(id: "a", accounts: [
            Account(id: "1"),
            Account(id: "2"),
            Account(id: "3")
        ])
    ]
}
struct AllItems: View {
    @EnvironmentObject var app: App
    var body: some View {
        ForEach(app.items.indices, id: \.self) { index in
            NavigationLink(destination: ItemView(item: self.$app.items[index])) {
                Text(self.app.items[index].id)
            }
        }
    }
}
struct ItemView: View {
    @Binding var item: Item
    var body: some View {
        ForEach(item.accounts.indices, id: \.self) { index in
            NavigationLink(destination: AccountView(item: self.$item.accounts[index])) {
                Text(self.item.accounts[index].id)
            }
        }
    }
}
struct AccountView: View {
    @Binding var account: Account
    var body: some View {
        Text(account.id)
    }
}

但是,如果我在 AccountView 页面上并且 items 变为空,则应用程序将崩溃并显示“致命错误:索引超出范围”。

我最初没有使用绑定,但是当 Account 结构的内容发生变化时,AccountView 没有更新,视图没有改变。现在通过传递绑定,视图确实会正确更新,但如果列表大小发生变化,它可能会导致崩溃。

【问题讨论】:

  • 您可以考虑this topic 中的方法 - 原因与您的崩溃和这种情况下的原因相同。

标签: swiftui swiftui-navigationlink


【解决方案1】:

以下测试代码显示了一种可能的解决方法,以防止在项目变空时崩溃。

class Item {
let id: String
var accounts: [Account]
init(id: String, accounts: [Account]) {
    self.id = id
    self.accounts = accounts
}
}

struct Account {
let id: String
}

class App: ObservableObject {
@Published var items: [Item] = [
    Item(id: "item a", accounts: [
        Account(id: "account 1"),
        Account(id: "account 2"),
        Account(id: "account 3")
    ])
]
}

struct ItemView: View {
@EnvironmentObject var app: App
@State var itemNdx: Int
var body: some View {
    itemNdx < self.app.items.count
        ? AnyView(ForEach(app.items[itemNdx].accounts.indices, id: \.self) { index in
            NavigationLink(destination: AccountView(itemNdx: self.itemNdx, accountNdx: index)) {
                Text(self.app.items[self.itemNdx].accounts[index].id)
            }
        })
        : AnyView(EmptyView())
}
}

struct AccountView: View {
@EnvironmentObject var app: App
@State var itemNdx: Int
@State var accountNdx: Int
var body: some View {
    itemNdx < self.app.items.count
        ? AnyView(Text(app.items[itemNdx].accounts[accountNdx].id))
        : AnyView(EmptyView())
}
}

struct AllItems: View {
@EnvironmentObject var app: App
var body: some View {
    ForEach(app.items.indices, id: \.self) { index in
        NavigationLink(destination: ItemView(itemNdx: index)) {
            Text(self.app.items[index].id)
        }
    }
}
}

struct ContentView: View {
// the app is passed in from the SceneDelegate "var app = App()"
@EnvironmentObject var app: App

var body: some View {
    NavigationView {
        AllItems()
    }.navigationViewStyle(StackNavigationViewStyle())
    .onAppear(perform: loadData)
}

func loadData() {
    // this will set the items to empty in 10 seconds
    DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
        self.app.items = []
    }
}
}

【讨论】:

  • 这看起来很不幸
  • 不确定您的意思是“这看起来很不幸”。此代码有效,并提供了解决您的问题的方法。你试过了吗?
  • 我知道这有点挑剔,但我不喜欢能正常工作的代码。我更喜欢一个有意义且看起来合适的解决方案。我发现了一些其他的选项,它们各有利弊,但是这个解决方案看起来非常复杂,只是显示一个列表。
  • 很公平。我会对您找到的解决方案感兴趣。让我们知道。
  • 我想出了另一个适合我的解决方案:stackoverflow.com/a/61170013/359532
【解决方案2】:

是的,如果您的代码的另一部分中的项目为空,则应用程序将崩溃。 当涉及到并发/多线程/异步等......你必须自己照顾它。例如,使用 GCD 框架或组合函数式反应库来获取正在发生的事情的通知并采取行动。

【讨论】:

  • 你能给出一个起点/代码吗?
猜你喜欢
  • 2018-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多