SwiftUI Изменить порядок динамических разделов списка из другого представления

У меня есть простой List с разделами, которые хранятся внутри ObservableObject. Я бы хотел изменить их порядок с другой точки зрения.

Это мой код:

class ViewModel: ObservableObject {
    @Published var sections = ["S1", "S2", "S3", "S4"]
    
    func move(from source: IndexSet, to destination: Int) {
        sections.move(fromOffsets: source, toOffset: destination)
    }
}
struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()
    @State var showOrderingView = false

    var body: some View {
        VStack {
            Button("Reorder sections") {
                self.showOrderingView = true
            }
            list
        }
        .sheet(isPresented: $showOrderingView) {
            OrderingView(viewModel: self.viewModel)
        }
    }

    var list: some View {
        List {
            ForEach(viewModel.sections, id: \.self) { section in
                Section(header: Text(section)) {
                    ForEach(0 ..< 3, id: \.self) { _ in
                        Text("Item")
                    }
                }
            }
        }
    }
}
struct OrderingView: View {
    @ObservedObject var viewModel: ViewModel

    var body: some View {
        NavigationView {
            List {
                ForEach(viewModel.sections, id: \.self) { section in
                    Text(section)
                }
                .onMove(perform: viewModel.move)
            }
            .navigationBarItems(trailing: EditButton())
        }
    }
}

Но в OrderingView при попытке переместить разделы я получаю такую ​​ошибку: Попытка создать две анимации для ячейки. Скорее всего, потому, что изменился порядок разделов.

Как изменить порядок разделов?


person pawello2222    schedule 20.07.2020    source источник
comment
С Xcode 12 / iOS 14 - ошибок нет, но тоже не работает.   -  person Asperi    schedule 20.07.2020
comment
@Asperi Знаете ли вы, следует ли мне как-то отключать анимацию только для изменения порядка разделов? Или, может быть, есть другой способ сделать это?   -  person pawello2222    schedule 20.07.2020
comment
Нет, здесь проблема в другом. Просто добавьте init() { print("created") } для ViewModel и выполните свой сценарий - вы увидите. С @StateObject в SwiftUI 2.0 не лучше. Вы столкнулись с очень интересной ошибкой SwiftUI. ))   -  person Asperi    schedule 20.07.2020


Ответы (2)


Проблема этого сценария повторяется много раз ViewModel, поэтому изменения, внесенные в лист, просто теряются. (Странно то, что в SwiftUI 2.0 с StateObject эти изменения тоже теряются, а EditButton вообще не работает.)

В любом случае. Похоже, здесь есть обходной путь. Идея состоит в том, чтобы разорвать зависимость (привязку) интервью и работать с чистыми данными, явно передавая их в лист, и явно возвращать их обратно из него.

Протестировано и работает с Xcode 12 / iOS 14, но я старался избегать использования функций SwiftUI 2.0.

class ViewModel: ObservableObject {
    @Published var sections = ["S1", "S2", "S3", "S4"]

    func move(from source: IndexSet, to destination: Int) {
        sections.move(fromOffsets: source, toOffset: destination)
    }
}

struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()
    @State var showOrderingView = false

    var body: some View {
        VStack {
            Button("Reorder sections") {
                self.showOrderingView = true
            }

            list
        }
        .sheet(isPresented: $showOrderingView) {
            OrderingView(sections: viewModel.sections) {
                self.viewModel.sections = $0
            }
        }
    }

    var list: some View {
        List {
            ForEach(viewModel.sections, id: \.self) { section in
                Section(header: Text(section)) {
                    ForEach(0 ..< 3, id: \.self) { _ in
                        Text("Item")
                    }
                }
            }
        }
    }
}

struct OrderingView: View {
    @State private var sections: [String]
    let callback: ([String]) -> ()

    init(sections: [String], callback: @escaping ([String]) -> ())
    {
        self._sections = State(initialValue: sections)
        self.callback = callback
    }

    var body: some View {
        NavigationView {
            List {
                ForEach(sections, id: \.self) { section in
                    Text(section)
                }
                .onMove {
                    self.sections.move(fromOffsets: $0, toOffset: $1)
                }
            }
            .navigationBarItems(trailing: EditButton())
        }
        .onDisappear {
            self.callback(self.sections)
        }
    }
}
person Asperi    schedule 20.07.2020
comment
Я проверил ваш код, и ваш обходной путь выглядит лучше, чем мой. Однако в iOS 13 это не работает - я все еще получаю ошибку Попытка создать две анимации для ячейки. Еще раз спасибо за вашу помощь - я сообщу об ошибке в Apple. - person pawello2222; 20.07.2020

Возможное решение для SwiftUI 1.0

Я нашел способ отключить анимацию для List, добавив .id(UUID()):

var list: some View {
    List {
        ...
    }
    .id(UUID())
}

Однако это нарушает анимацию перехода для ссылок навигации, созданных с помощью NavigationLink(destination:tag:selection:): Анимация перехода пропала при представлении ссылки NavigationLink в SwiftUI.

И все остальные анимации (например, onDelete) также отсутствуют.

Еще более хитрое решение - условно отключить анимацию списка:

class ViewModel: ObservableObject {
    ...
    @Published var isReorderingSections = false
    ...
}
struct OrderingView: View {
    @ObservedObject var viewModel: ViewModel

    var body: some View {
        NavigationView {
            ...
        }
        .onAppear {
            self.viewModel.isReorderingSections = true
        }
        .onDisappear {
            self.viewModel.isReorderingSections = false
        }
    }
}
struct ContentView: View {
    ...
    var list: some View {
        List {
            ...
        }
        .id(viewModel.isReorderingSections ? UUID().hashValue : 1)
    }
}
person pawello2222    schedule 20.07.2020