我有一个简单的 SwiftUI 应用程序,带有侧边栏和详细信息窗格,使用
NavigationSplitView
实现。它工作正常,除非(以编程方式)取消选择列表中选定的项目时,应用程序崩溃并显示无用的堆栈跟踪(主要是___lldb_unamed_symbol
)。具体来说,当 selectedItem = nil
时,应用程序崩溃。
可以使用以下程序重现该问题:
import SwiftUI
struct Person: Hashable {
var firstName: String
var lastName: String
}
struct PersonDetailView: View {
var body: some View {
if let selectedPersonBinding = Binding($selection) {
VStack {
TextField("First Name", text: selectedPersonBinding.firstName)
TextField("Last Name", text: selectedPersonBinding.lastName)
}
.padding()
}
}
@Binding var selection: Person?
}
struct ContentView: View {
var body: some View {
NavigationSplitView {
List(people, id: \.self, selection: $selectedPerson) {
Text($0.firstName)
}
.toolbar {
Button("Deselect") {
selectedPerson = nil
}
}
} detail: {
PersonDetailView(selection: $selectedPerson)
}
.onAppear() {
selectedPerson = people[0]
}
}
let people = [
Person(firstName: "Steve", lastName: "Jobs"),
Person(firstName: "Steve", lastName: "Wozniak"),
Person(firstName: "Ronald", lastName: "Wayne")
]
@State var selectedPerson: Person?
}
在 Mac 或 iPad 上运行该程序,单击“取消选择”按钮,应用程序将崩溃。
我推断它与详细视图与
selectedPerson
的绑定有关,因为如果删除该绑定,问题就会消失,但我不明白为什么它会因该绑定而崩溃。
这似乎与这篇文章中的问题相同,这在很大程度上被视为 SwiftUI bug。每当您使用
Binding
初始化程序将 Binding<T?>
转换为 Binding<T>?
,然后将生成的 Binding
传递到 if
语句中的某个其他视图时,就会发生这种情况。一个最小的可重复示例是:
struct ContentView: View {
@State private var s: String? = "Foo"
var body: some View {
if let binding = Binding($s) {
TextField("Foo", text: binding)
}
Button("Foo") {
s = nil
}
}
}
也就是说,在您的具体情况下,您的代码还有其他错误,如果您做得正确,您将避免此错误。
people
数组中的人员姓名,但 people
是一个 let
常量。 people
应改为 @State var
。$selectedPerson
传递到详细视图,因此文本字段将会更改 selectedPerson
,而不是 people
中的人员。您应该传递像 $people[selectedIndex]
这样的绑定。\.self
作为 id,因此当任何 people
更改时,列表行的 id 也会更改,并且所有内容都会被销毁并重新创建。这是不希望的。 Person
应符合 Identifiable
。Person
作为选择类型。这意味着对所选人员的每次更改都会更改选择值。再次强调,这是不可取的。选择类型应为 Person.ID
。进行这些更改后,您将得到:
struct Person: Hashable, Identifiable {
var firstName: String
var lastName: String
let id = UUID()
}
struct PersonDetailView: View {
var body: some View {
VStack {
TextField("First Name", text: $selection.firstName)
TextField("Last Name", text: $selection.lastName)
}
.padding()
}
@Binding var selection: Person
}
struct ContentView: View {
var body: some View {
NavigationSplitView {
List(people, selection: $selectedPerson) {
Text($0.firstName)
}
.toolbar {
Button("Deselect") {
selectedPerson = nil
}
}
} detail: {
if let selectedPerson, let index = people.firstIndex(where: { $0.id == selectedPerson }) {
PersonDetailView(selection: $people[index])
}
}
}
@State var people = [
Person(firstName: "Steve", lastName: "Jobs"),
Person(firstName: "Steve", lastName: "Wozniak"),
Person(firstName: "Ronald", lastName: "Wayne")
]
@State var selectedPerson: UUID?
}