SwiftUI 从另一个视图重新排序列表动态部分
SwiftUI Reorder list dynamic sections from another view
我有一个简单的 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
中尝试移动部分时出现此错误:“尝试为单元格创建两个动画”。可能是因为章节的顺序发生了变化。
如何更改部分的顺序?
SwiftUI 1.0 的可能解决方案
我找到了一种解决方法,通过添加 .id(UUID())
:
来禁用 List
的动画
var list: some View {
List {
...
}
.id(UUID())
}
但是,这会扰乱使用 NavigationLink(destination:tag:selection:)
创建的 NavigationLinks 的过渡动画:。
所有其他动画(如 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)
}
}
这个场景的问题被重现了很多次ViewModel
,所以在sheet中所做的修改就丢失了。 (奇怪的是,在带有 StateObject 的 SwiftUI 2.0 中,这些更改也丢失了,EditButton 根本不起作用。)
无论如何。看起来这里是找到的解决方法。这个想法是打破面试依赖性(绑定)并使用纯数据将它们显式传递给 sheet 和 return 它们显式返回。
测试并使用 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 = [=10=]
}
}
}
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: [=10=], toOffset: )
}
}
.navigationBarItems(trailing: EditButton())
}
.onDisappear {
self.callback(self.sections)
}
}
}
我有一个简单的 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
中尝试移动部分时出现此错误:“尝试为单元格创建两个动画”。可能是因为章节的顺序发生了变化。
如何更改部分的顺序?
SwiftUI 1.0 的可能解决方案
我找到了一种解决方法,通过添加 .id(UUID())
:
List
的动画
var list: some View {
List {
...
}
.id(UUID())
}
但是,这会扰乱使用 NavigationLink(destination:tag:selection:)
创建的 NavigationLinks 的过渡动画:
所有其他动画(如 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)
}
}
这个场景的问题被重现了很多次ViewModel
,所以在sheet中所做的修改就丢失了。 (奇怪的是,在带有 StateObject 的 SwiftUI 2.0 中,这些更改也丢失了,EditButton 根本不起作用。)
无论如何。看起来这里是找到的解决方法。这个想法是打破面试依赖性(绑定)并使用纯数据将它们显式传递给 sheet 和 return 它们显式返回。
测试并使用 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 = [=10=]
}
}
}
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: [=10=], toOffset: )
}
}
.navigationBarItems(trailing: EditButton())
}
.onDisappear {
self.callback(self.sections)
}
}
}