清除侧边栏列表的选择时,使用 NavigationSplitView 的 SwiftUI 应用程序崩溃

问题描述 投票:0回答:1

我有一个简单的 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
的绑定有关,因为如果删除该绑定,问题就会消失,但我不明白为什么它会因该绑定而崩溃。

ios swift macos swiftui swiftui-list
1个回答
0
投票

这似乎与这篇文章中的问题相同,这在很大程度上被视为 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
        }
    }
}

也就是说,在您的具体情况下,您的代码还有其他错误,如果您做得正确,您将避免此错误。

  1. 我假设您希望文本字段修改
    people
    数组中的人员姓名,但
    people
    是一个
    let
    常量。
    people
    应改为
    @State var
  2. 您将
    $selectedPerson
    传递到详细视图,因此文本字段将会更改
    selectedPerson
    ,而不是
    people
    中的人员。您应该传递像
    $people[selectedIndex]
    这样的绑定。
  3. 您使用
    \.self
    作为 id,因此当任何
    people
    更改时,列表行的 id 也会更改,并且所有内容都会被销毁并重新创建。这是不希望的。
    Person
    应符合
    Identifiable
  4. 您正在使用
    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?
}
© www.soinside.com 2019 - 2024. All rights reserved.