Приложение SwiftUI, использующее NavigationSplitView, аварийно завершает работу, когда выбор списка боковой панели очищается

У меня есть простое приложение 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, поскольку проблема исчезнет, ​​если я удалю эту привязку, но я не могу понять, почему он должен аварийно завершить работу с этой привязкой.

Это ответ на ваш вопрос: stackoverflow.com/questions/57021722/swiftui-optional-textfi‌​eld

workingdog support Ukraine 15.08.2024 01:15

Спасибо за комментарий. Это на самом деле не помогает, потому что не позволяет двустороннюю привязку к подсвойствам Person.

Andrew Madsen 15.08.2024 04:50
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Похоже, это та же проблема, что и в этом посте, которая в основном рассматривается как ошибка SwiftUI. Это происходит всякий раз, когда вы используете инициализатор 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 в качестве идентификатора, поэтому при изменении любого из people идентификаторы строк списка изменяются, и все уничтожается и воссоздается. Это нежелательно. 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?
}

Спасибо! Какой чудесно ясно объясненный ответ. Я тоже не осознавал, что можно связать индекс с таким массивом.

Andrew Madsen 15.08.2024 19:39

Другие вопросы по теме