SwiftData 谓词在用户输入时发生变化?

问题描述 投票:0回答:1

这几天我一直在和 SwiftData 斗争,但没有达成共识。我不得不说我只是一个初学者,所以我可能在其他地方也犯了错误,但我仍然不明白。

所以,我想做的是拥有一个

Word
(我的一类)列表,这些列表存储在 SwiftData 中,根据用户选择的类别进行过滤。不过 SwiftData 似乎还有其他想法。

为了组织代码,我从 Apple 的示例代码中获得灵感。

Category
型号

让我们从

Category
模型开始(它代表单词可能所属的类别)。
ColorComponents
是一个非常简单的
Codable
结构,我编写它来存储颜色,并不重要。

import Foundation
import SwiftData

@Model
class Category: Codable, Equatable {
    enum CodingKeys: CodingKey {
        case name, primaryColor, secondaryColor
    }
    
    @Attribute(.unique) let name: String
    let primaryColor: ColorComponents
    let secondaryColor: ColorComponents
    
    init(name: String, primaryColor: ColorComponents, secondaryColor: ColorComponents) {
        self.name = name
        self.primaryColor = primaryColor
        self.secondaryColor = secondaryColor
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        self.primaryColor = try container.decode(ColorComponents.self, forKey: .primaryColor)
        self.secondaryColor = try container.decode(ColorComponents.self, forKey: .secondaryColor)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.name, forKey: .name)
        try container.encode(self.primaryColor, forKey: .primaryColor)
        try container.encode(self.secondaryColor, forKey: .secondaryColor)
    }
    
    static func ==(lhs: Category, rhs: Category) -> Bool {
        lhs.name == rhs.name
    }
    
    static let example = Category(name: "General", primaryColor: ColorComponents(color: .mint), secondaryColor: ColorComponents(color: .blue))
}

Word
型号

然后是

Word
模型。现在,它包含一个返回谓词的静态方法。 Apple 的示例代码表明了这一点,并且可能是使谓词与其输入数据一起更改的唯一方法。

import Foundation
import SwiftData

@Model
class Word {
    let term: String
    let learntOn: Date
    var notes: String
    @Relationship var category: Category?
    
    var categoryName: String {
        category?.name ?? "No category"
    }
    
    init(term: String, learntOn: Date, notes: String = "", category: Category? = nil) {
        self.term = term
        self.learntOn = learntOn
        self.notes = notes
        self.category = category
    }
    
    static func predicate(category: Category?) -> Predicate<Word> {
        return #Predicate<Word> { word in
            // this expression is what I would like to have, but it throws an error at runtime
            category == nil || word.category == category
        }
    }
    
    static let example = Word(term: "Swift", learntOn: .now, notes: "A swift testing word.")
}

这是我有的两个型号。在主视图中,我使用

.modelContainer(for: Word.self)
创建模型容器。

SwiftUI 视图

然后我就可以看到正在进行查询的视图。根据苹果公司的说法,考虑到类别被传递给初始化器本身,这种做法可以确保查询在每次类别更改时更新(理想情况下,我希望用户能够随时选择)。

import SwiftData
import SwiftUI

struct WordsCardsListView: View {
    let category: Category?
    @Query private var words: [Word]
    
    init(category: Category? = .example) {
        self.category = category
        
        let predicate = Word.predicate(category: category!)    // force unwrapping just for testing, of course
        let sortDescriptors = [
            SortDescriptor(\Word.learntOn, order: .reverse)
        ]
        _words = Query(filter: predicate, sort: sortDescriptors)
    }
    
    var body: some View {
        List {
            // other views
            
            ForEach(words) { word in
                WordCardView(word: word)
                    .listRowSeparator(.hidden)
            }
        }
        .listStyle(.plain)
    }
}

我遇到的错误

我相信,我确实尝试了所有可能的组合,但我总是在运行时收到

SwiftData.SwiftDataError._Error.unsupportedPredicate

 错误(或者有时谓词甚至无法编译)。据我所知,谓词不支持比较对象(也许,每次我尝试比较 
Category
 甚至 
Word
 时它都会失败),并且在尝试访问 
word.category?.name
 时也会失败,无论是使用可选链接或强制展开(鉴于该类别的名称是唯一的,我也可以接受)。我确实知道谓词在它们可以接受的表达式方面受到一定限制,但我不明白为什么 Apple 实现有效而我的实现不行,因为我相信没有显着差异。

我确实知道最简单的解决方案是查询所有单词,然后过滤它们(这可能就是我最终要做的),但令我困惑的是,这样一个简单的想法(实时更新的过滤器)是使用 SwiftData 不太容易获得。

无论如何,我感谢所有读到这里并且需要花时间回答的人。

ios swift swiftui swift-data
1个回答
0
投票
有几件事至少可以帮助您缩小错误根源的范围。

首先,您可以处理 func 的

category

 参数的可选性,并返回一个单独的谓词(如果它是 
nil
:

static func predicate(category: Category?) -> Predicate<Word> { if let category { // match the provided category #Predicate { /* ... */ } } else { #Predicate { _ in true } } }
接下来,要实际比较类别,您需要考虑 

Word.category

 关系的可选性。在谓词中,您还不能直接比较选项,但您应该能够执行类似的操作

static func predicate(category: Category?) -> Predicate<Word> { if let category { #Predicate { word in category.name == (word.category?.name ?? "") } } else { #Predicate { _ in true } } }
这似乎太复杂了,事实确实如此。但我会考虑你的观点 - 与其拥有一个可选择采用类别的视图,为什么不拥有一个采用特定类别的视图,删除可选性,如果你需要一个显示所有单词的视图,请将其作为一个单独的视图?两个视图都可以在其 

body

 内共享组件,因此您不必重复执行所有相同的工作。这将消除您的 
func predicate
 中对一种形式的可选性的需求,这将极大地帮助您的 SwiftData 编译。

© www.soinside.com 2019 - 2024. All rights reserved.