Некоторые элементы списка невозможно редактировать с помощью .move в списке SwiftUI

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

Особенно странно, что какое-то перетаскивание работает, а какое-то нет. Оно меняется в зависимости от количества подпунктов в задаче. Модели представляют собой SwiftData и имеют отношение «один ко многим». Во многих отношениях проблема существует.

Как следует скорректировать код, чтобы все элементы можно было перетаскивать?

https://imgur.com/a/n1y7iXX

Ниже приведен весь код, необходимый для минимального примера. Я ориентируюсь на iOS 17.5, и это отображается как на предварительном просмотре, в симуляторе, так и на моем iPhone.

Модели

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

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

ItemListView (вот в чем проблема!)

struct ItemListView: View {
    @Bindable var todo: ToDo
    
    var body: some View {
        List {
            ForEach($todo.items) { $item in
                Text(item.timestamp.description)

            }
            .onMove { indexSet, offset in
                todo.items.move(fromOffsets: indexSet, toOffset: offset)
            }
        }
        
    }
}

Контентвиев

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    @Query var items: [ToDo]

    var body: some View {
        NavigationSplitView {
            List {
                ForEach(items) { item in
                    NavigationLink {
                        ItemListView(todo: item)
                    } label: {
                        Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
                    }
                }
                .onDelete(perform: deleteItems)
            }
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    EditButton()
                }
                ToolbarItem {
                    Button(action: addItem) {
                        Label("Add Item", systemImage: "plus")
                    }
                }
            }
        } detail: {
            Text("Select an item")
        }
    }

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

    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            for index in offsets {
                modelContext.delete(items[index])
            }
        }
    }
}

ПРИЛОЖЕНИЕ

@main
struct ListProjectApp: App {

    var sharedModelContainer: ModelContainer = {
        
        let schema = Schema([
            ToDo.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(ToDoContainer.create())
    }
}

actor ToDoContainer {
    
    @MainActor
    static func create() -> ModelContainer {
        let schema = Schema([
            ToDo.self,
            Item.self
        ])
        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

        let container = try! ModelContainer(for: schema, configurations: [modelConfiguration])
                
        
        let todo = ToDo(timestamp: Date())
        
        container.mainContext.insert(todo)

        let item1 = Item(timestamp: Date(), done: false)
        let item2 = Item(timestamp: Date(), done: false)
        let item3 = Item(timestamp: Date(), done: false)
        
        todo.items.append(item1)
        todo.items.append(item2)
        todo.items.append(item3)

        
        return container
    }
}

«todo.items.move», это никогда не сработает, поскольку новый порядок не будет сохранен, поэтому каждый раз при обновлении представления элементы будут отображаться в случайном порядке, и этот порядок не будет прежним после перезапуска приложения. . Чтобы это работало, вам нужно новое свойство Item для порядка сортировки.

Joakim Danielson 23.05.2024 17:00

Что касается проблемы с перетаскиванием, то, похоже, это какая-то проблема между SwiftData и SwiftUI. Похоже, что в некоторых сценариях есть обновление ToDo в базе данных, которое запускает обновление представления, а затем перетаскивание работает, но в некоторых других сценариях есть такого обновления нет, поэтому представление не обновляется.

Joakim Danielson 23.05.2024 17:07

@JoakimDanielson Спасибо! Однако возможно выполнить foreach по индексу, может ли это изменить порядок сортировки? Workout.haschanges возвращает true после перетаскивания, так что это может быть что-то.

ThomasDeLange 23.05.2024 17:26

Нет, вам не следует использовать ForEach для индексов.

Joakim Danielson 23.05.2024 17:33
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
86
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Если вы просто хотите, чтобы повторный заказ «работал», не сохраняя заказ, вы можете использовать отдельный @State для отслеживания заказа.

struct ItemListView: View {
    let toDo: ToDo
    
    @State var items: [Item] = []
    
    var body: some View {
        List {
            ForEach(items) { item in
                Text(item.timestamp.description)

            }
            .onMove { indexSet, offset in
                items.move(fromOffsets: indexSet, toOffset: offset)
            }
        }
        .onAppear {
            items = toDo.items
        }
    }
}

Если вы хотите сохранить порядок, вам следует добавить свойство order к Item, по которому можно будет сортировать. Также добавьте обратную связь, чтобы можно было легко получить элементы, принадлежащие ToDo, в Query.

@Model
final class ToDo {
    var timestamp: Date
    
    @Relationship(inverse: \Item.toDo)
    var items: [Item]
    
    init(timestamp: Date) {
        self.timestamp = timestamp
        self.items = []
    }
}

@Model
final class Item {
    var timestamp: Date
    var done: Bool
    var order = 0
    var toDo: ToDo?
    
    init(timestamp: Date, done: Bool) {
        self.timestamp = timestamp
        self.done = done
    }
}

В onMove рассчитайте новый порядок и обновите свойства order.

struct ItemListView: View {
    let toDo: ToDo
    
    @Query var items: [Item]
    
    init(toDo: ToDo) {
        self.toDo = toDo
        let id = toDo.persistentModelID
        self._items = Query(filter: #Predicate<Item> {
            $0.toDo?.persistentModelID == id
        }, sort: \.order)
    }
    
    var body: some View {
        List {
            ForEach(items) { item in
                Text(item.timestamp.description)

            }
            .onMove { indexSet, offset in
                // I'm setting the order properties of all the Items,
                // but there is probably a faster way to calculate the new order
                // that only sets the properties that needs to be set
                var copy = items
                copy.move(fromOffsets: indexSet, toOffset: offset)
                for (item, order) in zip(copy, 0..<copy.count) {
                    item.order = order
                }
                // context.save() here if auto save is off
            }
        }
    }
}

Спасибо за ответ! Смогли ли вы провести это самостоятельно? Я не могу заставить это работать. Это приводит к примерно такому же поведению.

ThomasDeLange 27.05.2024 18:42

@ThomasDeLange Конечно! Обе версии у меня работают на iOS 17.4.

Sweeper 28.05.2024 02:06

Мои результаты таковы, что я могу перемещать элемент только на одно место вверх или вниз. Когда я пытаюсь переместить его на два места вверх, он снова смещается назад... Вот так imgur.com/a/OROHbSS. Вы тоже испытываете это @Sweeper?

ThomasDeLange 29.05.2024 10:36

@ThomasDeLange Упс. Логика распределения заказов неправильная. Я обновил ответ.

Sweeper 29.05.2024 11:02

Ах! Он изменял свой собственный список на ходу, что вызывало странное поведение. Спасибо за помощь.

ThomasDeLange 29.05.2024 11:22

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