为啥 sorted(by:) 有效但 sort(by:) 在 ForEach 循环中无效

Posted

技术标签:

【中文标题】为啥 sorted(by:) 有效但 sort(by:) 在 ForEach 循环中无效【英文标题】:Why does sorted(by:) work but sort(by:) does not in ForEach loop为什么 sorted(by:) 有效但 sort(by:) 在 ForEach 循环中无效 【发布时间】:2020-10-13 14:54:03 【问题描述】:

我正在尝试以有序的方式显示自定义对象列表。 虽然这有效......

import SwiftUI

struct MyModel: Identifiable 
    let id = UUID()
    let timestamp: Date


struct ViewModel 
    var models = [
        MyModel(timestamp: Date().addingTimeInterval(300)),
        MyModel(timestamp: Date().addingTimeInterval(100)),
        MyModel(timestamp: Date().addingTimeInterval(500))
    ]


struct MyView: View 
    @State var viewModel = ViewModel()
    
    var body: some View 
        List 
            ForEach(viewModel.models.sorted(by:  (lhs, rhs) -> Bool in // will not work with `sort(by:)`
                lhs.timestamp > rhs.timestamp
            ))  model in
                Text("\(model.timestamp)")
            
            .onDelete(perform:  indexSet in
                //delete from viewModel.models
            )
        
    

...删除时它不够用,因为我正在处理的数组没有排序(可以说只是它的可视化)。

sort(by:) 应该是我需要的,因为它“对集合进行适当的排序,使用给定的谓词作为元素之间的比较。”根据 Apple 的文档。

但是,相同的ForEach 语句不适用于sort(by:),抛出错误Cannot convert value of type '()' to expected argument type 'Range<Int>'

在这种情况下,有没有办法使用ForEach 进行就地排序和循环,还是我必须在我的ViewModel 中处理排序?

【问题讨论】:

一个是变异函数,另一个返回一个新数组。如果您只想在视图模型中对数组进行一次排序 好的,明白了。变异后,我可以在队列中获取变异数组吗? 这里的变异意味着文档说它是被排序的原始数组,所以你已经有了它。 我也是这么想的——不过,我不能用 ForEach 循环它,这在 imo 中应该是可能的。 @ASSeeger 为什么应该是可能的? Void 的返回类型(又名空元组,())。迭代它与for i in print("hello world") 没有什么不同:这没有任何意义。您正在寻找非变异版本,为什么不直接使用它? 【参考方案1】:

之所以不能在ForEach 中使用sort(by:),是因为sort(by:) 是一个返回Void 的变异函数。另一方面,sorted(by:) 返回一个新数组,您可以使用 ForEach 遍历该数组。

您可以通过在ViewModel 上创建一个单独的计算属性来解决您的问题,该属性返回已排序的models 并使用此已排序的属性调用ForEach。您只需要确保您还对已排序的数组执行删除操作。

struct ViewModel 
    var models = [
        MyModel(timestamp: Date().addingTimeInterval(300)),
        MyModel(timestamp: Date().addingTimeInterval(100)),
        MyModel(timestamp: Date().addingTimeInterval(500))
    ]

    var sortedModels: [MyModel] 
        get 
            models.sorted(by:  $0.timestamp > $1.timestamp )
        
        set 
            models = newValue
        
    


struct MyView: View 
    @State var viewModel = ViewModel()

    var body: some View 
        List 
            ForEach(viewModel.sortedModels)  model in
                Text("\(model.timestamp)")
            
            .onDelete(perform:  indexSet in
                for index in indexSet 
                    viewModel.sortedModels.remove(at: index)
                
            )
        
    

【讨论】:

这是一个很好的解决方案,谢谢。我想我也可以在 Collection 上写一个扩展来拥有一个sort(by:) -> [Element],我想(理论上来说)。 这有点矫枉过正@LeoDabus,更实用的解决方案是在setter中排序(假设初始集合已经排序)。吸气剂应该是 O(1) @ClausJørgensen 此处显示的 getter 无论如何都会在每次调用此属性时对集合进行排序。 @DávidPásztor 顺便说一句,如果集合已经排序,则删除元素后不需要再次排序。删除元素时我也会反转索引。【参考方案2】:

无需创建计算属性。当您的列表出现时,您可以简单地对模型进行排序。如果模型已经排序,则无需在每次删除模型时对其进行排序。您还应该使您的模型符合可比较的协议:

import SwiftUI

struct ContentView: View 
    @State var viewModel = ViewModel()
    var body: some View 
        List 
            ForEach(viewModel.models) 
                Text("\($0.timestamp)")
            
            .onDelete 
                $0.reversed().forEach  viewModel.models.remove(at: $0) 
            
        .onAppear 
            viewModel.models.sort(by: >)
        
    


struct MyModel: Identifiable 
    let id = UUID()
    let timestamp: Date


extension MyModel: Comparable 
    static func < (lhs: Self, rhs: Self) -> Bool 
        lhs.timestamp < rhs.timestamp
    


struct ViewModel 
    var models = [
        MyModel(timestamp: Date().addingTimeInterval(300)),
        MyModel(timestamp: Date().addingTimeInterval(100)),
        MyModel(timestamp: Date().addingTimeInterval(500))
    ]


struct ContentView_Previews: PreviewProvider 
    static var previews: some View 
        ContentView()
    

【讨论】:

这实际上是一个好点!我决定在 ViewModel 中进行排序,因为我认为那是更好的地方(尝试坚持使用 MVVM 时)——但在其他情况下使用 .onAppear 可能会派上用场。【参考方案3】:

除了其他两个答案之外,我想添加更多关于您打算如何使用 ViewModel 的信息。

本质上,您希望有一个 ViewModel,它是一个 ObservableObject 和一个 @Published 属性,用于您希望更改的值。这样你就可以在 ViewModel 中封装任何重要的逻辑。

拥有一个结构 ViewModel 没有任何意义,因为结构不是可变的。

此外,您可以在模型类型上实现Comparable,允许您将排序绑定到类型,而不是在代码中的随机位置。现在,如果您的模型已经排序,那么当您删除单个模型时,如何也不必重新排序内容。

如果你有插入,你会想在插入时对它们进行排序。

最后,您应该只对 UI 状态使用 @State 变量,而不是 (View)Model 状态。

import SwiftUI

struct MyModel: Identifiable, Equatable, Comparable 
    let id = UUID()
    let timestamp: Date

    static func < (lhs: MyModel, rhs: MyModel) -> Bool 
        return lhs.timestamp > rhs.timestamp
    


class ViewModel: ObservableObject 
    @Published
    private(set) var models: [MyModel]

    init() 
        let now = Date()
        models = [
            MyModel(timestamp: now.addingTimeInterval(300)),
            MyModel(timestamp: now.addingTimeInterval(100)),
            MyModel(timestamp: now.addingTimeInterval(500))
        ].sorted()
    

    func remove(at indicies: IndexSet) 
        indicies.forEach  index in
            models.remove(at: index)
        
    


struct ContentView: View 
    @ObservedObject
    var viewModel = ViewModel()

    var body: some View 
        List 
            ForEach(viewModel.models)  model in
                Text("\(model.timestamp)")
            
            .onDelete(perform:  indexSet in
                viewModel.remove(at: indexSet)
            )
        
    

【讨论】:

感谢您指出我的 ViewModel 应该是一个类——非常有意义...多亏了你,我现在知道为什么这是关键了。

以上是关于为啥 sorted(by:) 有效但 sort(by:) 在 ForEach 循环中无效的主要内容,如果未能解决你的问题,请参考以下文章

sort与sorted

sorted使用详解

`sorted(list)` 与 `list.sort()` 有啥区别?

python sort() sorted()函数

sort、sorted排序技巧(多级排序)

python排序sorted与sort比较