为啥 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 循环中无效的主要内容,如果未能解决你的问题,请参考以下文章