Как написать запрос с предикатом в подробном представлении SwiftUI

Я пытаюсь написать @Query с пользовательским предикатом в подробном представлении SwiftUI. Когда я щелкаю элементы в списке, приложение зависает, и использование памяти постоянно увеличивается линейно.

Если я поставлю предикат на @Query в главном представлении, приложение будет работать нормально.

Все это основано на шаблонном приложении SwiftUI+SwiftData с минимальными изменениями в подробном представлении:

Подробный вид (где возникает проблема):

struct DetailView: View {
    var item: Item

    // Issue occurs when a Query with a filter is set. Without the filter it works fine
    // contents of Predicate doesn't matter so long as it's valid
    @Query(filter: #Predicate<Item> { item in
        true
    }) private var items: [Item]

    var body: some View {
        VStack {
            Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))")
            Text("Count: \(items.count)")
        }
    }
}

Посмотреть список:

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext

    // If this Query is modified, no issue
    @Query private var items: [Item]

    var body: some View {
        NavigationSplitView {
            List {
                ForEach(items) { item in
                    NavigationLink {
                        DetailView(item: item)
                    } label: {
                        Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
                    }
                }
            }
        } detail: {
            Text("Select an item")
        }
    }

    private func addItem() {
        withAnimation {
            let newItem = Item(timestamp: Date())
            modelContext.insert(newItem)
        }
    }
}

Модель данных:

@Model
final class Item {
    var timestamp: Date
    
    init(timestamp: Date) {
        self.timestamp = timestamp
    }
}

Время работы приложения:

@main
struct TestSwiftDataApp: App {
    var sharedModelContainer: ModelContainer = {
        let schema = Schema([
            Item.self,
        ])
        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

        do {
            return try ModelContainer(for: schema, configurations: [modelConfiguration])
        } catch {
            fatalError("Could not create ModelContainer: \(error)")
        }
    }()

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(sharedModelContainer)
    }
}

Я не уверен, есть ли недостаток производительности, но вы всегда можете просто добавить свою собственную вычисляемую переменную, которая принимает все нефильтрованные результаты, а затем самостоятельно отфильтровать ее вручную.

User45i6h45ih3455 09.07.2024 05:58

навигационные ссылки отображаются до того, как они будут нажаты

User45i6h45ih3455 09.07.2024 06:04

Я думаю, что, возможно, решение состоит в том, чтобы просто вывести запрос из подробного представления и передать количество элементов в качестве аргумента конструктора.

User45i6h45ih3455 09.07.2024 06:07

@User45i6h45ih3455 User45i6h45ih3455 это надуманный пример для простоты.

Ezekiel 09.07.2024 06:18

Это не документированный способ использования NavigationSplitView. Можете ли вы использовать NavigationSplitView, как описано в документации ? То есть ваш List должен иметь selection:, а разделенное представление должно автоматически отображать подробное представление в конструкторе представлений detail: на основе выбора. Не следует использовать пункт назначения NavigationLink.

Sweeper 09.07.2024 06:27

Кроме того, я не могу воспроизвести это на iOS 17.4. Все работает как положено - приложение не зависает.

Sweeper 09.07.2024 06:34

Мне удалось воспроизвести это на macOS, и я мог видеть в консоли с активным ведением журнала отладки Core Data, что одна и та же выборка выполнялась снова и снова, когда я нажимал на элемент на боковой панели. Я также могу убедиться, что изменение реализации NavigationSplitView, предложенное @Sweeper, устраняет эту проблему.

Joakim Danielson 09.07.2024 14:05

Я забыл сказать, что при воспроизведении проблемы мне удалось «исправить» ее так же, как это сделал ОП, удалив предикат.

Joakim Danielson 09.07.2024 14:14

Еще одно исследование: при использовании NavigationLink я вижу, что в консоли выполняется одна выборка, но с вариантом выбора (без NavigationLink) я вижу две выборки. Когда я добавляю let _ = Self._printChanges() в тело каждого представления, я вижу, что разница заключается в том, что при выборе элемента на боковой панели изменение свойства выбора также запускает выполнение выборки, отличной от той, что в DetailView. Для меня это похоже на ошибку, но, безусловно, на данный момент нужно знать, если запрос возвращает много объектов.

Joakim Danielson 09.07.2024 14:33

@Sweeper Итак, в проекте, который вдохновил меня на это, я фактически использовал NavigationStack вместо NavigationSplitView. И для этого проекта, и для примера кода здесь кажется, что переход на использование navigationDestination решил проблемы? Если вы хотите написать ответ, предполагая, что я приму его

Ezekiel 10.07.2024 03:26

И да, когда я использую Self._printChanges(), он просто говорит, что @self менялся снова и снова.

Ezekiel 10.07.2024 03:26

Обратите внимание: хотя это правда, что это исправлено, я продолжаю сталкиваться с этой проблемой снова с помощью дополнительных навигационных ссылок... расстраивает.

Ezekiel 10.07.2024 03:51
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать 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
12
74
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Это не документированный способ использования NavigationSplitView. Вам следует использовать переключатель List, чтобы контролировать отображение подробного представления.

@Query private var items: [Item]
@State private var selectedItem: PersistentIdentifier?

var body: some View {
    NavigationSplitView {
        List(selection: $selectedItem) {
            ForEach(items) { item in
                NavigationLink(value: item.id) {
                    Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
                }
            }
        }
    } detail: {
        if let selectedItem, let item: Item = modelContext.registeredModel(for: selectedItem) {
            DetailView(item: item)
        } else {
            Text("Select an item")
        }
    }

Если вместо этого вы используете NavigationStack, использование navigationDestination(for:) на основе значения решает проблему.

NavigationStack {
    List {
        ForEach(items) { item in
            NavigationLink(value: item) {
                Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
            }
        }
    }
    .navigationDestination(for: Item.self) { item in
        DetailView(item: item)
    }
}

Решение NavigationStack устранило мою проблему. Есть ли причина, по которой вы решили получить его на основе идентификатора вместо прямой передачи элемента?

Ezekiel 10.07.2024 04:08

@Ezekiel В версии NavigationSplitViewForEach(item) использует свойство id для тегов строк списка, и выбор List основан на этом, так что это более естественно. Для NavigationStack действительно незачем это делать - просто копипастить удобнее :)

Sweeper 10.07.2024 04:13

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