SwiftUI从另一个视图重新排序列表动态部分

3
我有一个简单的包含在ObservableObject中的部分列表(List)。我希望能够从另一个视图中对它们进行重新排序。
这是我的代码:
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 中遇到了这个错误:“尝试为单元格创建两个动画”。很可能是因为部分的顺序已更改。
我应该如何更改部分的顺序?

使用 Xcode 12 / iOS 14 - 没有错误,但也不起作用。 - Asperi
@Asperi,你知道我是否应该在更改部分顺序时禁用动画吗?或者也许有其他方法可以做到这一点? - pawello2222
不,这里的问题是不同的。只需为ViewModel添加init() { print("created") }并执行您的场景 - 您会看到的。在SwiftUI 2.0中使用@StateObject也没有更好。您遇到了非常有趣的SwiftUI错误。)) - Asperi
2个回答

2
这种情况的问题在于多次重现ViewModel,因此在表格中进行的修改就会丢失。(奇怪的是,在SwiftUI 2.0中使用StateObject时这些更改也会丢失,并且EditButton根本不起作用。)
无论如何,看起来已经找到了一种解决方法。思路是打破Interview依赖(绑定),并使用纯数据将其显式传递到sheet中,并从其中显式返回它们。
已测试并在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)
        }
    }
}

我检查了你的代码,你的解决方法看起来比我的好。然而,在iOS 13中它不起作用 - 我仍然会得到“尝试为单元格创建两个动画”的错误。再次感谢你的帮助 - 我会向苹果报告一个漏洞。 - pawello2222

0

SwiftUI 1.0的一种可能的解决方案

我发现了一个解决方案,通过添加.id(UUID())来禁用List的动画:

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

然而,这会影响使用NavigationLink(destination:tag:selection:)创建的导航链接的转换动画:Transition animation gone when presenting a NavigationLink in SwiftUI

而且所有其他动画(如onDelete)也都消失了。

更加hacky的解决方案是有条件地禁用列表动画:

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)
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接