Я изменил пример элементов по умолчанию из xcode, включив в него две модели: представление с запросом и подробное представление. В этом подробном представлении я не могу правильно переместить все элементы, только два верхних, см. прикрепленное видео.
Особенно странно, что какое-то перетаскивание работает, а какое-то нет. Оно меняется в зависимости от количества подпунктов в задаче. Модели представляют собой SwiftData и имеют отношение «один ко многим». Во многих отношениях проблема существует.
Как следует скорректировать код, чтобы все элементы можно было перетаскивать?
Ниже приведен весь код, необходимый для минимального примера. Я ориентируюсь на 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
}
}
Что касается проблемы с перетаскиванием, то, похоже, это какая-то проблема между SwiftData и SwiftUI. Похоже, что в некоторых сценариях есть обновление ToDo в базе данных, которое запускает обновление представления, а затем перетаскивание работает, но в некоторых других сценариях есть такого обновления нет, поэтому представление не обновляется.
@JoakimDanielson Спасибо! Однако возможно выполнить foreach по индексу, может ли это изменить порядок сортировки? Workout.haschanges возвращает true после перетаскивания, так что это может быть что-то.
Нет, вам не следует использовать ForEach для индексов.





Если вы просто хотите, чтобы повторный заказ «работал», не сохраняя заказ, вы можете использовать отдельный @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 Конечно! Обе версии у меня работают на iOS 17.4.
Мои результаты таковы, что я могу перемещать элемент только на одно место вверх или вниз. Когда я пытаюсь переместить его на два места вверх, он снова смещается назад... Вот так imgur.com/a/OROHbSS. Вы тоже испытываете это @Sweeper?
@ThomasDeLange Упс. Логика распределения заказов неправильная. Я обновил ответ.
Ах! Он изменял свой собственный список на ходу, что вызывало странное поведение. Спасибо за помощь.
«todo.items.move», это никогда не сработает, поскольку новый порядок не будет сохранен, поэтому каждый раз при обновлении представления элементы будут отображаться в случайном порядке, и этот порядок не будет прежним после перезапуска приложения. . Чтобы это работало, вам нужно новое свойство Item для порядка сортировки.