这几天我一直在和 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)
创建模型容器。
然后我就可以看到正在进行查询的视图。根据苹果公司的说法,考虑到类别被传递给初始化器本身,这种做法可以确保查询在每次类别更改时更新(理想情况下,我希望用户能够随时选择)。
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 不太容易获得。
无论如何,我感谢所有读到这里并且需要花时间回答的人。
首先,您可以处理 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 编译。