是否可以在 SwiftUI 中从计算的 属性 创建绑定 属性?

Is it possible to create a binding property from a computed property in SwiftUI?

在我的模型数据中class我有一个任务数组。

class Model: ObservableObject {
    @Published var tasks: [Task] = Task.sampleData
}

在我的 HomeView 中,我显示了这些任务,但是用户可以使用选择器按创建日期或截止日期对它们进行排序。在 HomeView 中,我有一个枚举,其中包含用户可以对它们进行排序的选项和一个状态实例来保存选取的值。默认设置为按截止日期排序。

@EnvironmentObject private var model: Model
@State private var sortOption: SortOption = .dueDate

enum SortOption {
    case dueDate, creationDate
}

然后我决定创建一个包含排序数组的计算值,这样我就不会更改原始任务数组,因为它用于在应用程序的其他部分显示任务。

var sortedTasks: [Task] {
    model.tasks.sorted(by: {
        if sortOption == .dueDate {
            return [=14=].dueDate < .dueDate
        } else {
            return [=14=].creationDate < .creationDate
        }
    })
}

不久我就运行陷入了这个问题。我的任务行需要一个绑定任务,我无法从计算的 属性 中检索所需的绑定,这是 sortedTasks 数组。

// ERROR: Cannot find '$sortedTasks' in scope
ForEach($sortedTasks) { $task in
    TaskRow(task: $task)
}

我的理解是,如果我想保留 sortedTasks 数组,那么我将不得不从 TaskRow 中删除绑定 属性,但是如果我想保留绑定 属性,应该采用什么方法是显示排序数组而不操作数据模型中的任务数组class?计算出的 属性 return 可以是绑定任务对象的排序数组吗?

你不能那样做,但你可以在这里使用 Combine 方法:

将您的 sortedTaskssortOption var 移动到名为 Model 的 class 并从您的两个 vars 创建一个应该触发 sortedTasks 的组合发布者要更新的 var。对收到的 collection 进行排序并将其分配给您的 sortedTasks.

class Model: ObservableObject {
    @Published var tasks: [TaskModel] = TaskModel.sampleData
    
    @Published var sortedTasks: [TaskModel] = TaskModel.sampleData
    @Published var sortOption: SortOption = .dueDate

    init(){
        Publishers.CombineLatest($sortOption, $tasks)
            .map{ sortOption, tasks in 
               // apply sorting here
            }.assign(to: &$sortedTasks) // assign to your sorted var
    }
    
}

像这样使用它:

var body: some View{
        HStack{
            ForEach($model.sortedTasks) { $task in
//                TaskRow(task: $task)
            }
        }
}

我已将 Task 模型重命名为 TaskModel,因为我认为以提供的系统命名的结构是一个非常糟糕的主意 class。

排序取决于 sortOption,它是视图状态,因此在模型中进行排序是不正确的,因为这样您就无法使用不同的排序选项来获得不同的视图。解决方案是对绑定进行排序,而不是对任务进行排序,例如

var sortedTasks: [Binding<Task>] {
    $model.tasks.sorted { $x, $y in // $model.tasks.animation().sorted if you would like animations like List row moves.
        if sortOption == .dueDate {
            return x.dueDate < y.dueDate
        } else {
            return x.creationDate < y.creationDate
        }
    }
}

ForEach(sortedTasks) { $task in
    TaskRow(task: $task)
}

您可能想知道 ForEach 如何找到 id,那是因为 Binding@dynamicMemberLookup,这意味着它将 属性 查找转发给它值。