【问题标题】:SwiftUI strange behavior when moving items between sections in a List在列表中的部分之间移动项目时的 SwiftUI 奇怪行为
【发布时间】:2020-01-08 05:57:39
【问题描述】:

所以我一直在尝试使用swiftUI 制作一个组件,它允许您在部分之间移动List 中的项目。

我准备了一个包含两个部分的示例:“第一个列表”和“第二个列表”。每当您点击一个项目时,它会交换部分。截图如下:

当我点击“第一个列表:1”时,它会正确移动到第二部分:

但是,由于我在部分中命名元素的方式,它的名称现在应该更改为“第二列表:1”(请参见下面的代码)。所以这很奇怪。但它变得陌生:

当我现在在第二部分点击“第一个列表:1”时,会发生这种情况:

它没有正确交换回来。它只是被重复了,但这次重复的名称实际上是正确的。

考虑到下面的代码,我不明白这是怎么可能的。似乎swiftUI 以某种方式重用了该项目,即使它重新呈现了视图?它似乎也重用了.onTapGesture 闭包,因为本应将项目放回第一部分的方法实际上从未被调用过。

知道这里发生了什么吗?以下是该问题的完整示例:

import SwiftUI
import Combine

struct TestView: View {
    @ObservedObject var viewModel: ViewModel

    class ViewModel: ObservableObject {
        let objectWillChange = PassthroughSubject<ViewModel,Never>()

        public enum List {
            case first
            case second
        }

        public var first: [Int] = []
        public var second: [Int] = []

        public func swap(elementWithIdentifier identifier: Int, from list: List) {
            switch list {
            case .first:
                self.first.removeAll(where: {$0 == identifier})
                self.second.append(identifier)
            case .second:
                print("Called")
                self.second.removeAll(where: {$0 == identifier})
                self.first.append(identifier)
            }

            self.objectWillChange.send(self)
        }

        init(first: [Int]) {
            self.first = first
        }
    }

    var body: some View {
        NavigationView {
            List {
                Section(header: Text("First List")) {
                    ForEach(self.viewModel.first, id: \.self) { id in
                        Text("First List: \(id)")
                            .frame(maxWidth: .infinity, maxHeight: .infinity)
                            .onTapGesture {
                                self.viewModel.swap(elementWithIdentifier: id, from: .first)
                        }
                    }
                }

                Section(header: Text("First List")) {
                    ForEach(self.viewModel.second, id: \.self) { id in
                        Text("Second List: \(id)")
                            .onTapGesture {
                                self.viewModel.swap(elementWithIdentifier: id, from: .second)
                        }
                    }
                }
            }
            .listStyle(GroupedListStyle())
            .navigationBarTitle(Text("Testing"))
        }.environment(\.editMode, .constant(EditMode.active))
    }
}

struct TestView_Preview: PreviewProvider {
    static var previews: some View {
        TestView(viewModel: TestView.ViewModel(first: [1, 2, 3, 4, 5]))
    }
}

【问题讨论】:

  • 您的var firstvar second 需要存在于外部模型对象中。 TestView 结构会在它所依赖的状态发生变化时由 SwiftUI 重新创建,因此您的数据会不断重新创建。要进行测试,请将您的数组作为全局变量移出,它应该可以按预期工作。
  • 它们在外部模型对象中。它们是类ViewModel 的属性,充当ObservedObject。还是我弄错了?
  • 我知道这是一个较旧的线程,但我很想看看你是否能够让它工作
  • 不,抱歉。很遗憾,我还没有找到解决方案

标签: swift swiftui


【解决方案1】:

我不知道为什么,但您的 swap 方法似乎对您添加的第一个对象做了一些奇怪的事情,因为如果第二个有效,那么您可能丢失了一些实例。

对了,每次在每个列表中添加一个新对象,是否需要removeAll?

 public function interchange (identifier elementWithIdentifier: Int, from list: List) {
        switch list {
        case .first:
            self.first.removeAll (where: {$ 0 == identifier})
            self.second.append (identifier)
        case .second:
            print ("Called")
            self.second.removeAll (where: {$ 0 == identifier})
            self.first.append (identifier)
        }

        self.objectWillChange.send (self)
    }

也许你的问题出在这个函数上,一切看起来都很好。

【讨论】:

    【解决方案2】:

    我发现自己处于类似情况,并找到了解决此问题的更优雅的解决方法。我认为问题出在 iOS13 上。在 iOS14 中,问题不再存在。下面详细介绍了一个适用于 iOS13 和 iOS14 的简单解决方案。

    试试这个:

    extension Int {
        var id:UUID {
            return UUID()
        }
    }
    

    然后在您的ForEach 参考\.id\.self.id 而不是\.self,即在您的两个部分中都这样:

    ForEach(self.viewModel.first, id: \.id) { id in
        Text("First List: \(id)")
           .onTapGesture {
                self.viewModel.swap(elementWithIdentifier: id, from: .first)
           }
    }
    

    这将使事情顺利进行。但是,在摆弄时我确实发现了这些问题:

    1. iOS14 中几乎不存在动画。不过,这可以解决。
    2. 在 iOS13 中,.listStyle(GroupedListStyle()) 动画看起来很奇怪。删除它,动画看起来会好很多。
    3. 我没有在大型列表中测试过这个解决方案。因此,请注意可能出现的性能问题。它适用于小型列表。

    再一次,这是一种解决方法,但我认为 Apple 仍在解决 SwiftUI 中的问题。

    更新

    PS 如果您在 iOS14 中使用任何 onDeleteonMove 修饰符,则会将动画添加到列表中,从而导致奇怪的行为。我发现使用 \.self 适用于 iOS14 和 \.self.id 适用于 iOS13。代码不是很漂亮,因为您很可能会在代码中检查#available(iOS 14.0, *)。但它有效。

    【讨论】:

      【解决方案3】:

      我解决这个问题的唯一方法是通过向列表添加随机 id 来防止列表的差异。不过,这会删除动画,因此请寻找更好的解决方案

      List {
      ...
      }
      .id(UUID())
      

      删除这些部分也可以解决此问题,但也不是有效的解决方案

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-11-05
        • 2020-11-15
        • 1970-01-01
        • 1970-01-01
        • 2021-05-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多